权衡的艺术
1.3 虚拟 dom
虚拟 dom 的耗时:创建 javascript 的计算量 + 创建真实 dom 的计算量 (数据变化量有关)
innerHtml 的耗时:拼接字符串的计算量 + innerHtml 的 DOM 计算量 (模板大小有关)
原生 JavaScript > 虚拟 dom > innerHTML
1.4 运行和编译时
1 框架设计的核心要素
2.1 缩减框架代码的体积
例:vue3 源码的 warn 函数
1 | if(_DEV_ && ires) { |
2.2Tree-Shaking
Tree-Shaking:消除永远不会使用到的代码 如上面说的DEV
能使用 Tree-Shaking 的必须是满足 ESM
Tree-Shaking 会产生副作用
1 | **加上标记来告诉系统可以安心清除副作用 |
2.3 框架输出的产物
无论是 rollup.js 还是 webpack 在寻找资源文件的时候 如果 package.json 中存在 module 字段 都会优先使用 module 字段指向的资源代替 main 字段指向的资源
1 | { |
用处:当我们使用构建提供工具打包的 ESM 格式的资源时 不能直接把DEV转换成 true 或 false 需要使用(process.env.NODE* !== ‘production’)替换—DEV*常量
1 | if((process.env.NODE) !== 'production') { |
需求场景:当进行服务端渲染时 vue.js 的代码是在 Node.js 的环境中运行的 但是 Node.js 时 CommonJS 形式 所以可以更改 roullup.js 中的 format 配置
1 | format: 'cjs'//指定模块形式 |
2.4 特定开关
2.5 错误处理
VUE.JS3 的设计思路
3.2 渲染器
1 | function renderer(vnode, container) { |
响应系统的作用和实现
4.1 响应式数据与副作用
副作用函数:会产生副作用的函数 即该函数的执行会直接或间接地影响到其他函数或变量的执行
4.2 响应式数据的基本实现
通过拦截一个对象的读取和设置操作
```
//当读取obj.text的时候 就将副作用函数储存到一个桶里面
//当设置obj.text的时候 就将副作用从桶里面取出执行便可
/ 存储副作用函数的桶
const bucket = new Set()// 原始数据
const data = { text: ‘hello world’ }
// 对原始数据的代理
const obj = new Proxy(data, {
// 拦截读取操作
get(target, key) {// 将副作用函数 effect 添加到存储副作用函数的桶中 bucket.add(effect) // 返回属性值 return target[key]
},
// 拦截设置操作
set(target, key, newVal) {// 设置属性值 target[key] = newVal // 把副作用函数从桶里取出并执行 bucket.forEach(fn => fn())
}
})function effect() {
document.body.innerText = obj.text
}
effect()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
**缺陷**:如果副作用改名或者是匿名函数 则该响应系统失效
- 设计一个完善的响应系统
- 提供一个用来注册副作用函数的机制来解决上面的问题
- ```
//用一个变量春促当前激活的effect函数
let activeEffect;
function effect(fn) {
//当调用effect注册副作用函数是 将副作用函数赋值给activeEffect
activeEffect = fn;
fn(0);//执行副作用函数
};
effect(() => {
console.log('effect do');
documrnt.body.innerText = obj.text;//进行读取操作
})
const obj = new Proxy(data, {
get(target, key) {
//将当前被调用的副作用函数加入到桶中
if(activeEffect) {
bucket.add(activeEffect)//新增
}
return target[key];
},
set(target, key, newVal) {
target[key] = newVal;
bucket.forEach( fn => fn());
return true;
}
})完成了可以添加匿名或任意名字的副作用函数
缺陷:无法监听指定属性,即如果进行类似于新增不存在的属性 该副作用函数依然会被执行(正常不应该被执行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50测试:新增不存在的obj.text2属性
effect(() => {
obj.body.innerText = obj.text
console.log('effect done')
});
setTimeout(() => {
obj.text2 = 'none exists';//新增一个不存在的属性
})
//运行结果:effect done 执行两次
//分析:每次修改对象obj都会将effect存入桶中 导致读取时执行不该执行的副作用
//没有在副作用函数与被操作的目标之间建立明确的联系
**解决方法:在副作用函数与被操作的字段之间建立联系 可以使用weakMap
//储存副作用的桶
const bucket = new WeakMap();
//修改拦截器
const obj = new Proxy(data, {
//拦截读取行为
get(target, key) {
//没有activeEffect 直接return
if(!activeEffect) return;
//根据traget从桶中取得desMap, :key --> effects
let desMap = bucket.get(target);
//如果不存在desMap 那么就创建一个WeakMap
if(!desMap) {
bucket.set(target, (desMap = new Map()))
}
//根据key自从desMap中读取deps deps时一个set类型
//里面储存着所有与当前key有相关的副作用函数:effects
let deps = desMap.get(key);
if(!deps) {
desMap.set(key, (deps = new Set()));
}
deps.add(activeEffect);//将激活的副作用函数存储到桶里
return target[key];//返回属性值
};
//拦截设置操作
set(target, key, newVal) {
//设置属性值
target[key] = newVal;
//根据target从decket中取出desMap
let desMap = ducket.get(target);
if(!desMap) return;
//根据key取得所有的副作用函数并依次执行
let deps = desMap.get(key);
deps && deps.forEach( fn => fn());//判空再执行
}
})为什么使用 WeakMap 不使用 Map
- weakMap 是弱引用 不会影响垃圾回收
- 只有当 key 所引用的对象存在时(没有被回收时)才有价值的信息都优先使用 weakMap 来存储
**存在问题:代码耦合度高 **
解决方法: 抽离封装
function track(target, key) { let depsMap = bucket.get(target) if (!depsMap) { bucket.set(target, (depsMap = new Map())) } let deps = depsMap.get(key) if (!deps) { depsMap.set(key, (deps = new Set())) } deps.add(activeEffect) } function trigger(target, key) { const depsMap = bucket.get(target) if (!depsMap) return const effects = depsMap.get(key) effects && effects.forEach(fn => fn()) } // 对原始数据的代理 const obj = new Proxy(data, { // 拦截读取操作 get(target, key) { // 将副作用函数 activeEffect 添加到存储副作用函数的桶中 track(target, key) // 返回属性值 return target[key] }, // 拦截设置操作 set(target, key, newVal) { // 设置属性值 target[key] = newVal // 把副作用函数从桶里取出并执行 trigger(target, key) } })
effect(function effectFn() { document.body.innerText = obj.ok? obj.text : 'not' }) //此时依赖函数被obj.ok和obj.text同时依赖 当obj.ok为false时 无论obj.text怎么变化 都是'not' 所以不应该触发副作用函数 //解决方法:每次副作用函数执行时 都把它从所有与之关联的依赖集合中删除 //当副作用执行完毕后 会建立联系 但再新的联系中不会包含遗留的副作用函数1
2
3
4
5
## 4.4 切换分支与 cleanup
分支切换可能会产生遗留的副作用函数//用一个全局变量储存被注册的副作用函数 let activeEffect; function effect(fn) { //当effectFn被执行时 将其设置为当前的激活的副作用函数 const effectFn = () => { activeEffect = effectFn; fn(); } //effectEffect.deps 用来储存所有与该副作用函数想关联的依赖集合 effectEn.dep = []; //执行副作用函数 effectFn(); }1
2
3
**副作用依赖函数集合**- 有了这个联系后 再每次执行副作用函数时 根据 effectFn.deps 获取所有的相关联的依赖集合 从而将副作用函数从依赖集合中清除 - ``` let activeEffect; function effect(fn) { const effectFn = () => { //调用cleanUp函数完成清除依赖 cleanup(effectFn); activeEffect = effectFn; fn(); } } function cleanup(effectFn) { //遍历effectFn.deps数组 for (let i = 0; i < effectFn.deps.length; i++) { //deps时依赖集合 const deps = effectFn.deps[i] //将effectFn从依赖集合中删除 deps.delete(effectFn) } //重置effectFn.deps数组 effectFn.deps.length = 0 }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- **Track 函数**
- ```
function track(target, key) {
if(!activeEffect) return;
let depsMap = bucket.get(key);
if(!depsMap) {
bucket.set(target, (desMap => new Map()));
}
let deps = depsMap.get(key);
if(!deps) {
depsMap.set(key, ( deps => new Set()))
};
//把当前激活的副作用函数添加到依赖集合deps中
deps.add(activeEffect);
//deps就是一个与当前副作用函数存在联系的集合
//将其添加到activeEffects.deps中
activeEffect.deps.push(deps);
}const obj = {foo: 1}; Reflect.get(obj, foo, {foo: 2});//输出21
2
3
4
5
6
7
8
9
10
11
12
13
-
# 非原始值的响应式方案
## 5.1Proxy 以及 Reflect
**proxy 只能代理对象 不能代理其他类型的数据**
### 5.1.2 Reflect
Reflect 可以接受第三个参数 即 receiver 相当于函数调用过程中的 this