ES6 再学习 2021-01-08
2023-06-29 18:20:06 刚读了一遍,并复写在了 03_js/076_结束.html
.
Vue 再学习 2021-01-11
2023-06-29 18:19:22 刚把它简要读了一遍,感谢我之前做的好笔记啊。
.
微信读书
操作笔记 09_vue/书籍:Vue.js设计与实现
2023-06-29 16:46:49(开始)
2023-07-28 10:49:31(结束) 一个月了,拖拖拉拉,真的很想是放弃了,慢慢还是坚持住了。
感悟
1 | 2023-07-03 09:48:45 |
前言
1 | # Vue.js 3.0 |
第1章 权衡的艺术
“框架设计里到处都体现了权衡的艺术。”
1.1 命令式和声明式
1 | 命令式框架 => 关注过程 |
1.2 性能与可维护性的权衡
1 | [命令式, 声明式] 各有好坏,框架需权衡 [性能, 可维护性]。 |
1.3 虚拟 DOM 的性能到底如何
1 | 所谓的虚拟 DOM,就是为了最小化找出差异这一步的性能消耗而出现的。 |
1.4 运行时和编译时
1 | # 框架的三种选择 |
1.5 总结
1 | # 命令式、声明式 |
第2章 框架设计的核心要素
2.1 提升用户的开发体验
1 | # 开发体验 |
2.2 控制框架代码的体积
1 | # 代码体积 |
2.3 框架要做到良好的 Tree-Shaking
1 | # Tree-Shaking |
2.4 框架应该输出怎样的构建产物
1 | # iife |
2.6 错误处理
1 | # 统一定义 utils.js |
2.7 良好的 TypeScript 类型支持
1 | # TS 优点 |
2.8 总结
1 | 开发体验是衡量一个框架的重要指标之一。 |
第3章 Vue.js 3 的设计思路
3.1 声明式地描述 UI
1 | # Vue.js 3 => 声明式 UI 框架 |
3.2 初识渲染器
1 | 1.虚拟DOM => 2.渲染器 => 3.真实DOM |
3.3 组件的本质
1 | 组件就是一组 DOM 元素的封装,这组 DOM 元素就是组件要渲染的内容。 |
3.4 模板的工作原理
1 | 模板 => 编译器 => 渲染函数 |
3.5 Vue.js 是各个模块组成的有机整体
1 | # 模板 |
3.6 总结
1 | Vue.js 是一个声明式的框架。声明式的好处在于,它直接描述结果,用户不需要关注过程。 |
第4章 响应系统的作用与实现
4.1 响应式数据与副作用函数
1 | # 副作用 |
4.2 响应式数据的基本实现
1 | 在 ES2015之前,只能通过 Object.defineProperty 函数实现,这也是 Vue.js 2 所采用的方式。 |
4.3 设计一个完善的响应系统
1 | 上面,我们硬编码了副作用函数的名字(effect),导致一旦副作用函数的名字不叫 effect,那么这段代码就不能正确地工作了。 |
1 | # 三个角色 |
1 | // 存储副作用函数的桶(WeakMap 代替 Set ) |
1 | # 在 get 拦截函数内调用 track 函数追踪变化 |
4.4 分支切换与 cleanup
1 | # 触发3次 |
4.5 嵌套的 effect 与 effect 栈
1 | # effect 可嵌套 |
1 | # 009_4.5_响应式 v09 嵌套 错误演示.html |
1 | # 010_4.5_响应式 v10 嵌套 栈.html |
4.6 避免无限递归循环
1 | # 011_4.6_响应式 v11 无限递归 错误演示.html |
1 | # 012_4.6_响应式 v12 无限递归 守卫条件.html |
4.7 调度执行
1 | # 013_4.7_响应式 v13 调度 顺序问题 正常.html |
1 | # 014_4.7_响应式 v14 调度 顺序问题 改变.html |
1 | # 015_4.7_响应式 v15 调度 123 有中间状态.html |
1 | # 016_4.7_响应式 v16 调度 13 无中间状态.html |
4.8 计算属性 computed 与 lazy
1 | # 017_4.8_响应式 v17 lazy 无返回值.html |
1 | # 018_4.8_响应式 v18 lazy 有返回值.html |
1 | # 019_4.8_响应式 v19 computed 重新计算问题.html |
1 | # 020_4.8_响应式 v20 computed dirty解决重新计算.html |
1 | # 021_4.8_响应式 v21 computed 更新不变问题.html |
1 | # 022_4.8_响应式 v22 computed scheduler解决更新不变.html |
1 | # 023_4.8_响应式 v23 computed 内层变化不影响外层.html |
1 | # 024_4.8_响应式 v24 computed 内层变化影响外层.html |
4.9 watch 的实现原理
所谓 watch,其本质就是观测一个响应式数据,当数据发生变化时通知并执行相应的回调函数。
实际上,watch 的实现本质上就是利用了 effect 以及 options.scheduler 选项。
1 | # 025_4.9_响应式 v25 watch 固定属性.html |
1 | # 026_4.9_响应式 v26 watch 任意属性.html |
1 | # 027_4.9_响应式 v27 watch getter函数.html |
1 | # 028_4.9_响应式 v28 watch 新旧值.html |
4.10 立即执行的 watch 与回调执行时机
1 | # 029_4.10_响应式 v29 watch immediate立即执行.html |
1 | # 030_4.10_响应式 v30 watch flush执行时机.html |
4.12 总结
1 | # 响应式数据的拦截机制 |
第5章 非原始值的响应式方案
1 | 什么。实际上,实现响应式数据要比想象中难很多,并不是像上一章讲述的那样,单纯地拦截get/set 操作即可。 |
5.1 理解 Proxy 和 Reflect
Proxy
1 | # 基本语义 |
Reflect
1 | # Reflect |
访问器属性的 this 问题
1 | # 031_5.1_响应式 v31 Proxy 属性访问器this.html |
1 | # 032_5.1_响应式 v32 Proxy 属性访问器this receiver.html |
5.2 JavaScript 对象及 Proxy 的工作原理
1 | 如何区分一个对象是普通对象还是函数呢? |
5.3 如何代理 Object
1 | 一个普通对象的所有可能的读取操作。 |
1 | # 035_5.3_响应式 v35 ownKeys for...in 新旧属性都触发.html |
1 | # 036_5.3_响应式 v36 ownKeys for...in 仅新增属性触发.html |
1 | # 037_5.3_响应式 v37 ownKeys for...in 删除属性触发.html |
5.4 合理地触发响应
1 | # 038_5.4_响应式 v38 值未变也触发.html |
1 | # 039_5.4_响应式 v39 值变了才触发.html |
1 | # 040_5.4_响应式 v40 NaN问题.html |
1 | # 041_5.4_响应式 v41 解决NaN问题.html |
1 | # 042_5.4_响应式 v42 代理原型问题.html |
1 | # 043_5.4_响应式 v43 解决代理原型问题.html |
5.5 浅响应与深响应
1 | # 044_5.5_响应式 v44 浅响应.html |
1 | # 045_5.5_响应式 v45 深响应.html |
1 | # 046_5.5_响应式 v46 浅深响应.html |
5.6 只读和浅只读
1 | # 047_5.5_响应式 v47 readonly只读.html |
5.7 代理数组
1 | 在 JavaScript 中,数组只是一个特殊的对象而已。 |
5.7.1 数组的索引与 length
1 | # 048_5.7.1_响应式 v48 数组 index设置可触发.html |
1 | # 049_5.7.1_响应式 v49 数组 length不会重新触发.html |
1 | # 050_5.7.1_响应式 v50 数组 解决length不会重新触发.html |
1 | # 051_5.7.1_响应式 v51 数组 length属性设置问题.html |
1 | # 052_5.7.1_响应式 v52 数组 解决length属性设置问题.html |
5.7.2 遍历数组
1 | # 053_5.7.2_响应式 v53 数组 for...in.html |
1 | # 054_5.7.2_响应式 v54 数组 for...in length属性问题.html |
1 | # 055_5.7.2_响应式 v55 数组 for...of.html |
1 | # 056_5.7.2_响应式 v56 数组 for...of length属性问题.html |
5.7.3 数组的查找方法
1 | # 057_5.7.3_响应式 v57 数组 代理对象includes.html |
1 | # 058_5.7.3_响应式 v58 数组 原始对象includes.html |
1 | # 059_5.7.3_响应式 v59 数组 重写includes.html |
1 | # 060_5.7.3_响应式 v60 数组 重写includes indexOf lastIndexOf.html |
5.7.4 隐式修改数组长度的原型方法
1 | # 061_5.7.4_响应式 v61 数组 push oom.html |
1 | # 062_5.7.4_响应式 v62 数组 重写push.html |
1 | # 063_5.7.4_响应式 v63 数组 重写push pop shift unshift splice.html |
5.8 代理 Set 和 Map
5.8.1 如何代理 Set 和 Map
1 | # 064_5.8.1_响应式 v64 set 代理问题.html |
1 | # 065_5.8.1_响应式 v65 set 封装new Proxy.html |
5.8.2 建立响应联系
1 | # 066_5.8.2_响应式 v66 set add未触发响应.html |
1 | # 067_5.8.2_响应式 v67 set add delete.html |
5.8.3 避免污染原始数据
1 | # 068_5.8.3_响应式 v68 map set触发响应.html |
1 | # 069_5.8.3_响应式 v69 map 污染问题.html |
1 | # 070_5.8.3_响应式 v70 map 解决污染问题.html |
5.8.4 处理 forEach
1 | # 071_5.8.4_响应式 v71 map foreach.html |
1 | # 072_5.8.4_响应式 v72 map foreach 改造.html |
1 | # 073_5.8.4_响应式 v73 map set修改问题.html |
5.8.5 迭代器方法
1 | # 074_5.8.5_响应式 v74 map 正常迭代.html |
1 | # 075_5.8.5_响应式 v75 map 代理迭代.html |
1 | # 076_5.8.5_响应式 v76 map 解决代理迭代.html |
1 | # 077_5.8.5_响应式 v77 map k v响应式数据.html |
1 | # 078_5.8.5_响应式 v78 map entries问题.html |
1 | # 079_05.8.5_响应式 v79 map 解决entries问题.html |
5.8.6 values 与 keys 方法
1 | # 080_5.8.5_响应式 v80 map values.html |
1 | # 081_5.8.5_响应式 v81 map keys修改问题.html |
1 | # 082_5.8.5_响应式 v82 map 解决keys修改问题.html |
5.9 总结
1 | ## 对象和 Proxy |
第6章 原始值的响应式方案
1 | 第5章 非原始值的响应式方案 |
6.1 引入 ref 的概念
1 | # 083_6.1_响应式 v83 基本值不能代理.html |
1 | # 084_6.1_响应式 v84 ref包裹基本值.html |
1 | # 085_6.1_响应式 v85 ref标识.html |
6.2 响应丢失问题
1 | # 数据暴露 |
1 | # 086_6.2_响应式 v86 ...展开符 响应丢失问题.html |
1 | # 087_6.2_响应式 v87 重写属性 toRef toRefs.html |
1 | # 088_6.2_响应式 v88 完善toRef set __v_isRef.html |
6.3 自动脱 ref
1 | # 089_6.3_响应式 v89 属性值访问对比.html |
1 | # 090_6.3_响应式 v90 字动脱 ref get.html |
6.4 总结
1 | # ref 的概念 |
第7章 渲染器的设计
1 | 渲染器的代码量非常庞大,需要合理的架构设计来保证可维护性,不过它的实现思路并不复杂。 |
7.1 渲染器与响应系统的结合
1 | # 渲染 |
1 | 它暴露的全局 API 名叫 VueReactivity |
7.2 渲染器的基本概念
1 | # 渲染器的作用:虚拟 DOM 渲染为 真实 DOM |
1 | # 简单的 createRenderer函数 |
1 | # 渲染一次 |
1 | # 定义函数 createRenderer |
7.3 自定义渲染器
1 | # ============= 大致代码结构 |
7.4 总结
1 | # 渲染器与响应系统 |
第8章 挂载与更新
8.1 挂载子节点和元素的属性
children 数组:挂载多个节点
1 | # children => 对象 改为 数组 |
节点属性
1 | # 新增属性 vnode.props |
8.2 HTML Attributes 与 DOM Properties
1 | # HTML Attributes 与 DOM Properties |
8.3 正确地设置元素属性
浏览器解析、Vue.js解析
1 | # 浏览器 |
disabled 属性存在,就会禁用
1 |
|
1 | # 布尔值 矫正问题 |
1 | # 问题 => 只读属性 |
1 | # 抽取 patchProps |
8.4 class 的处理
1 | <p class="foo bar"></p> |
8.5 卸载操作
1 | // 初次挂载 |
1 | # vnode.el 引用 |
1 | # 抽取方法 unmount ===> 为了后续在卸载时,扩展更多的功能 |
8.6 区分 vnode 的类型
type不同:卸载 old_node,挂载 new_node
1 | # 测试代码 |
不同类型的 vnode 处理
1 | 一个 vnode 可以用来描述普通标签,也可以用来描述组件,还可以用来描述 Fragment 等。 |
8.7 事件的处理
v1 添加事件监听
1 | # 测试对象 |
v2 invoker 包装单个事件处理
1 | # 不用每次都移除监听,添加监听 |
v3 invoker 包装多个事件处理
1 | # 测试对象 |
v4 invoker 单个事件, 多个处理
1 | # addEventListener 相同事件,支持多个处理函数 |
8.8 事件冒泡与更新时机问题
1 | # 测试数据 |
8.9 更新子节点
回顾一下 元素的子节点是如何被挂载的
1 | function mountElement(vnode, container) { |
子节点:三种类型、九种更新
1 | # 子节点:三种类型 |
处理子节点
1 | function patchElement(n1, n2) { |
8.10 文本节点和注释节点
1 | # Symbol 标识 => Text Comment |
跨平台处理
1 | patch 函数依赖浏览器平台特有的API,即 createTextNode 和 el.nodeValue。 |
8.11 Fragment
什么是 Fragment?
1 | Fragment(片断)是 Vue.js 3 中新增 vnode 类型。 |
Fragment 挂载、卸载
1 | # 挂载 Fragment |
8.12 总结
1 | # 节点挂载、属性和卸载 |
第9章 简单 Diff 算法
1 | 当新旧 vnode 的子节点都是一组节点时,为了性能开销更低,需比较两组子节点的算法就叫 Diff 算法。 |
9.1 减少 DOM 操作的性能开销
相同节点
1 | 按照之前的算法,需要 6次 DOM操作:3次DOM卸载,3次DOM挂载。 |
不相同节点:挂载、卸载
1 | function patchChildren(n1, n2, container) { |
9.2 DOM 复用与 key 的作用
1 | 关于这个也需要 6次DOM操作,3次循环(卸载旧的,挂载新的)。 |
1 | # 复用 DOM(key相同) |
9.3 找到需要移动的元素
1 | function patchChildren(n1, n2, container) { |
9.4 如何移动元素
1 | # 指向同一个真实 DOM |
9.5 添加新元素
1 | function patchChildren(n1, n2, container) { |
patch 第四个参数:锚点元素
1 | // patch 函数需要接收第四个参数,即锚点元素 |
9.6 移除不存在的元素
1 | function patchChildren(n1, n2, container) { |
9.7 总结
1 | # Diff算法与DOM操作优化 |
第10章 双端 Diff 算法
10.1 双端比较的原理
1 | # 分析 => 对照着图片,想想就知道了。 |
10.2 双端比较的优势
1 | 简单 Diff 算法 ===> 两次 DOM 移动 |
10.3 非理想状况的处理方式
1 | # 分析 => 对照着图片,想想就知道了。 |
10.4 添加新元素
1 | # 分析 |
10.5 移除不存在的元素
1 | # 分析 |
10.6 总结
1 | # 双端 Diff 算法 |
第11章 快速 Diff 算法
1 | 速度很快,最早应用于 ivi 和 inferno 这两个框架,Vue.js 3 借鉴并扩展了它。 |
11.1 相同的前置元素和后置元素
纯文本 Diff 算法
1 | 借鉴了纯文本 Diff 算法的思路。 |
1 | function patchKeyedChildren(n1, n2, container) { |
11.2 判断是否需要进行 DOM 移动操作
1 | function patchKeyedChildren(n1, n2, container) { |
11.3 如何移动元素
递增子序列
1 | # 分析 |
移动元素的操作代码位置
1 | # 移动元素的操作代码位置 |
快速 diff 算法 => 完整代码
1 | function patchKeyedChildren(n1, n2, container) { |
11.4 总结
1 | 快速 Diff 算法在实测中性能最优。 |
第12章 组件的实现原理
1 | 有了组件,我们就可以将一个大的页面拆分为多个部分, |
12.1 渲染组件
回顾 vnode.type
1 | # 不同类型的 vnode.type |
组件的 vnode.type表示
1 | # 组件的处理 vnode.type === 'object' |
12.2 组件状态与自更新
1 | # 组件渲染 data 数据 |
12.3 组件实例与组件的生命周期
1 | # 分析 |
12.4 props 与组件的被动更新
1 | # 模版 |
1 | # 父组件模版 |
渲染上下文对象 renderContext
1 | # 代码实现 |
12.5 setup 函数的作用与实现
setup 简介
1 | # Vue 3 新增 setup |
setup 最小实现
1 | function mountComponent(vnode, container, anchor) { |
12.6 组件事件与 emit 的实现
1 | # 组件的事件处理 |
emit 代码实现
1 | # setupContext.emit |
12.7 插槽的工作原理与实现
插槽 slot
1 | 顾名思义,组件的插槽指组件会预留一个槽位,该槽位具体要渲染的内容由用户插入, |
setupContext.slots 代码实现
1 | # 分析 |
12.8 注册生命周期
1 | Vue.js 3 生命周期钩子函数,例如 onMounted、onUpdated 等。 |
12.9 总结
1 | # 组件和组件实例 |
第13章 异步组件与函数式组件
1 | 异步组件:以异步的方式加载并渲染一个组件。 |
13.1 异步组件要解决的问题
异步示例
1 | # 同步渲染 |
异步加载组件
1 | # 考虑点 |
13.2 异步组件的实现原理
13.2.1 封装 defineAsyncComponent 函数
1 | # defineAsyncComponent 使用 |
13.2.2 超时与 Error 组件
1 | # 定义参数 timeout errorComponent |
13.2.3 延迟与 Loading 组件
1 | # 定义参数 delay loadingComponent |
Loading 组件的卸载
1 | // 当异步组件加载成功后,会卸载 Loading 组件并渲染异步加载的组件。 |
13.2.4 重试机制
模拟重试
1 | 封装一个 fetch 函数,用来模拟接口请求: |
defineAsyncComponent 处理重试
1 | function defineAsyncComponent(options) { |
13.3 函数式组件
1 | 一个函数式组件本质上就是一个普通函数,该函数的返回值是虚拟 DOM。 |
函数式组件的支持
1 | # patch |
13.4 总结
1 | # 异步组件 |
第14章 内建组件和模块
14.1 KeepAlive 组件的实现原理
14.1.1 组件的激活与失活
KeepAlive 组件是什么?
1 | # KeepAlive 组件作用 |
KeepAlive 组件的实现原理
1 | # KeepAlive 组件的实现原理 |
KeepAlive 具体实现
1 | const KeepAlive = { |
参数解释
1 | KeepAlive 组件本身并不会渲染额外的内容,它的渲染函数最终只返回需要被 KeepAlive 的组件,我们把这个需要被 KeepAlive 的组件称为“内部组件”。 |