JS & 项目功能相关文章信息点整理(一)|青训营笔记

32 阅读10分钟

这是我参与「第五届青训营」伴学笔记活动的第7天。

2月20日 打卡 day14

今日学习《在 JavaScript 中,什么时候使用 Map 或胜过 Object》。

总结:

  • Map 比 Object 快,除非有小的整数、数组索引的键,且更省内存;如果需要一个频繁更新的 hash map,请使用 Map;如果想一个固定的键值集合(即记录),请使用 Object,并注意原型继承带来的陷阱。
  • ES6的Map像是一个功能更强的对象,但接口却有些笨拙。
  • 在 Hash Map 中使用对象最明显的缺点是,对象只允许键是字符串和 symbol。任何其他类型的键都会通过 toString 方法被隐含地转换为字符串。
  • 更重要的是,使用对象做 Hash Map 会造成混乱和安全隐患。
  • 在 ES6 之前,获得 hash map 唯一方法是创建一个空对象。但在创建时,这个对象不再是空的,它自动继承了 Object.prototype。所以在 hashMap 上我们能调用 hasOwnProperty 等方法。解决这个问题可以使用 Object.create(null),生成一个不继承 Object.prototype 的对象。
  • obj.hasOwnProperty() 有一个可靠性风险:名称冲突问题:当一个对象自己的属性与它的原型上的属性有名称冲突时,它就会打破预期,从而使程序崩溃。可以做一些防御性编程来防止这种情况,可以从 Obj.prototype 中“借用”真正的 hasOwnProperty 来代替: Obj.prototype.hasOwnProperty.call()。
  • 次优的人机工程学:Object 没有提供足够的人机工程学,不能作为 hash map 使用,许多常见的任务不能直观地执行。
  • Object 没有提供方便的 API 获取 size,属性的数量。解决手段:Object.keys()、
  • Object.getOwnPropertyNames()、Object.getOwnPropertySymbols、Reflect.ownKeys():一次获得字符串键和 symbol 键,不论是否可枚举。
  • iterate:循环遍历对象也有复杂性。虽然我们可以使用 for..in..,但它会读取到继承的可枚举属性。我们不能对一个对象使用 for...of..,因为默认情况下它不是一个可迭代对象,除非我们明确定义 Symbol.iterator 在它上面。Object.keys/values/entry 的迭代引入了额外的开销步骤。
  • 键插入顺序不定:大多浏览器中,整数键按升序排序,并优于字符串键,即使字符串键在整数键之前插入。
  • clear:只能使用 delete操作符一个个删除每个属性,这众所周知的慢。
  • 检查属性是否存在:不能依靠点/括号富豪检查一个属性的存在,因为值本身可能被设置为 undefined。需要使用 Object.prototype.hasOwnProperty 或 Object.hasOwn。
  • Map 支持任何数据类型的键;提供更好的人机工程学,可以使用 for..of 轻松迭代一个 Map,和 嵌套的解构;可以一次性删除所有项;轻松获取 Map 中项个数。
  • 性能差异:数据量并不真正多时(低于100000),Map 是 Object 两倍,超过时,性能差距缩小。
  • Object 整数键优势:V8内部优化了整数索引的属性,并将他们存储在一个单独的数组中,能线性、连续地访问,而 Map 没有。[0,1000]范围整数键,Object 插入速度比 Map 快 65%,迭代速度快 16%。

在 JavaScript 中,什么时候使用 Map 或胜过 Object - 掘金 (juejin.cn)

2月21日 打卡 day15

今日学习《前端实现docx、pdf格式文件在线预览》。

  • docx:使用 docx-preview 插件
  • pdf:使用 pdfjs-dist 插件。pdf 的预览需要将文件流解析写到 canvas 上实现预览效果。涉及 ArrayBuffer & Blob 数据格式转换问题。
  • 注意:pdf 文件渲染后如果不能调整大小会因为源文件的大小和文件内容出现模糊问题,所以缩放渲染是有必要的。

前端实现docx、pdf格式文件在线预览 - 掘金 (juejin.cn)

2月22日 打卡 day16

今日学习《前端工程化基石 -- AST(抽象语法树)以及AST的广泛应用🔥》

涉及手写编译器。

最佳实践:

  • 尽量避免遍历抽象语法树。遍历 AST 代价昂贵。Babel 的优化方法是合并多个 visitor,使能单次遍历做完所有事情的访问者对象。可以手动查找就不遍历。
  • 优化嵌套的访问者对象。每次调用 FunctionDeclaration() 时都会创建新的访问者对象,使 Babel 更大且每次都要做验证,最好将访问者向上提升。

应用场景:

  • 使用 Babel 修改函数名
  • 手写简易版 babel-plugin-transform-es2015-arrow-functions
  • 手写复杂版 babel-plugin-transform-es2015-arrow-functions
  • 手写 console.log 插件。强化 console,让其打印出当前文件名,以及打印位置的行与列等代码信息。
  • 手写监控系统中的日志上传插件。往每个函数作用域中添加一行日志执行函数。注意函数定义方式一共有 4 种;logger npm包。
  • 实现简易版 ESLint。Babel 中 AST 遍历也有生命周期,有两个钩子:在遍历开始之前或结束之后,可用于设置或清理/分析工作。
  • 实现代码压缩。
  • 实现按需加载插件。解决 loadash 不支持按需加载的痛点问题。
  • 实现 TS 的类型检验。

前端工程化基石 -- AST(抽象语法树)以及AST的广泛应用🔥 - 掘金 (juejin.cn)

2月23日 打卡 day17

