Skip to content

项目课第一节笔记

统计信息:字数 19991 阅读40分钟

2021-10

前端工程师基本技能:vue+react+node 基本实现增删改查功能(node+mysql+vue)

官方第一节笔记

项目尽可能把每个需求,都做成亮点(站在架构师的⻆度,来设计项目)

  1. 纠正视⻆,不从开发工程师的⻆度来看,从前端架构师角度,增删改查的代码,只是一小部分工作

  2. 一个项目需要什么

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. 一个项目怎么做才算亮点(亮点才能升职加薪,面试展示自己)

    1. 每个需求,都可以做成你的亮点,只要你有心(性能报错卡顿)

      1. 数据量很大(淘宝电商)
      2. 网络不稳定,性能受限(政府电脑老版本)
      3. 用户体验(产品把用户想成傻子)
      

    2. 文件上传

    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,
      };
    });
  }
}
  1. 计算hash卡顿解决了,比如100个切片需要发送,请求数量很多:

        1. 如果直接promise.all上传,浏览器发起 100 个 tcp 请求,虽然浏览器有并发限制,只会同时发送6个传递数据,同时建立这么多请求,会让浏览器卡顿。
    
        2. 控制并发数,比如4,异步任务的并发数控制,使用队列就可以了。这个功能是头条经常用的笔试题
    
        3. 还可以做报错重试:异步任务通过一个队列。如果任务报错,出列再塞进去;同一个任务报错3次,或者2次,统一终止整个上传任务,提示用户报错,重试,用对象存储报错的次数 {task1:1}
    

  2. 根据网速确定切片大小 :先传一个切片,看看返回的时间 2. 怎么判定上传速度合适?使用TCP的慢启动逻辑,很流畅。先丢一个小区块,判断返回时间,如果比较短 *2,如果超时/2。系数可以用一些数据公式变得平缓一些。

  3. 文件扩展名,怎么判断用户上传的是符合要求的文件呢(如果我们只能上传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);
}
  1. 表格渲染,列表渲染:数据量大,PC端分⻚,移动端无间隔滚动,使用虚拟列表

