引言
vue3.0 base版终于发布了,最近了解下相关功能的介绍以及API文档,其中响应式从原来的Object.defineProperty()改为Proxy代理。
什么是Proxy
什么是Proxy代理,简而言之就是在原来的对象上加上一层拦截,当外界对对象进行访问是,必须经过这个层对象。因此提供了一层机制,可以对外界对象的访问进行过滤和修改。
- get(target, propKey, receiver): 拦截对象的读取。
- set(target, propKey, receiver): 拦截对象的设置。
- has(target, propKey): 拦截propKey in proxy, 返回一个布尔值。
- deleteProperty(target, propKey): 拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target): 拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 - preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 - isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
用法
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
}
});
proxy.name // "张三"
proxy.age // 抛出一个错误
Proxy和Object.defineProperty对比
Proxy优点
- Proxy 可以原生支持数组,而Object.defineProperty不支持数组操作
- Proxy 可以监控到删除新增等属性, 而Object.defineProperty只能监控到get,set
- Proxy有多大13中拦截方法,而Object.defineProperty不具备
Proxy缺点
- Proxy有兼容问题,对于IE11不支持,Object.defineProperty没有兼容问题
Vue3.0 响应式实现
- reactive 主要用于创建proxy代理
- effect 生成响应式
1. 通过reactive创建代理对象
reactive函数
- 参数 target 要代理的对象
- 返回值:被代理的对象
function reactive(targe) {
// 如果不为对象,则不需要代理
if(!isObject(target)) {
return;
}
return createReactiveObject(target);
}
// 真正实现代理的函数
function createReactiveObject(target) {
const baseHandle = {
get(target, key, receive) {
console.log('get', key)
const res = Reflect.get(target, key, receive);
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receive){
const hasKey = hasOwn(target, key); // 判断属性是否有
const oldValue = target[key];
const res = Reflect.set(target, key, value, receive);
return res; // 必须有返回值,如果没有返回值,当代理数组的时候会报错
},
deleteProperty(target, key){
const res = Reflect.deleteProperty(target, key);
return res;
}
}
const observer = new Proxy(target, baseHandle);
toProxy.set(target, observer);
toRaw.set(observer, target);
return observer;
}
// demo
const person = {name: 'dragon', age: 20};
const proxy = reactive(person);
proxy.age = 21;
console.log(proxy.age) //21
此时对象已被代理,但是还有一些问题,当一个对象多次代理时, 会产生多个对象:
const person = {name: 'dragon', age: 20};
const proxy = reactive(person);
const proxy1 = reactive(person);
const proxy2 = reactive(proxy1);
// 对象被多次代理,以及可以对代理对象进行二次代理
修改方案,可以做一个缓存,保存下已被代理的对象和代理的对象的对应关系:
+ const toProxy = new WeakMap(); // 放的是愿对象(key) 和 代理对象(proxy)
+ const toRaw = new WeakMap(); // 放的是代理对象(proxy) 和 愿对象(key)
// 修改reactive函数
function reactive(target) {
if(!isObject(target)) {
return;
}
// 防止被多次代理
+ const observer = toProxy.get(target);
+ if(observer) {
+ return observer;
+ }
// 防止多层代理
+ if(toRaw.has(target)) {
+ return target;
+ }
return createReactiveObject(target);
}
2. 通过effect函数实现收集
// 当proxy.name 修改后触发回掉,且第一调用时会首先调用一次回调
effect(() => {console.log(proxy.name)})
effect实现
activeEffectStacks = []; // 回调函数堆栈
// 响应式。副作用
function effect(fn) {
const effect = createReactiveEffect(fn);
effect();
}
// 创建响应式的effect
function createReactiveEffect(fn) {
const effect = function() {
return run(effect, fn); // 让fn执行,并让effect 存入栈中
}
return effect;
}
function run(effect, fn) { //运行fn, 并让effect存入栈中
try{
activeEffectStacks.push(effect);
fn(); // fun 执行的时候会调用取值
}finally {
activeEffectStacks.pop();
}
}
修改baseHandle中的get和set方法,进行收集
// 修改createReactiveObject
function createReactiveObject(target) {
const baseHandle = {
get(target, key, receive) {
console.log('get', key)
const res = Reflect.get(target, key, receive);
// 判断当前返回值是否为对象,如果是对象,则重新代理
+ track(target, key); // 收集依赖
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receive){
const hasKey = hasOwn(target, key); // 判断属性是否有
const oldValue = target[key];
const res = Reflect.set(target, key, value, receive);
+ if(!hasKey) { // 新增属性
+ trigger(target, 'add', key);
+ } else if(oldValue !== value) { // 更新
+ trigger(target, 'set', key);
+ }
return res; // 必须有返回值,如果没有返回值,当代理数组的时候会报错
},
deleteProperty(target, key){
const res = Reflect.deleteProperty(target, key);
return res;
}
}
const observer = new Proxy(target, baseHandle);
toProxy.set(target, observer);
toRaw.set(observer, target);
return observer;
}
此过程中添加了track和trigger方法,其中track是进行收集的,而trigger方法是进行调用的。
let targetsMap = new WeakMap();
function track(target, key) { // 如果这个target中的key变化了我就执行方法
const effect = activeEffectStacks[activeEffectStacks.length - 1];
if(effect) {
let depsMap = targetsMap.get(target);
if(!depsMap) {
targetsMap.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if(!deps) {
depsMap.set(key, (deps = new Set()));
}
if(!deps.has(effect)) {
console.log(effect);
deps.add(effect);
}
}
}
function trigger(target, type, key) {
let depsMap = targetsMap.get(target);
if(depsMap) {
const deps = depsMap.get(key);
if(deps) {
deps.forEach(effect => {
effect();
});
}
}
}
收集的整体过程
- 调用effect方法,在effect方法中调用createReactiveEffect()生成用于真正进行回调函数effect,同时调用effect;
- 调用effect时,会触发run方法,此时,把回调函数effect加入到栈中,同时在run中,调用fn
- 调用fn时会触发,get方法,在get中通个track进行收集,把结果缓存到
targetsMap
中 - 调用set时,在trigger触发响应
完整代码
const toProxy = new WeakMap(); // 放的是愿对象(key) 和 代理对象(proxy)
const toRaw = new WeakMap(); // 放的是代理对象(proxy) 和 愿对象(key)
// 判断是否为对象
function isObject(target) {
return typeof target === 'object' && target !== null;
}
// 判断是否包含key
function hasOwn(target, key) {
return target.hasOwnProperty(key);
}
function reactive(target) {
if(!isObject(target)) {
return;
}
// 防止被多次代理
const observer = toProxy.get(target);
if(observer) {
return observer;
}
// 防止多层代理
if(toRaw.has(target)) {
return target;
}
return createReactiveObject(target);
}
// 创建响应式对象
/**
* Refelct 会有返回值,不会报错,可以遍历 symbol
*/
function createReactiveObject(target) {
const baseHandle = {
get(target, key, receive) {
console.log('get', key)
const res = Reflect.get(target, key, receive);
// 判断当前返回值是否为对象,如果是对象,则重新代理
track(target, key); // 收集依赖
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receive){
const hasKey = hasOwn(target, key); // 判断属性是否有
const oldValue = target[key];
const res = Reflect.set(target, key, value, receive);
if(!hasKey) { // 新增属性
trigger(target, 'add', key);
} else if(oldValue !== value) { // 更新
trigger(target, 'set', key);
}
return res; // 必须有返回值,如果没有返回值,当代理数组的时候会报错
},
deleteProperty(target, key){
const res = Reflect.deleteProperty(target, key);
return res;
}
}
const observer = new Proxy(target, baseHandle);
toProxy.set(target, observer);
toRaw.set(observer, target);
return observer;
}
let activeEffectStacks = [];
let targetsMap = new WeakMap();
function track(target, key) { // 如果这个target中的key变化了我就执行方法
const effect = activeEffectStacks[activeEffectStacks.length - 1];
if(effect) {
let depsMap = targetsMap.get(target);
if(!depsMap) {
targetsMap.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if(!deps) {
depsMap.set(key, (deps = new Set()));
}
if(!deps.has(effect)) {
console.log(effect);
deps.add(effect);
}
}
}
function trigger(target, type, key) {
let depsMap = targetsMap.get(target);
if(depsMap) {
const deps = depsMap.get(key);
if(deps) {
deps.forEach(effect => {
effect();
});
}
}
}
// 响应式。副作用
function effect(fn) {
const effect = createReactiveEffect(fn);
effect();
}
// 创建响应式的effect
function createReactiveEffect(fn) {
const effect = function() {
return run(effect, fn); // 让fn执行,并让effect 存入栈中
}
return effect;
}
function run(effect, fn) { //运行fn, 并让effect存入栈中
try{
activeEffectStacks.push(effect);
fn(); // fun 执行的时候会调用取值
}finally {
activeEffectStacks.pop();
}
}
// 依赖收集,发布订阅
const proxy = reactive({name: {n: 1}});
effect(() => {
console.log(proxy.name.n)
})
proxy.name.n = 3;