项目课第一节笔记¶
统计信息:字数 19991 阅读40分钟
2021-10
前端工程师基本技能:vue+react+node 基本实现增删改查功能(node+mysql+vue)
官方第一节笔记¶
项目尽可能把每个需求,都做成亮点(站在架构师的⻆度,来设计项目)
-
纠正视⻆,不从开发工程师的⻆度来看,从前端架构师角度,增删改查的代码,只是一小部分工作
-
一个项目需要什么
1. 文档
2. 版本控制(git,gitlab, github)制定规范(分支管理,git message)master test dev
3. 质量(代码质量,eslint,jest、jira。。bug管理)
4. 开发流程(敏捷开发)
5. 写代码(代码设计(分模块,分任务),代码实现,联调)
6. 自动化测试;发布部署(自动化部署 docker 部署)mvp版本发步,给产品测试评估反馈,后续考虑的工作和任务(0.1版本上线)
7. 维护,功能开发456持续执行(0.x 版本上线)
8. 提升开发效率(组件化,发npm包,考虑在内部网络搭建私有npm服务)
9. 权限,监控,统计,报错收集,量化我们的产品性能
10. 上面提高开发效率的内容,考虑固化沉淀为系统,这就是前端团队的基础建设
-
一个项目怎么做才算亮点(亮点才能升职加薪,面试展示自己)
-
每个需求,都可以做成你的亮点,只要你有心(性能报错卡顿)
1. 数据量很大(淘宝电商) 2. 网络不稳定,性能受限(政府电脑老版本) 3. 用户体验(产品把用户想成傻子)
-
文件上传
1. inputtype=file,axios.post,node接受文件存起来,over 最多加一个上传进度条 2. 粘贴,拖拽(事件处理)
-
上传文件
<template>
<input type="file" name="file" @change="handleFileChange">
<el-button @click="uploadFile"></el-button>
<el-precess :stroke-width='20' :text-inside="true" : percentage="uploadProcess"></el-precess>
</template>
3. 文件2个G的视频,网速100K还不稳定,偶尔会断开
1. 文件切片,分片上传
2. 断点续传(上传之前,后端告知已经存在的切片,需要后端的配合)
3. file.slice()就可以做文件切片了-也需要后端配合
4. 如何让后端知道你是哪个文件?如何确定文件的唯一性,用文件名肯定不靠谱(使用md5做哈希)Md5,计算密集,2G的文件大概计算md5要15秒左右的时间,怎么解决卡顿问题?
1. webworker(会额外加载js,有性能额外的损耗,需要新的进程)
2. 框架源码,怎么处理任务量大这个场景?(逻辑比较复杂)
1. 时间切片来计算,利用浏览器空闲时间计算
2. requestIdleCallback 你也可以自己模拟,利用 event-loop 的机制就可以模拟。浏览器中 Performance 可以看到 idle 部分的时间。浏览器的时间分为:scripting, rendering, painting, system, idle 部分。然后加密算法可以在浏览器空闲时间计算(参考React源码就是自己模拟的)
3. 抽样哈希(野路子):抽取特征值。每个切片都是1M,第一个切片和最后 一个切片全部的数据。中间的切片取前中后2各字节,拼在一起。文件多大,抽样值都在3M以内。布隆过滤器(算法)两个文件hash一样,可能文件不一样,hash不一样,文件一定不一样。file.slice不会造成卡顿,浏览器并没有新建内存区间来存储。
web-worker 计算哈希
self.importScripts('spark-md5.min.js');
// 参数是主线程传递的数据
self.onmessage = (e) => {
const { chunks } = e.data;
const spark = new self.SparkMD5.ArrayBuffer();
let progress = 0;
let count = 0;
const loadNext = index => {
const reader = new FileReader();
reader.readAsArrayBuffer(chunks[index].file);
reader.onload = (e) => {
count++;
spark.append(e.target.result);
if (count == chunks.length) {
self.postMessage({
progress: 100,
hash: spark.end(),
})
} else {
progress += 100 / chunks.length;
self.postMessage({
progress,
})
loadNext(count);
}
}
}
loadNext(0);
}
计算哈希的三种方法
import { allResolved } from "q";
class Upload {
// 判断图片文件
async isImage(file) {
return await this.isGif(file) || await this.isPng(file);
}
// 创建文件分片(按照尺度切分文件)
createFileChunk(file, size = CHUNK_SIZE) {
const chunks = [];
let cur = 0;
while (cur < this.file.size) {
chunks.push({
index: cur,
file: this.file.slice(cur, cur + size),
});
}
return chunks;
}
// 计算哈希方法1:通过外部的 webworker 计算哈希值
async calculateHashWorker() {
return new Promise((resolve) => {
// 这个文件单独写
this.worker = new Worker('./hash.js');
// 主线程和工作线程通信方法
this.worker.postMessage({ chunks: this.chunks });
this.worker.onmessage = (e) => {
const { progress, hash } = e.data;
this.hasProgress = Number(progress.toFixed(2));
if (hash) {
resolve(hash);
}
}
});
}
// 计算哈希方法2:通过浏览器空闲时间计算
// 浏览器通常 60 fps,那么每次的间隔是 16.7 Ms
async calculateHashIdle() {
const chunks = this.chunks;
return new Promise((resolve) => {
const spark = new sparkMD5.ArrayBuffer();
let count = 0;
const appendToSpark = async (file) => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = e => {
spark.append(a.target.result);
}
})
}
const workLoop = async (deadline) => {
// timeRemaining 当前帧的剩余时间
while (count < chunks.length && deadline.timeRemaining() > 1) {
// 空闲时间,且切片还有
await appendToSpark(chunks[count].file);
count++;
if (count < chunks.length) {
this.hashProgress = Number(
((100 * count) / chunks.length.toFixed(2))
);
} else {
this.hashProgress = 100;
allResolved(spark.end());
}
}
// 方法内部会继续调用自己,保证继续算hash
window.requestIdleCallback(workLoop);
}
// 浏览器空闲就会执行这个方法(执行一次)
window.requestIdleCallback(workLoop);
})
}
// 计算哈希方法3:布隆计算法
async calculateHashSample() {
// 布隆过滤器,判断一个数据是否存在
// 一个G的文件,抽样后在5M之内
// hash 一样,文件不一定一样
// hash不一样,文件一定不一样
return new Promise((resolve) => {
const spark = new sparkMD5.ArrayBuffer();
const reader = new FileReader();
const file = this.file;
const size = file.size;
const offset = 2 * 1024 * 1024;
// 第一个和最后一个区块的数据全部要
let chunks = [file.slice(0, offset)];
let cur = offset;
while (cur < size) {
if (cur + offset >= size) {
// 最后一个区块
chunks.push(file.slice(cur, cur + offset));
} else {
// 中间的区块
const mid = cur + offset / 2;
const end = cur + offset;
chunks.push(file.slice(cur, cur + 2));
chunks.push(file.slice(mid, mid + 2));
chunks.push(file.slice(end - 2, end));
}
cur += offset;
}
// 中间的部分,取前中后两个字节即可
reader.readAsArrayBuffer(new Blob(chunks));
reader.onload = e => {
spark.append(e.target.result);
this.hashProgress = 100;
resolve(spark.end());
}
})
}
async uploadFile() {
// 先判断文件类型
if (!await this.isImage(this.file)) {
console.log('file is not image');
} else {
console.log('file is correct');
}
// 文件分片
const chunks = this.createFileChunk(this.file);
// 三种计算hash的方法(上面的方法)
// webworker
// const hash = await this.calculatedHashWorker();
// idle
// const hash = await this.calculatedHashIdle();
// sample
// 抽样哈希,不算全部的hash,布隆过滤器算法,损失小部分精度,换取哈希计算效率
const hash = await this.calculateHashSample();
this.hash = hash;
this.chunks = chunks.map((chunk, index) => {
const name = hash + '-' + index;
return {
hash,
name,
index,
chunk: chunk.file,
progress: 0,
};
});
}
}
-
计算hash卡顿解决了,比如100个切片需要发送,请求数量很多:
1. 如果直接promise.all上传,浏览器发起 100 个 tcp 请求,虽然浏览器有并发限制,只会同时发送6个传递数据,同时建立这么多请求,会让浏览器卡顿。 2. 控制并发数,比如4,异步任务的并发数控制,使用队列就可以了。这个功能是头条经常用的笔试题 3. 还可以做报错重试:异步任务通过一个队列。如果任务报错,出列再塞进去;同一个任务报错3次,或者2次,统一终止整个上传任务,提示用户报错,重试,用对象存储报错的次数 {task1:1}
-
根据网速确定切片大小 :先传一个切片,看看返回的时间 2. 怎么判定上传速度合适?使用TCP的慢启动逻辑,很流畅。先丢一个小区块,判断返回时间,如果比较短 *2,如果超时/2。系数可以用一些数据公式变得平缓一些。
- 文件扩展名,怎么判断用户上传的是符合要求的文件呢(如果我们只能上传png图片)因为每个文件都有固定的头信息,二进制的文件流 固定位数的值,确定一个文件类型 ,通过文件内容判断,而不是简单的后缀名(实例:把一个png图片的后缀改成 PDF,然后上传)VSCode hexdump图片你的宽高,也在二进制里(可以直接读取到图片的尺寸)。因为二进制写起来比较麻烦, 通常4个二进制一起,变成16进制,好显示。
async blobToString(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function() {
// console.log(readers.result)
const ret = reader.result.split('').map(v => v.charCodeAt()).map(v => v.toString(16).toUpperCase()).join('');
resolve(ret);
}
reader.readAsBinaryString(blob);
});
}
async isGif(file) {
// GIF 有两个标准 89a 87a
// 前面6个是16进制(需要进制转换)
const ret = await this.blobToString(file.slice(0, 6));
return (ret == '47 49 46 38 39 61') || (ret == '47 49 46 38 37 61');
}
async isPng(file) {
const ret = await this.blobToString(file.slice(0, 8));
return ret == '89 50 4E 47 0D 0A 1A 0A';
}
async isJpg(file) {
const len = file.size;
const start = await this.blobToString(file.slice(0, 2));
const end = await this.blobToString(file.slice(-2, len));
return start == 'FF D8' && end == 'FF D9';
}
// 通过文件流判断图片文件
// 先判断是不是gif
async isImage(file) {
return await this.isGif(file) || await this.isPng(file);
}
- 表格渲染,列表渲染:数据量大,PC端分⻚,移动端无间隔滚动,使用虚拟列表
需求分析¶
- 登录、注册、jwt
- 个人中心、图片上传
- 文章发布(简单的定制一下markdown编辑器)
- 文章列表:移动端使用无限滚动,虚拟列表
- 用户关注,文章点赞(踩),评论(用户一对多,多对多的关系设计,需要后端设计表,mysql 多对多
- web-rtc 浏览器拿到摄像头的权限
技术选型¶
- 技术选型没有对错,只有合不合适(VUE-REACT、element)
- 团队现状、上手难度、技术生态
- 技术选型对比:组件数;npm下载;团队人数;某个组件;按需加载;配合的admin框架
开发规范¶
- eslint:如果是老项目,可以考虑增量eslint:lint-staged(旧代码不处理,新代码处理)
- git 分支管理
- git钩子:precommit之前,跑eslint
- gitlog 规范:gitcommit-m'日志规范
- Npm script 工作流
- 目录规范 nuxt+eggjs 这俩自己的规范,我们用就可以了
- 统计:国内百度统计;GA(google analysis);growingio
- 报错:sentry 错误日志数据
- 代码部署:github action 或 gitlab 简单的自动化;push 触发任务,跑测试,发布部署,部署结果通知企业微信或者钉钉;
- AXIOS配置等等
做需求的时候,用第三方插件没问题的,但是,想进步,就要看源码
我们开发项目,用第三方库即可。想进步,不能只会用,而要看懂源码
站在一个稍微高级一点的视⻆,一个项目到底需要那些东⻄
目的是为了站在一个架构师的⻆度 代码能力只是其中一部分(整体把控)
第二节笔记¶
上次回顾:复杂项目对自己的要求,需要做成什么样。
- 数据量大
- 网络,电脑的性能,很差
- 用户想的傻一些
前端架构师的身份,如何看待项目?从工程师=》前端管理的过程,别的知识体系
- 项目人员的规划(招聘人数,需要不同层次)
- 需求问题(项目管理)
-
项目的可维护性(未来)
-
代码的实现是开心的现在
-
小项目的leader之后,如何进一步
-
每一条路,对知识和能力的要求是不一样的 就从这个项目开始展开
- 登录注册
- 用户中心
- 文章管理
- 关注点赞(多对多的关系设计)
- 评论
做项目之后怎么可以维护,后期怎么发展?¶
- 制定规范(规矩)
- 开源(开发效率,影响力)
- code review
- 工具(开发工具)
代码之后¶
- 人效(如何提高每个人的效率,每个维度综合考虑)代码量(吐槽),可以用gitlab的api,来统计;每个人都要触碰到自己的极限。比如轮岗(每个人尽可能能维护两个模块+) 比如内部系统奖励机制(团队文化),发挥内部主管能动性。日志监控,主动领任务
- 基础建设
项目⻓期维护的必备设置¶
命令行工具cli 组件(代码规范,单元测试)
埋点(数据采集:性能监控、错误监控、用户行为错误监控)
- 性能监控;浏览器或者node把统计的性能参数,发给我们服务器;perfomance;lighthourse(宏观的)
window.performance
{ startTime, requestStart, responseStart, responseEnd }
资源加载耗时 = responseEnd - startTime
-
错误监控:sentry;fundebug 也挺有意思的,复现功能;原理也不难,window.onerror,trycatch;主动上报;promise报错。可以把错误写到一个 GIF 图片中,然后发送到后端(百度实现方法)
-
用户行为日志监控:ga;百度统计;growingio
构建发布:gitlab;github(action);自动化发布,钉钉推送消息
第二个视频¶
代码写完了,项目做完后,之后怎么思考?技术收获+人生规划
计划(向上管理)
- 团队的资源
- 团队的成⻓
- 目的是通过某个项目,优化自己的知识体系(同一个项目,同一个需求,不同的前端,做出来就是不一样)
对团队和自己的要求(技术级别,有点像学历)¶
- P5:中级工程师:独立完成经验丰富,给我需求可以完成,重点是代码
- P6:高级工程师:要求担当(军令状,可以完成一个大型任务),前沿研究(分析问题的能力)踩坑(源码阅读)
- P7:领域专家;体系化知识,某个领域内有卓越的产出(github开源项目几百个星星)
技术路线,可以是P级别是其中一条路,winter老师,玉伯,张云⻰,尤大,张鑫旭。。。有很多路线(大家基本知识熟悉,需要有自己的特长)前端的路线又很多,每条路要求的知识都不一样
- 玉伯,体验科技,深挖前端技术和管理,语雀
- winter,P8之后,再搞教育(横向发展,没有继续深造P),手淘
- 张云⻰(fis的作者):全民直播,走的是全栈CTO的路线
- 张鑫旭,css死磕,阅文集团
- 尤大:独立开发者
思考一下自己未来的发展¶
-
大公司前端架构师:多关注技术:技术原理,vuenuxt,egg;计算机基础;算法,网络,操作系统,数据库.... ;从算法开始,算法训练营欢迎大家;网络协议跟上;前端的前沿技术。工作内容:有没有兴趣读完vue源码?
-
大公司前端经理:多关注人:管理,听起来高大上,干起来很辛苦;人员的招聘规划,进度的管理,资源的协调。。。 懂技术,懂人;同时,你的技术还不能落下,否则底下的人对你没有技术尊重,队伍不好带啊。工作的内容会发生变化:把我们的小开社区对外发布维护,你需要几个人;这几个人,怎么招,跟hr聊;招到之后,怎么建设团队(提升积极性;提升成员技术能力)流程有效执行(绩效KPI,还是okr)绩效差的哥们,和好的哥们,怎么去跟他沟通;怎么留人和开人(阿里连续两年KPI最差被开了)有很多类似这种任务
- 小公司技术总监:稍微弱化前端,强化后端或者全栈;强化产品能力;强化管理能力;接触的更广,更泛泛,更需要综合实力
- 独立开发者,独立做项目:公司就你一个人,做一个产品来养活自己(需要综合的能力:全栈!!!;营销;产品;用户增⻓)什么都干;极可能多用开源和成型产品;等等——国内不多
加班¶
很多公司都有加班,⻓期996的结果,并不能提升你的效率,只会让你爱上摸⻥。
- 学习的重要性大于加班
- 短期996可以接受,常年996还是算了(自己有一点生活,业余时间可以学习)
- 加班能学会算法吗?加班可以短期内让技能更熟练,编程的高手更需要学习(学习好的算法)
以后学不动了会不会被淘汰啊
- 只学皮毛,一定会被淘汰;学核心的知识,核心知识永不过时(算法、编译原理,ast 抽象语法树、codegen核心理念)
- 武林高手要学习内力(学习算法),而不是十八般兵器(不是学习各种语言,各种框架)
- 任何一个知识点,都有一本经典的书,死磕下来,你就超过了大部分同行(算法第四版,犀牛书,红宝书,每年都看一遍)博客适合查缺补漏,不适合整体学习(一篇博客学会犀牛书不行)
- 任何一个值钱的技能,都不可能简单的获得
前端的路很宽广的,是技术圈最懂用户体验的,最有可能独立做出项目的。年龄限制,只针对增删改查程序员。贪多嚼不烂。
- 无论是你想赚钱,提升编程,职位晋升,都需要看书
- 就算你想找对象,我也推荐一本书《魔⻤约会学》,如何聊天
- 技术层面的书,豆瓣8分以上,都值得看
- 我早上看书,无论几点睡觉,6点~7点起床,出去跑步,看一个小时书
- 开课吧的装修,就是图书馆,公司也可以看书
- 读书其实是有快感的;薪资的提升《算法》逼格比较高,适合装逼
项目的思考,职业生涯的思考
读书:HTTP 权威指南;你不知道的 JS(三本书);算法(第四版);如何阅读一本书;程序员的自我修养;不好看的书,看一下就扔掉,不浪费时间