Vue3前置知识探索——Proxy和Reflect

1,043 阅读3分钟

我们知道,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):拦截in
  • deleteProperty(target, property):拦截delete
  • apply(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')

上面提到了元编程的概念,元编程,就是对编程的过程进行编程。

我们可以用代码随心所欲地操作对象,但是如果需要去监控【操作对象的过程】,就是站在更高的维度去思考问题了,一旦想通了这件事,就相当于我们编程的“武器库”加入了更先进的武器。

无独有偶,我最近看的一本书中,有一个“元认知”的概念。元认知,就是对认知的过程进行认知。

image.png

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