需求分析

  1. 登录、注册、jwt
  2. 个人中心、图片上传
  3. 文章发布(简单的定制一下markdown编辑器)
  4. 文章列表:移动端使用无限滚动,虚拟列表
  5. 用户关注,文章点赞(踩),评论(用户一对多,多对多的关系设计,需要后端设计表,mysql 多对多
  6. web-rtc 浏览器拿到摄像头的权限

技术选型

  1. 技术选型没有对错,只有合不合适(VUE-REACT、element)
  2. 团队现状、上手难度、技术生态
  3. 技术选型对比:组件数;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配置等等

做需求的时候,用第三方插件没问题的,但是,想进步,就要看源码

我们开发项目,用第三方库即可。想进步,不能只会用,而要看懂源码

站在一个稍微高级一点的视⻆,一个项目到底需要那些东⻄

目的是为了站在一个架构师的⻆度 代码能力只是其中一部分(整体把控)

第二节笔记

上次回顾:复杂项目对自己的要求,需要做成什么样。

  1. 数据量大
  2. 网络,电脑的性能,很差
  3. 用户想的傻一些

前端架构师的身份,如何看待项目?从工程师=》前端管理的过程,别的知识体系

  1. 项目人员的规划(招聘人数,需要不同层次)
  2. 需求问题(项目管理)
  3. 项目的可维护性(未来)

  4. 代码的实现是开心的现在

  5. 小项目的leader之后,如何进一步

  6. 每一条路,对知识和能力的要求是不一样的 就从这个项目开始展开

  7. 登录注册
  8. 用户中心
  9. 文章管理
  10. 关注点赞(多对多的关系设计)
  11. 评论

做项目之后怎么可以维护,后期怎么发展?

  1. 制定规范(规矩)
  2. 开源(开发效率,影响力)
  3. code review
  4. 工具(开发工具)

代码之后

  1. 人效(如何提高每个人的效率,每个维度综合考虑)代码量(吐槽),可以用gitlab的api,来统计;每个人都要触碰到自己的极限。比如轮岗(每个人尽可能能维护两个模块+) 比如内部系统奖励机制(团队文化),发挥内部主管能动性。日志监控,主动领任务
  2. 基础建设

项目⻓期维护的必备设置

命令行工具cli 组件(代码规范,单元测试)

埋点(数据采集:性能监控、错误监控、用户行为错误监控)

  1. 性能监控;浏览器或者node把统计的性能参数,发给我们服务器;perfomance;lighthourse(宏观的)
window.performance
{ startTime, requestStart, responseStart, responseEnd } 
资源加载耗时 = responseEnd - startTime
  1. 错误监控:sentry;fundebug 也挺有意思的,复现功能;原理也不难,window.onerror,trycatch;主动上报;promise报错。可以把错误写到一个 GIF 图片中,然后发送到后端(百度实现方法)

  2. 用户行为日志监控:ga;百度统计;growingio

构建发布:gitlab;github(action);自动化发布,钉钉推送消息

第二个视频

代码写完了,项目做完后,之后怎么思考?技术收获+人生规划

计划(向上管理)

  1. 团队的资源
  2. 团队的成⻓
  3. 目的是通过某个项目,优化自己的知识体系(同一个项目,同一个需求,不同的前端,做出来就是不一样)

对团队和自己的要求(技术级别,有点像学历)

  1. P5:中级工程师:独立完成经验丰富,给我需求可以完成,重点是代码
  2. P6:高级工程师:要求担当(军令状,可以完成一个大型任务),前沿研究(分析问题的能力)踩坑(源码阅读)
  3. P7:领域专家;体系化知识,某个领域内有卓越的产出(github开源项目几百个星星)

技术路线,可以是P级别是其中一条路,winter老师,玉伯,张云⻰,尤大,张鑫旭。。。有很多路线(大家基本知识熟悉,需要有自己的特长)前端的路线又很多,每条路要求的知识都不一样

  1. 玉伯,体验科技,深挖前端技术和管理,语雀
  2. winter,P8之后,再搞教育(横向发展,没有继续深造P),手淘
  3. 张云⻰(fis的作者):全民直播,走的是全栈CTO的路线
  4. 张鑫旭,css死磕,阅文集团
  5. 尤大:独立开发者

思考一下自己未来的发展

  • 大公司前端架构师:多关注技术:技术原理,vuenuxt,egg;计算机基础;算法,网络,操作系统,数据库.... ;从算法开始,算法训练营欢迎大家;网络协议跟上;前端的前沿技术。工作内容:有没有兴趣读完vue源码?

  • 大公司前端经理:多关注人:管理,听起来高大上,干起来很辛苦;人员的招聘规划,进度的管理,资源的协调。。。 懂技术,懂人;同时,你的技术还不能落下,否则底下的人对你没有技术尊重,队伍不好带啊。工作的内容会发生变化:把我们的小开社区对外发布维护,你需要几个人;这几个人,怎么招,跟hr聊;招到之后,怎么建设团队(提升积极性;提升成员技术能力)流程有效执行(绩效KPI,还是okr)绩效差的哥们,和好的哥们,怎么去跟他沟通;怎么留人和开人(阿里连续两年KPI最差被开了)有很多类似这种任务

  • 小公司技术总监:稍微弱化前端,强化后端或者全栈;强化产品能力;强化管理能力;接触的更广,更泛泛,更需要综合实力
  • 独立开发者,独立做项目:公司就你一个人,做一个产品来养活自己(需要综合的能力:全栈!!!;营销;产品;用户增⻓)什么都干;极可能多用开源和成型产品;等等——国内不多

加班

很多公司都有加班,⻓期996的结果,并不能提升你的效率,只会让你爱上摸⻥。

  1. 学习的重要性大于加班
  2. 短期996可以接受,常年996还是算了(自己有一点生活,业余时间可以学习)
  3. 加班能学会算法吗?加班可以短期内让技能更熟练,编程的高手更需要学习(学习好的算法)

以后学不动了会不会被淘汰啊

  1. 只学皮毛,一定会被淘汰;学核心的知识,核心知识永不过时(算法、编译原理,ast 抽象语法树、codegen核心理念)
  2. 武林高手要学习内力(学习算法),而不是十八般兵器(不是学习各种语言,各种框架)
  3. 任何一个知识点,都有一本经典的书,死磕下来,你就超过了大部分同行(算法第四版,犀牛书,红宝书,每年都看一遍)博客适合查缺补漏,不适合整体学习(一篇博客学会犀牛书不行)
  4. 任何一个值钱的技能,都不可能简单的获得

前端的路很宽广的,是技术圈最懂用户体验的,最有可能独立做出项目的。年龄限制,只针对增删改查程序员。贪多嚼不烂。

  1. 无论是你想赚钱,提升编程,职位晋升,都需要看书
  2. 就算你想找对象,我也推荐一本书《魔⻤约会学》,如何聊天
  3. 技术层面的书,豆瓣8分以上,都值得看
  4. 我早上看书,无论几点睡觉,6点~7点起床,出去跑步,看一个小时书
  5. 开课吧的装修,就是图书馆,公司也可以看书
  6. 读书其实是有快感的;薪资的提升《算法》逼格比较高,适合装逼

项目的思考,职业生涯的思考

读书:HTTP 权威指南;你不知道的 JS(三本书);算法(第四版);如何阅读一本书;程序员的自我修养;不好看的书,看一下就扔掉,不浪费时间


Last update: November 9, 2024