我们知道,Vue3是用Proxy来实现数据代理,虽然Proxy在很久之前就已经支持了,但平时我们在业务中很少使用,因此可能对其感到一点陌生。
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy
Proxy会创建一个代理对象,用户拦截并自定义JavaScript对象的基本操作。
核心思想是在目标对象和代码之间建立一个拦截层,让我们可以监视、拦截对目标对象的操作。
创建
let proxy = new Proxy(target, handler)
有哪些handler
get(target, property, receiver):拦截对象属性访问set(target, property, value, receiver):拦截对象属性赋值has(target, property):拦截indeleteProperty(target, property):拦截deleteapply(target, thisArg, argumentsList):拦截函数调用construct(target, argumentsList, newTarget):拦截new
Demo:访问、赋值、删除、属性是否存在
let person = {
name: 'Kun',
age: 22,
saying: '你干嘛哎呦'
}
let proxy = new Proxy(person, {
get: function(target, prop, receiver) {
console.log('读取属性名:' + prop)
return target[prop]
},
set: function(target, prop, value, receiver) {
console.log('设置属性:' + prop + '为' + value)
target[prop] = value
},
deleteProperty: function(target, prop) {
console.log('删除属性:', prop)
delete target[prop]
},
has: function(target, prop) {
console.log('判断属性是否存在:', prop)
return prop in target
}
})
proxy.name
proxy.age = 23
delete proxy.saying
'age' in proxy
Demo:函数调用拦截
let func = function(a, b) { return a + b }
let funcProxy = new Proxy(func, {
apply: function(target, thisArg, argumentList) {
console.log('函数调用了,参数为' + argumentList)
return target(...argumentList)
}
})
funcProxy(1, 2)
Demo:对象实例创建拦截
let cons = function(str) {
this.a = str
}
let consProxy = new Proxy(cons, {
construct: function(target, argumentList, newTarget) {
console.log('构造函数拦截')
return new target(...argumentList)
}
})
let consExample = new consProxy('a')
上面提到了元编程的概念,元编程,就是对编程的过程进行编程。
我们可以用代码随心所欲地操作对象,但是如果需要去监控【操作对象的过程】,就是站在更高的维度去思考问题了,一旦想通了这件事,就相当于我们编程的“武器库”加入了更先进的武器。
无独有偶,我最近看的一本书中,有一个“元认知”的概念。元认知,就是对认知的过程进行认知。
Reflect
看到上面的Demo里,我们都是用各种方法或者操作符去操作原始对象,那我们是否可以使用一个统一的方法去操作对象?答案就是Reflect。
使用Reflect最大的好处,就是把Object操作由命令式改成函数式,让代码语义明确;
例如上面的Demo里,我们可以把直接操作原数据的方法改写成Reflect的:
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
// 其余方法可查询MDN文档或者阮一峰老师的ES6网站,这里只列举上面的例子出现的
Demo改造:我们发现Reflect方法和Proxy的handler是一样的。
let person = {
name: 'Kun',
age: 22,
saying: '你干嘛哎呦'
}
let proxy = new Proxy(person, {
get: function(target, prop, receiver) {
console.log('读取属性名:' + prop)
return Reflect.get(target, prop, receiver)
},
set: function(target, prop, value, receiver) {
console.log('设置属性:' + prop + '为' + value)
Reflect.set(target, prop, value, receiver)
},
Reflect: function(target, prop) {
console.log('删除属性:', prop)
Reflect.Reflect(target, prop)
},
has: function(target, prop) {
console.log('判断属性是否存在:', prop)
return Reflect.has(target, prop)
}
})
proxy.name
proxy.age = 23
delete proxy.saying
'age' in proxy
观察者模式
当我们了解了proxy和reflect的基本用法之后,我们是否可以在脑海中画出Vue3的数据驱动DOM变化的轮廓?在学习Vue3之前,我们看一下用Proxy实现一个观察者模式的Demo:
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn); // queuedObservers是一个函数集合
const observable = obj => new Proxy(obj, { set }); // 传入一个对象,返回这个对象的proxy,并且拦截了set方法
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer()); // 每次对对象的属性进行写入操作时,遍历这个函数集合,每个函数都执行一下
return result;
}
// person是一个被监听set方法的Proxy
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print); // 把print加入函数集合里,如果Proxy发生set操作,print就会执行
person.name = '李四'; // name改变后,自动执行print