【Vue源码解读】一文看懂Vue2和Vue3的Proxy
Vue2的proxy实现
众所周知,Vue2中实现代理的方式是通过数据劫持来实现的,也就是使用的Object.defineProperty
;简单举例如下:
var obj = {};var initValue = 20;
Object.defineProperty(obj, "age", {
get: function () {
console.log('get')
return initValue;
},
set: function (value) {
console.log('set')
initValue = value;
}
});
console.log(obj.age);
obj.age = 22;
console.log(obj.age);
复制代码
如上代码,控制台会输出:
在Vue2中其实就是这么来实现的数据劫持,其中get里面会收集依赖--depend
,set里面会触发依赖--notify
;vue-defineReactive源码直达
当然还有数组的处理,因为数组是个比较特殊的数据类型,Vue2中对数组的方法进行了重新封装,改变原始数组数据的方法都被重新封装了,如下:push、pop、shift、unshift、splice、sort、reverse
;vue数组重新封装源码直达
关于Vue2中的observe具体是如何实现的,在这里不做过多解读,可以看下这篇文章Vue2源码解读-Observe
Vue2的proxy存在的问题
在Vue2中,即便是对数组进行了重新封装,还是会存在问题,如下:
var list = ['tom', 'jack', 'draven', 'ifil']// 直接改变数组的长度,Vue2是监听不到的,
list.length = 3;
// 直接改变数组中的某个元素,Vue2中也是监听不到的,
list[2] = 'luckyDraven';
复制代码
虽然Vue2中对上面代码中对数组的修改方式提供了Vue.$set方法去弥补,但是对于开发人员来说,也是增加了额外的工作量嘛。关于这部分内容Vue2的官方文档也进行了说明。关于数组
Vue3的Proxy
Vue3抛弃了数据劫持,转而使用的是Proxy+Reflect来实现的数据代理。
什么是Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。Proxy 的构造函数语法为:
const p = new Proxy(target, handler)复制代码
- target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
handler可以包含的方法(也叫捕捉器)如下:
// Object.getPrototypeOf 方法的捕捉器。handler.getPrototypeOf()
// Object.setPrototypeOf 方法的捕捉器。
handler.setPrototypeOf()
// Object.isExtensible 方法的捕捉器。
handler.isExtensible()
// Object.preventExtensions 方法的捕捉器。
handler.preventExtensions()
// Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.getOwnPropertyDescriptor()
// Object.defineProperty 方法的捕捉器。
handler.defineProperty()
// in 操作符的捕捉器。
handler.has()
// 属性读取操作的捕捉器。
handler.get()
// 属性设置操作的捕捉器。
handler.set()
// delete 操作符的捕捉器。
handler.deleteProperty()
// Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.ownKeys()
// 函数调用操作的捕捉器。
handler.apply()
// new 操作符的捕捉器。
handler.construct()
复制代码
举个官方例子:
const handler = { get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
复制代码
在上面简单的例子中,当对象中不存在属性名时,默认返回值为 37。上面的代码以此展示了 get handler 的使用场景。详细描述可以移步官网Proxy
什么是Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。
Reflect 对象提供了以下静态方法,这些方法与proxy handler methods的命名相同.
语法:
// 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。
// 和 Function.prototype.apply() 功能类似。
Reflect.apply(target, thisArgument, argumentsList)
// 对构造函数进行 new 操作,相当于执行 new target(...args)。
Reflect.construct(target, argumentsList[, newTarget])
// 和 Object.defineProperty() 类似。如果设置成功就会返回 true
Reflect.defineProperty(target, propertyKey, attributes)
// 作为函数的delete操作符,相当于执行 delete target[name]。
Reflect.deleteProperty(target, propertyKey)
// 获取对象身上某个属性的值,类似于 target[name]。
Reflect.get(target, propertyKey[, receiver])
// 类似于 Object.getOwnPropertyDescriptor()。
// 如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined.
Reflect.getOwnPropertyDescriptor(target, propertyKey)
// 类似于 Object.getPrototypeOf()。
Reflect.getPrototypeOf(target)
// 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
Reflect.has(target, propertyKey)
// 类似于 Object.isExtensible().
Reflect.isExtensible(target)
// 返回一个包含所有自身属性(不包含继承属性)的数组。
// (类似于 Object.keys(), 但不会受enumerable影响).
Reflect.ownKeys(target)
// 类似于 Object.preventExtensions()。返回一个Boolean。
Reflect.preventExtensions(target)
// 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
Reflect.set(target, propertyKey, value[, receiver])
// 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
Reflect.setPrototypeOf(target, prototype)
复制代码
举个例子:
- 检测一个对象是否存在特定属性
const duck = {
name: 'Maurice',
color: 'white',
greeting: function() {
console.log(`Quaaaack! My name is ${this.name}`);
}
}
Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false
复制代码
- 为一个对象赋值,获取对象所有的属性
const duck = {
name: 'Maurice',
color: 'white',
greeting: function() {
console.log(`Quaaaack! My name is ${this.name}`);
}
}
Reflect.set(duck, 'eyes', 'black'); // returns "true" if successful
console.log(Reflect.ownKeys(duck)); // ["name", "color", "greeting", "eyes"]
复制代码
实现部分
阅读到这里,应该可以看到Proxy中handler部分和Reflect中所支持的静态方法是一一对应的。
Vue3中通过Proxy结合Reflect来彻底代理实现了数据代理。关于源码分析部分可以查看这篇文章Vue3源码解读-createReactiveObject。
Vue3中通过不同的api来调用不同的handler实现数据代理。贴一下源码解读中的图吧:
Vue2和Vue3的Proxy对比
- 通过Vue2中
Object.defineProperty
是不支持对数组的监听的,只支持对对象的监听; - 来看个Vue3中使用Proxy+Reflect的例子
var list = ["tom", "jack"];
var proxy = new Proxy(list, {
set: function (target, key, value) {
Reflect.set(target, key, value);
}
});
proxy.length = 3;
console.log(list); // ["tom", "jack", undefined]
proxy[2] = "draven";
console.log(list); // ["tom", "jack", "draven"]
复制代码
可以看到通过Proxy+Reflect实现了Vue2中length和直接赋值监听不到的问题。
the last
当然了Proxy的优势不止上面数组的部分,还有一些其他的优势,可以去官网进行深度阅读。
以上是 【Vue源码解读】一文看懂Vue2和Vue3的Proxy 的全部内容, 来源链接: utcz.com/a/129382.html