Proxy和defineProperty

Vue3.0中双向绑定的实现从Object.defineProperty变为ES6中的Proxy。其实ProxydefinePrototype在平常业务场景中还是挺少用到,但是作为前端工程师这一块是一个主流框架的底层原理。所以还是必须要了解这两种方法。

一、Proxy

1. 什么是 Proxy

  • MDN 中定义:Proxy 对象用于创建一个对象的代理,从而实现基本的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
  • ES6 入门中定义:Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy原意就是代理,可以理解为代理某些操作。

2. 语法

1
const p = new Proxy(target, handler);
  • target:就是你要是用Proxy包装的目标对象(可以是任何类型的对象,包括函数、数组,甚至是另一个代理)。
  • handler:是一个对象,里面有各种你代理的方法。

3. 一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let obj = {
age: 10,
};

let handler = {
get: (target, propKey, receiver) => {
console.log(target, propKey, receiver);
return target[propKey];
},

set: (target, propKey, value, receiver) => {
console.log(target, propKey, value, receiver);
target[propKey] = value;
},
};

const proxy_obj = new Proxy(obj, handler);
proxy_obj.age = 11;
console.log(proxy_obj.age);
  • 输出结果
    输出结果
  • 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)

注:描述对象指valuewritableenumerableconfigurablegetset这六个描述对象。

二、Reflect

说来惭愧,笔者在很长一段时间里,都没有意识到前端还有Reflect这一模块,用模块来形容Reflect是因为,它长得是一个对象的样子,也很像一个函数对象,但很抱歉的是,他没有new方法,他就是一个普普通通的对象

1. 什么是 Reflect

  • MDN 中的定义:Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
  • ES6 中的定义:Reflect对象与Proxy很相似,都是ES6为了操作对象而提供的新的 API。

2. 如何使用

首先,我们要了解,为什么要创造出Reflect。ES6 中列出了Reflect对象设计的意义。

  1. Object中明显属于语言内部的方法如Object.defineProperty放到Reflect对象中来。现阶段,某些方法同时在ObjectReflect对象上部署,未来新方法指将部署在Reflect对象上,可以说Reflect上可以找到语言内部的方法。
  2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回 false。
  3. Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。
1
2
3
4
"assign" in Object; // true;

// 新写法
Reflect.has(Object, "assign"); // true;
  1. Reflect对象的方法Proxy对象的方法,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var loggedObj = new Proxy(obj, {
get(target, name) {
// proxy修改默认行为
console.log("get", target, name);

// 返回默认行为
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log("delete" + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log("has" + name);
return Reflect.has(target, name);
},
});

3. Reflect 的支持的拦截操作

Proxy支持的操作完全相同

三、Object.defineProperty

1. 什么是 Object.defineProperty

  • MDN 中的定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
1
2
3
4
5
6
7
8
9
10
11
12
const object1 = {};

Object.defineProperty(object1, "property1", {
value: 42,
writable: false,
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1);
// expected output: 42

2. 使用

1
Object.defineProperty(obj, prop, descriptor);
  • obj:要定义属性的对象
  • prop:要定义或修改的属性的名称或Symbol
  • descriptor:要定义或修改的属性描述符。
-------------本文结束感谢您的阅读-------------