Vue3实现原理
# 核心方法
在3.x
中,使用reactive
创建数据池。其和2.x
中的data
起一定相同作用。
其本质是将传入的普通对象转为该普通对象的响应式代理(响应式数据)。
等同于2.x
的Vue.obserable()
let proxy = Vue.reactive({name: 'Jonham'});
// 含义:副作用 数据变化了 更新视图、请求数据
Vue.effect(() => {
proxy.name = 'CD_wOw'; // 此处查询数据 会调用get方法
})
2
3
4
5
在这个代码里面,必然是先渲染。随后数据改变之后再渲染一次。
核心方法通过effect()
方法实现
默认effect()
会先执行一次
3.x
响应式实现原理 核心代码
// 判断是否是对象
function isObject (val) {
return typeof val === 'object' && val!== null;
}
// 初始化方法
function reactive (target) {
// 创建响应式对象
return createReactiveObject(target)
}
// 创建响应式对象
function createReactiveObject (target) {
// 不是对象则无需其他操作 直接返回原数据
if (!isObject(target)) {
return target;
}
// 查看是否被代理
let proxy = toProxy.get(target); // 如果已经代理过了,就直接返回proxy
if (proxy) {
return proxy;
}
if (toRaw.has(proxy)) { // 防止代理过的对象再次被代理(嵌套)
return proxy;
}
// 创建一个观察者
// 代理target对象
let baseHandler = { // 源码里面是五个,此处只写三个
get (target, key, receiver) { // 查询
// proxy + reflect 反射
let result = Reflect.get(key, target, receiver);
// 收集依赖
return isObject(result) ? reactive(result) : result;
},
set (target, key, value, receiver) { // 设置
let result = Reflect.set(target, key, value, receiver);
return result;
},
deleteProperty () { // 删除
let result = Reflect.deleteProperty(target, key);
return result;
},
};
let observed = new Proxy(target, baseHandler); // es6
toProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
}
// proxy 代理对象 该对象不需要重写 name、key、value属性
let proxy = reactive({name: 'Jonham'})
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
当对proxy代理对象进行增、删、改、查操作的时候,分别触发baseHandler
内部相应的方法
Proxy
缺点 兼容性差 止于IE11
代理对象,原对象的方法,增删改查。则需要对其原来的方法进行拦截(baseHandler
)
内部方法接收多个参数,target
原对象。key
是具体查的某个字段。receiver
为代理后的对象(proxy
)
receiver
就是新的proxy
get
方法:如果设置没成功如果对象不可以被更改 writeable
set
方法:返回Boolean
类型
Porxy
另做讲解
Reflect是
ES6
为了操作对象而新增的API
将Object对象的一些明显属于语言内部的方法(比如
Object.defineProperty
),放到Reflect对象上,那么以后我们就可以从Reflect
对象上可以拿到语言内部的方法。
在使用对象的
Object.defineProperty(obj, name, {}
)时,如果出现异常的话,会抛出一个错误,需要使用try catch
去捕获,但是使用Reflect.defineProperty(obj, name, desc
) 则会返回false
3.x
也用了递归思路。但其巧妙之处在于并不是如2.x
一上来就直接递归。而是在对数据进行操作的时候(get
方法内部判断触发)进行多层代理
get (target, key, receiver) { // 查询
let result = Reflect.get(key, target, receiver);
return isObject(result) ? reactive(result) : result;
},
2
3
4
警告
当然,Porxy
代理对象仅需要一次即可。
但不可否认,在书写代码同时,会有一些小错误。造成同一个对象,多次调用代理方法。又或者将代理对象再次进行reactive(ProxyObject)
代理
此时需要记录一下,避免此种情况发生。
# hash表
hash
表 映射表 注册映射表
let tpProxy = new WeakMap(); // 原对象:被代理过的对象
let toRaw = new WeakMap(); // 被代理过的对象:原对象
let observed = new Proxy(target, baseHandler); // es6
toProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
2
3
4
5
6
7
8
new WeakMap()
弱引用映射表 es6
放置的是源对象
WeakMap
不使用map的原因是防止被回收掉
这样,进行代理时可判断是否被代理
// 查看是否被代理
let proxy = toProxy.get(target); // 如果已经代理过了,就直接返回proxy
if (proxy) {
return proxy;
}
if (toRaw.has(proxy)) { // 防止代理过的对象再次被代理(嵌套)
return proxy;
}
2
3
4
5
6
7
8
代理对象更改为数组
let arr = [1, 2, 3];
let proxy = reactive(arr);
proxy.push(4);
2
3
会执行两个步骤
1、 会在arr
中索引为3
的位置上设置为4
;(新增)
2、 会把arr
的length
更改为4
;(修改)
弊端: push
数组时会触发两次视图更新
所以,在set
方法内部需要识别是改属性还是新增属性
在set
内,判断target
是否有key
,有的话就是更改,没有的话就是新增
function hasOwn(target, key) {
}
// set 内执行
let oldValue = target[key];
let hasKey = hasOwn(target, key); // 判断这个属性 以前有没有
if (!hasKey) { // 新增属性
trigger(target, 'add', key);
} else if (oldValue !== value) { // 修改属性
trigger(target, 'set', key);
} // 屏蔽无意义的修改
2
3
4
5
6
7
8
9
10
11
12
# 依赖收集
发布订阅 栈 先进后出 响应式 副作用
let activeEffectStack = []; // 栈型结果
function effect(fn) {
// 需要把fn这个函数编程响应式的函数
let effect = createReactiveEffect(fn);
effect(); // 默认先执行一次
}
function createReactiveEffect (fn) {
let effect = function () { // 这个就是创建的响应式effect
return run(); // 运行
}
return effect;
}
let obj = reactive({name: 'Jonham'});
effect(( => { // 默认先执行一次,依赖的数据变化了会在执行一次
console.log(obj.name);
})
obj.name = 'CD_wOw'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
effect
作用和watch
相当
此处的obj.name
数据变化,则effect
方法会被执行
核心
function createReactiveEffect (fn) {
let effect = function () { // 这个就是创建的响应式effect
return run(effect, fn);
}
return effect;
}
2
3
4
5
6
run()
:
1、让fn()
执行。
2、同时把effect
存放到栈(activeEffectStack
)中
function run(effect, fn) { // 运行run 并且将effect存起来
try {
activeEffectStack.push(effect);
fn(); // 同2.x,利用了js是单线程
} finally {// 即便fn()报错,也能执行 清理方法
activeEffectStack.pop(effect);
}
}
2
3
4
5
6
7
8
警告
收集依赖,本质上是修改某一属性,则对其绑定一个effect
方法,组成键值对,并且存放在activeEffectStack
中,组成数组。
// name 对应key; effect对应相应的函数
activeEffectStack = [
{
target: {
key: [fn, fn]
},
}
]
2
3
4
5
6
7
8
也就是俗称 依赖收集
get (target, key, receiver) { // 查询
// proxy + reflect 反射
let result = Reflect.get(key, target, receiver);
// 收集依赖 订阅 把当前的key 和 这个 effect 对应起来
trak(target, key, effect); // 如果目标上的 key 变化了 重新让数组中的effct 执行即可
return isObject(result) ? reactive(result) : result; // 是个递归
},
2
3
4
5
6
7
依赖收集 一般收集依赖时,需看是否已经创建关系,如果没有,才可往
WeakMap()
内部添加关系 查询值会触发track
,里面是个WeakMap
,其key
可以是个对象并且是原对象,值value
是个映射表,表的值也是key
和value
,key
也是原对象,value
是不重复的数
let targetMap = new WeakMap(); // 集合和hash表
// 如果当前的 target 中 key 变化了 我就执行数组里的方法
function trak (target, key) {
let effect = activeEffectStack[activeEffectStack.length - 1];
if (effect) { // 有对应关系 才创建关联
let depsMap = targetMap.get(target);
if (!deppsMap) { // 取不到则设置一个默认值depsMap
targetMap.set(target, depsMap = new Map());
}
// 查看key是否存下
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, deps = new Set());
}
if (!deps.has(effect)) {
deps.add(effect);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
设置值触发trigger
function trigger (target, type, key) {
let depsMap = targetMap.get(target); // 去映射表当中查询
if (depsMap) {
let deps = depsMap.get(key);
if (deps) { // 将当前key 对应的effect 依次执行
deps.forEach(effect => {
effect();
})
}
}
}
2
3
4
5
6
7
8
9
10
11
核心方法
trak()
、tigger()
.