今日学习《我被骂了,但我学会了如何构造高性能的树状结构🔥》。

  • 工作中见到的树状结构不利于进行增删改查,容易出错。
  • 树状结构扁平化后结构清晰,能轻易对数据进行处理。
  • 大数据量情况下,扁平化能极大提高性能,减少遍历的性能消耗。
  • Map 形式的拍扁后数据,通常有一个 item 专门表示根节点,它的 parentId 为空表示没有父节点,childrenIds 存储他们各自 item 的子节点 id。
  • 渲染环节,数据拍扁前后使用差别不大。
  • 添加环节,Map形式不需要递归循环遍历整棵数据树。只需在 Map 中新增一条数据项,之后给对应父节点的 childrenIds 中 push 该子项 id 即可。
  • 修改与删除环节,找到对应节点更改即可。

我被骂了,但我学会了如何构造高性能的树状结构🔥 - 掘金 (juejin.cn)

2月24日 打卡 day18

今日学习《islands 架构原理和实践》。

  • 理解 MPA 和 SPA 的区别和不同场景的取舍是理解 islands 架构的关键。
  • Island 架构实现能做到框架无关。创建组件的方法、组件转换为 HTML 字符串的 renderToString 方法、浏览器端的 hydrate 方法,都可以替换成其他框架的实现。
  • 注意:VitePress 利用 Vue 的编译时优化以及内部定制的 Hydrate 方案足以解决传统 SSG 的全量 hydration 问题,采用 Islands 架构意义不大。
  • VitePress 内部使用的是 Shell 架构,VitePress 在 hydrate 过程中把正文的静态部分排除,原理是在 Vue 的模版编译阶段,对静态虚拟 DOM 节点进行优化,输出 createStaticVNode 的格式。采用 SSG + SPA 模式,根据是否为首屏来分发不同的 JS
  • Shell 方案采用需要满足的条件:模版编译阶段,将静态节点进行特殊标记;运行时,支持 hydrate 跳过对静态节点的内容检查。故 Solid、Svelte 很难实现 Vue 的 Shell 架构(因为无法标记静态节点),可将 Shell 方案理解为 Vue 框架下的特殊优化。

Islands 架构原理和实践 - 掘金 (juejin.cn)

2月25日 打卡 day19

今日学习《【中级/高级前端】为什么我建议你一定要读一读 Tapable 源码?》。

  • Webpack 是一个典型的可插拔架构,逻辑清晰、灵活好扩展。而实现这一切的核心就是 Tapable,学习其原理能让日常 Webpack Plugin 开发中更加得心应手。
  • Tapable 内部以巧妙方式实现了 发布订阅模式,其中包含:懒编译/动态编译,类与继承抽象类的面向对象思想以及 this 指向的升华等等知识点。
  • Tapable 是一个类似于 EventEmitter 的库,但它更专注于自定义时间的触发和处理。比如 前端框架的生命周期函数,在固定时间节点执行对应生命周期,tapable 做的就与此类似。
  • Tapable 使用步骤:实例化钩子函数、注册事件、触发事件。
  • Tapable 通过 tap 函数注册监听函数,然后通过 call 函数按顺序执行之前注册的函数。

核心思想:

  • tap 函数本质是一个收集器,调用 tap 函数需要将传入的信息进行收集,并转换成一个数组,数组中存放注册函数的类型type、回调函数(fn)等信息。
  • call 函数本质是按指定的类型去执行 this.taps 中的注册函数 fn。
  • 生成运行函数,使用 new Function()。
  • 懒编译/动态编译的体现就是只有在执行 call 方法时才会去动态生成执行函数,如果不执行则不处理。
  • 动态编译使库更灵活,如果想实现其他 Hook,只需要定义好各自的 compiler 函数即可。
  • 含 Webpack 作者采用 new Function 方案原因的文章.

【中级/高级前端】为什么我建议你一定要读一读 Tapable 源码? - 掘金 (juejin.cn)

2月26日 打卡 day20

今日学习《JavaScript 必须学会的11 个工具方法(避免重复造轮子)》。

  1. 计算距离下次生日的天数:建议 day.js 实现。
  2. 回到顶部
  3. 复制文本
  4. 防抖/节流
  5. 过滤特殊字符
  6. 常用正则判断:检验 2-9 位文字;检验手机号;检验 6-18 位大小写字母数字下划线组成的密码。
  7. 初始化数组
  8. 将 RGB 转换为十六进制
  9. 检测是否是一个函数
  10. 检测是否是一个安全数组
  11. 检测对象是否为一个安全对象

JavaScript 必须学会的11 个工具方法(避免重复造轮子) - 掘金 (juejin.cn)

2月27日 打卡 day21

今日学习《现代前端工程为什么越来越离不开 Monorepo?》。

  • 实际场景中的 Monorepo 落地需要一套完整的工程体系来进行支撑,需要考虑项目间依赖分析、依赖安装、构建流程、测试流程、CI 及发布流程等诸多工程环节,还要考虑项目规模到一定程度后的性能问题,如项目 构建/测试 时间过长需要进行 增量构建/测试、按需执行 CI 等。
  • 集成的 monorepo 方案有:nx、rushstack,提供从初始化、开发、构建、测试到部署的全流程能力。
  • 多个项目中,一些逻辑会被多次用到,但直接 copy 的代码出现bug或需要调整时,就需要一个个项目进行修改,维护成本高,解决的方法是发布成 npm 包,但 npm 包发布本身就有一定复杂度,存在依赖更新不及时等痛点问题。
  • Monorepo 具有的优点有:工作流一致性,依赖的代码改动会被立马感知;项目基建成本降低,所有项目复用一套标准的工具和规范,无需切换开发环境;团队协作更易,git commit 的历史记录支持以功能为单位进行提交。

现代前端工程为什么越来越离不开 Monorepo? - 掘金 (juejin.cn)