Vue3.0
中双向绑定的实现从Object.defineProperty
变为ES6
中的Proxy
。其实Proxy
和definePrototype
在平常业务场景中还是挺少用到,但是作为前端工程师这一块是一个主流框架的底层原理。所以还是必须要了解这两种方法。
一、Proxy
1. 什么是 Proxy
- MDN 中定义:Proxy 对象用于创建一个对象的代理,从而实现基本的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
- ES6 入门中定义:Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。
Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy
原意就是代理,可以理解为代理某些操作。
2. 语法
1 | const p = new Proxy(target, handler); |
target
:就是你要是用Proxy
包装的目标对象(可以是任何类型的对象,包括函数、数组,甚至是另一个代理)。handler
:是一个对象,里面有各种你代理的方法。
3. 一个例子
1 | let obj = { |
- 输出结果
- target:就是你代理的对象。
- propKey:要读取的属性名。
- value:要设置的新 value。
- receiver:Proxy 或者继承 Proxy 的对象。
4. Proxy 的支持的拦截操作
get(target, propKey, receiver)
:拦截对象属性的读取,如proxy.foo
proxy['foo']
set(target, propKey, value, receiver)
:返回一个布尔值。拦截对象属性的设置,如proxu.foo = v
proxy['foo'] = v
,
has(target, propKey)
:拦截propKey in proxy
的操作,返回一个布尔值。deleteProperty(target, propKey)
:返回一个布尔值,拦截以下方法delete proxy[propKey]
ownKeys(target)
:返回一个数组,该方法返回目标对象所有的属性的属性名,而Obejct.keys()
的返回结果仅包括目标对象自身的可属性。拦截以下方法Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
for...in
循环
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)
。
注:描述对象指value
、writable
、enumerable
、configurable
、get
、set
这六个描述对象。
二、Reflect
说来惭愧,笔者在很长一段时间里,都没有意识到前端还有Reflect
这一模块,用模块来形容Reflect
是因为,它长得是一个对象的样子,也很像一个函数对象
,但很抱歉的是,他没有new
方法,他就是一个普普通通的对象
。
1. 什么是 Reflect
- MDN 中的定义
:Reflect
是一个内置的对象,它提供拦截JavaScript
操作的方法。这些方法与proxy handlers
的方法相同。Reflect
不是一个函数对象,因此它是不可构造的。 - ES6 中的定义
:Reflect
对象与Proxy
很相似,都是ES6
为了操作对象而提供的新的 API。
2. 如何使用
首先,我们要了解,为什么要创造出Reflect
。ES6 中列出了Reflect
对象设计的意义。
- 将
Object
中明显属于语言内部的方法如Object.defineProperty
放到Reflect
对象中来。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来新方法指将部署在Reflect
对象上,可以说Reflect
上可以找到语言内部的方法。 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回 false。 - 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
1 | "assign" in Object; // true; |
Reflect
对象的方法Proxy
对象的方法,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
1 | var loggedObj = new Proxy(obj, { |
3. Reflect 的支持的拦截操作
与Proxy
支持的操作完全相同
三、Object.defineProperty
1. 什么是 Object.defineProperty
- MDN 中的定义
:Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
1 | const object1 = {}; |
2. 使用
1 | Object.defineProperty(obj, prop, descriptor); |
- obj:要定义属性的对象
- prop:要定义或修改的属性的名称或
Symbol
- descriptor:要定义或修改的属性描述符。