vue.js 3.0 重写了响应式系统,和vue.js 2.x 相比主要变化体现在以下几个方面。
Proxy
对象实现属性监听,在初始化的时候不需要遍历所有的属性再把属性通过 defineProperty()
转化成 getter
和 setter
。
reactive
/ ref
/ toRefs
/ computed
(创建响应式数据或响应式对象)
effect
( watch
/ watchEffect
底层所使用的函数)
track
(收集依赖)
trigger
(触发更新)
问题1: Proxy对象的使用中,set
和deleteProperty
中需要返回布尔类型的值;在严格模式下,如果返回false
的话会出现Type Error
的异常。
'use strict'
const target = {
foo: 'xxx',
bar: 'yyy'
}
// `set`和`deleteProperty`中需要返回布尔类型的值;
// 在严格模式下,如果返回`false`的话会出现`Type Error`的异常。
const proxy = new Proxy(target, {
get(target, key, receiver){
// return target[key]
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver){
// target[key] = value
return Reflect.set(target, key, value, receiver)
},
deleteProperty(target, key){
// delete target[key]
return Reflect.deleteProperty(target, key)
}
})
proxy.bar = 'zzz'
// delete target.foo
从上面的Proxy对象的创建我们可以看到,创建时除了要被代理的目标对象以外,我们还需要传递第二个参数,它是一个对象,我们可以叫做处理器或者监听器。其中的get/set/deleteProperty
可以分别监听:对属性的访问、赋值以及删除操作。get/set
这两个方法最后有一个参数叫receiver
,在这里代表的是当前的Proxy对象或者继承Proxy的对象。在获取或设置值的时候使用了Reflect
,分别调用了Reflect中的同名get
和set
这两个方法。Reflect
是反射的意思,是es6中新增的成员。是在代码运行期间用来获取或设置对象中的成员。过去es6之前JavaScript中并没有反射,它可以很随意的把一些方法挂载到Object中,如Object.getPrototypeOf()
方法,Reflect中也有对应的方法Reflect.getPrototypeOf()
,方法的作用是一样的,只是表达语义的问题。如果在Reflect
中有对应的Object
中的方法,我们都建议使用Reflect
中的方法。所以例子中在创建Proxy对象传入的处理器中都是使用了Reflect
来操作对象中的成员。Vue.js 3.0中的源码也是使用的这种方式来操作对象的成员的。
问题2: 与Proxy
和Reflect
中使用的receiver
相关
Proxy中的receiver
:Proxy或者继承 Proxy的对象,它是当前创建的proxy对象或者继承自当前proxy的子对象。
Reflect中的receiver
:如果target
对象设置了getter
,getter
中的this
指向receiver
// Proxy中的`receiver`:Proxy或者继承 Proxy的对象,它是当前创建的proxy对象或者继承自当前proxy的子对象。
// Reflect中的`receiver`:如果`target`对象设置了`getter`,`getter`中的`this`指向`receiver`
const obj = {
get foo() {
console.log(this)
return this.bar
}
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
if(key === 'bar') {
return 'value - bar'
}
// return Reflect.get(target, key) // 这里Reflect.get没有传递receiver, 上面的obj没有bar属性,所以会返回undefined
return Reflect.get(target, key, receiver) // 这里向Reflect.get传递了receiver, 获取foo属性时,foo中的this指向了receiver,也就是当前的创建的proxy, 它的getter中对key值做了判断,所以会返回'value - bar'
}
})
receiver
,以防止类似的意外发生。reactive
函数模拟实现target
,判断这个参数是否是对象,如果不是的话直接返回。handler
,设置 get/set/deleteProperty
reactive
函数对其进行响应式处理。get
中收集依赖set
中触发更新deleteProperty
中触发更新new Proxy(target, handler)
代理对象// utils functions
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function convert(target) {
return isObject(target) ? reactive(target) : target;
}
function hasOwnProperty(key) {
return Object.prototype.hasOwnProperty(key);
}
function hasOwn(target, key) {
return hasOwnProperty.call(target, key);
}
export function reactive(target) {
if (!isObject(target)) return target;
const handler = {
get(target, key, receiver) {
// 收集依赖 Todo
console.log('get', key);
track(target, key);
const result = Reflect.get(target, key, receiver);
return convert(result);
},
set(target, key, value, receiver) {
const oldVal = Reflect.get(target, key, receiver);
// set and deleteProperty must return a boolean
let result = true;
if (oldVal !== value) {
result = Reflect.set(target, key, value, receiver);
// 触发更新 Todo
trigger(target, key);
console.log('set', key, value);
}
return result;
},
deleteProperty(target, key) {
const haskey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (haskey && result) {
// 触发更新
trigger(target, key);
console.log('delete', key);
}
return result;
},
};
return new Proxy(target, handler);
}
targetMap
/ depsMap
/ dep
WeakMap
,即弱引用的Map,里面的key是就是我们的target对象。因为是弱引用,当目标对象失去引用之后,可以销毁。
Map
,这个字典中的key是目标对象中的属性名称,值是一个 Set
集合。
effect
函数。因为我们可以多次调用一个effect,在effect中访问同一个属性,那这个时候该属性会收集多次依赖,对应多个effect函数。
所以通过这种结构,我们可以存储目标对象,目标对象的的属性,以及属性对一个的Effect函数。一个属性可能对应多个函数。那么将来触发更新的时候,我们可以来这个结构中根据目标对象的属性找到effect函数,然后执行。
effect
和 track
实现原理effect
函数:
effect
中,首先要执行一次传入的函数参数callback
。callback
中会访问响应式对象属性,收集依赖。callback
,这个变量叫做activeEffect
,默认值为null
。effect
中把callback
存储到activeEffect
中activeEffect
重置为null
,因为收集依赖的时候如果有嵌套属性的话,是一个递归的过程。effect
函数定义如下:let activeEffect = null;
export function effect(callback) {
activeEffect = callback;
// 初始化生时会被调用一次
callback(); // 访问响应式对象属性,去收集依赖
activeEffect = null;
}
track
函数:它的作用是收集依赖,它需要往targetMap中添加记录effect函数的callback。
track
函数接收两个参数,一个是目标对象target
,还有一个是要跟踪的属性key
。activeEffect
,因为最终我们要去保存activeEffect
。如果activeEffect
为null
则直接返回,说明没有要收集的依赖。targetMap
中根据当前的target
来找depsMap
,因为我们当前的target
就是我们targetMap
中的键。depsMap
,因为当前的target
可能没有收集过依赖。如果没有找到的话,那么就要为当前的target
创建一个对应的depsMap
,来存储对应的属性key
和dep
对象(也就是我们要执行的那些effect
函数)。targetMap
中。dep
对象。在depsMap
里边根据属性key作为健来查找dep
,判断dep
是否存在。dep
是一个Set类型的集合,用来存储我们属性对应的那些effect
函数。如果没有找到对的话,也要跟上面一样,要创建一个新的dep
集合,并且把它添加到depsMap
中。effect
函数添加到dep
集合中。dep.add(activeEffect)
get
中来调用一下这个 track
函数,进行收集依赖。let targetMap = new WeakMap();
export function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
trigger
函数:它的作用是触发更新,它要去targetMap
中找到属性对应的的effect
函数,然后来执行。
trigger
函数接收两个参数,一个是目标对象target
,还有一个是要跟踪的属性key
。trigger
函数中,我们要根据target
在targetMap
中找到depsMap
,即把target
作为键,来targetMap
中找到depsMap
。depsMap
中存储的是我们的属性以及dep
集合(键值对)。dep
集合存储的就是我们属性对应的那些effect
函数。depsMap
,没有找到直接返回。key
来找对应的dep
集合。dep
集合中是否有值。如果有值的话就要遍历dep
集合,然后执行它里边的每一个effect
函数。set
和deleteProperty
方法中,调用trigger函数触发更新。export function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
dep.forEach((effect) => {
effect();
});
}
import { reactive, effect } from './reactivity';
const obj = {
name: 'huiquan',
age: '18',
message: 'hi',
};
const reactiveObj = reactive(obj);
let himsg = '';
effect(() => {
himsg = `${reactiveObj.name}, i'm ${reactiveObj.age}`;
});
console.log(himsg);
reactiveObj.age = 28;
console.log(reactiveObj.age);
console.log(himsg);
reactiveObj.name = 'gg';
console.log(himsg);
以上函数都在一个模块reactivity.js中定义:
// utils functions
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function convert(target) {
return isObject(target) ? reactive(target) : target;
}
function hasOwnProperty(key) {
return Object.prototype.hasOwnProperty(key);
}
function hasOwn(target, key) {
return hasOwnProperty.call(target, key);
}
export function reactive(target) {
if (!isObject(target)) return target;
const handler = {
get(target, key, receiver) {
// 收集依赖 Todo
console.log('get', key);
track(target, key);
const result = Reflect.get(target, key, receiver);
return convert(result);
},
set(target, key, value, receiver) {
const oldVal = Reflect.get(target, key, receiver);
// set and deleteProperty must return a boolean
let result = true;
if (oldVal !== value) {
result = Reflect.set(target, key, value, receiver);
// 触发更新 Todo
trigger(target, key);
console.log('set', key, value);
}
return result;
},
deleteProperty(target, key) {
const haskey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (haskey && result) {
// 触发更新
trigger(target, key);
console.log('delete', key);
}
return result;
},
};
return new Proxy(target, handler);
}
let activeEffect = null;
export function effect(callback) {
activeEffect = callback;
// 初始化生时会被调用一次
callback(); // 访问响应式对象属性,去收集依赖
activeEffect = null;
}
let targetMap = new WeakMap();
export function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
export function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
dep.forEach((effect) => {
effect();
});
}
【在线案例演示地址】