Skip to content

Proxy和Reflect

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy是一个类,需要使用new关键字实例化

同时,Vue3响应式的部分原理也是和Proxy相关

1.Proxy

1.1 构造函数

js
new Proxy(target, handler)

参数:

  • target:需要被代理的对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个对象,其属性是定义了在对代理执行操作时的行为的函数。可配置属性详情如下

1.1 具体用法

js
const obj = {};

const proxy = new Proxy(obj, {
  get(target, key) {},
  set(target, key, value) {},
  // ...
});

obj.name = "xiazhen";

以上代码,obj称做目标对象proxy称为代理对象

1.2 handler

handler是一个对象,在创建Proxy代理对象时,可以添加相应的配置项,对代理对象的属性进行约束

handler对象中属性都是钩子函数,分类如下

set()

set()方法用于拦截设置属性值的操作,当代理对象中的属性被修改时会触发set()函数

js
const proxy = new Proxy(target, {
    set(target, property, value, receiver) {}
});

参数:

  • target:目标对象。

  • property:将被设置的属性名或 Symbol

    • 当目标对象是Object时,property为修改属性的键
    • 当目标对象是Array时,property为修改属性的下标
  • value:新属性值。

  • receiver:最初接收赋值的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)。

返回值:方法应当返回一个布尔值。

  • 返回 true 代表属性设置成功。
  • 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。

TIP

该方法会拦截目标对象的以下操作:

  • 指定属性值:proxy[foo] = barproxy.foo = bar
  • 指定继承者的属性值:Object.create(proxy)[foo] = bar
  • Reflect.set()

如果违背以下的约束条件,proxy 会抛出一个 TypeError 异常:

  • 若目标属性是一个不可写及不可配置的数据属性,则不能改变它的值。
  • 如果目标属性没有配置存储方法,即 [[Set]] 属性的是 undefined,则不能设置它的值。
  • 在严格模式下,如果 set() 方法返回 false,那么也会抛出一个 TypeError 异常。

代理对象

js
const obj = {
  name: "ikun",
};

const proxy = new Proxy(obj, {
  set(target, key, value) {
    target[key] = value;
  },
});

proxy.name = "xiaohei";

console.log(obj)        // { name: 'xiaohei' }
console.log(proxy);     // { name: 'xiaohei' }

代理数组

js
const arr = [1, 2, 3];

const proxy = new Proxy(arr, {
  set(target, key, value) {
    target[key] = value;
  },
});

proxy[0] = 666;

console.log(arr);     // [ 666, 2, 3 ]
console.log(proxy);     // [ 666, 2, 3 ]

get()

get()方法用于拦截对象的读取属性操作。当代理对象的数下被读取时,会触发get()函数

js
const proxy = new Proxy(target, {
  set(target, property, receiver) {}
});

参数:

  • target:目标对象。
  • property:被获取的属性名或下标
  • receiver:Proxy 或者继承 Proxy 的对象

返回值:get()方法可以返回任何值。一般返回源对象中相应属性的值

TIP

该方法会拦截目标对象的以下操作:

  • 访问属性:proxy[foo] 和 proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]
  • Reflect.get()

如果违背了以下的约束,proxy 会抛出 TypeError:

  • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同。
  • 如果要访问的目标属性没有配置访问方法,即 get 方法是 undefined 的,则返回值必须为 undefined。
js
const obj = {
  name: "ikun",
};

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log('属性被读取')
    return target[key];
  },
});

console.log(proxy.name);    // ikun

has()

has()方法是针对 in操作符的代理方法。

js
const proxy = new Proxy(target, {
    has(target, prop) {},
});

参数:

  • target:目标对象。
  • prop:需要检查是否存在的属性。

返回值:has 方法返回一个 boolean 属性的值。

TIP

这个钩子可以拦截下面这些操作:

  • 属性查询:foo in proxy
  • 继承属性查询:foo in Object.create(proxy)
  • with 检查: with(proxy) { (foo); }
  • Reflect.has()

如果违反了下面这些规则,proxy 将会抛出 TypeError:

  • 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏。
  • 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏
js
const obj = {
  name: "ikun",
};

const proxy = new Proxy(obj, {
  has(target, prop) {
    // return prop in target;
    return target.hasOwnProperty(prop);
  },
});

console.log('name' in proxy)      // true
console.log('ikun' in proxy)      // false

deleteProperty()

deleteProperty() 方法用于拦截对对象属性的 delete操作。

const proxy = new Proxy(target, {
  deleteProperty(target, property) {},
});

参数:

  • target:目标对象。
  • property:待删除的属性名。

返回值:返回一个 Boolean类型的值,表示了该属性是否被成功删除。

TIP

该方法会拦截以下操作:

  • 删除属性:delete proxy[foo]delete proxy.foo
  • Reflect.deleteProperty()

如果违背了以下不变量,proxy 将会抛出一个 TypeError:

  • 如果目标对象的属性是不可配置的,那么该属性不能被删除。
js
var p = new Proxy(
  {},
  {
    deleteProperty: function (target, prop) {
      console.log("called: " + prop);
      return true;
    },
  },
);

delete p.a; // "called: a"

apply()

apply() 方法用于拦截函数的调用。

js
const proxy = new Proxy(target, {
  apply(target, thisArg, argumentsList) {},
});

参数:

  • target:目标对象(函数)。
  • thisArg:被调用时的上下文对象。
  • argumentsList:被调用时的参数数组。

返回值:apply 方法可以返回任何值。

TIP

该方法会拦截目标对象的以下操作:

如果违反了以下约束,代理将抛出一个 TypeError:

target 必须是可被调用的。也就是说,它必须是一个函数对象。

js
const add = function (a, b) {
  return a + b;
};
const proxy = new Proxy(add, {
  apply(target, thisArg, argumentsList) {
    return target.apply(thisArg, argumentsList);
  },
});

const res = proxy(5, 6);    

console.log(res); // 11

construct()

construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 construct()内部方法(即 new target 必须是有效的)。

js
const proxy = new Proxy(target, {
  construct: function (target, argumentsList, newTarget) {},
});

参数:

  • target:目标对象。
  • argumentsList:constructor 的参数列表。
  • newTarget:最初被调用的构造函数。

返回值:construct 方法必须返回一个对象。

TIP

该拦截器可以拦截以下操作:

如果违反以下约定,代理将会抛出错误 TypeError:

  • 必须返回一个对象。
js
class Person {
  constructor(name) {
    this.name = name;
  }
}

const proxy = new Proxy(Person, {
  construct(target, argumentsList, newTarget) {
    return new target(...argumentsList);
  },
});


const proxyPerson = new proxy('John');

console.log(proxyPerson.name); // John

defineProperty()

defineProperty() 用于拦截对象的 Object.defineProperty() 操作。

js
const proxy = new Proxy(target, {
  defineProperty: function (target, property, descriptor) {},
});

参数:

  • target:目标对象。
  • property:待检索其描述的属性名。
  • descriptor:待定义或修改的属性的描述符。

返回值:defineProperty 方法必须以一个 Boolean返回,表示定义该属性的操作成功与否。

TIP

该方法会拦截目标对象的以下操作:

如果违背了以下的不变量,proxy 会抛出 TypeError:

  • 如果目标对象不可扩展,将不能添加属性。
  • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话。
  • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的。
  • 如果一个属性在目标对象中存在对应的属性,那么 Object.defineProperty(target, prop, descriptor) 将不会抛出异常。
  • 在严格模式下,false 作为 handler.defineProperty 方法的返回值的话将会抛出 TypeError 异常。
js
const p = new Proxy(
  {},
  {
    defineProperty: function (target, prop, descriptor) {
      console.log("called: " + prop);
      return true;
    },
  },
);

var desc = { configurable: true, enumerable: true, value: 10 };
Object.defineProperty(p, "a", desc); // "called: a"

TIP

当调用 Object.defineProperty() 或者 Reflect.defineProperty(),传递给 definePropertydescriptor 有一个限制 - 只有以下属性才有用,非标准的属性将会被无视:

  • enumerable
  • configurable
  • writable
  • value
  • get
  • set
js
const p = new Proxy(
  {},
  {
    defineProperty(target, prop, descriptor) {
      console.log(descriptor);
      return Reflect.defineProperty(target, prop, descriptor);
    },
  },
);

Object.defineProperty(p, "name", {
  value: "proxy",
  type: "custom",
}); // { value: 'proxy' }

getOwnPropertyDescriptor()

getOwnPropertyDescriptor() 方法是 Object.getOwnPropertyDescriptor() 的钩子。

js
const p = new Proxy(target, {
  getOwnPropertyDescriptor: function (target, prop) {},
});

参数:

  • target:目标对象。
  • prop:返回属性名称的描述。

返回值:返回一个 object 或 undefined

TIP

这个钩子可以拦截这些操作:

如果下列不变量被违反,代理将抛出一个 TypeError

  • getOwnPropertyDescriptor 必须返回一个 object 或 undefined
  • 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
  • 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在。
  • 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在。
  • 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在。
  • Object.getOwnPropertyDescriptor(target)的结果可以使用 Object.defineProperty 应用于目标对象,也不会抛出异常。
js
var p = new Proxy(
  { a: 20 },
  {
    getOwnPropertyDescriptor: function (target, prop) {
      console.log("called: " + prop);
      return { configurable: true, enumerable: true, value: 10 };
    },
  },
);

console.log(Object.getOwnPropertyDescriptor(p, "a").value); // "called: a"; output 10

getPrototypeOf()

getPrototypeOf() 是一个代理(Proxy)方法,当读取代理对象的原型时,该方法就会被调用。

js
const p = new Proxy(obj, {
  getPrototypeOf(target) {
    // ...
  }
});

参数:

  • target:被代理的目标对象。

返回值:返回值必须是一个对象或者 null

TIP

在 JavaScript 中,下面这五种操作(方法/属性/运算符)可以触发 JS 引擎读取一个对象的原型,也就是可以触发 getPrototypeOf() 代理方法的运行:

如果遇到了下面两种情况,JS 引擎会抛出 TypeError 异常:

  • getPrototypeOf() 方法返回的不是对象也不是 null
  • 目标对象是不可扩展的,且 getPrototypeOf() 方法返回的原型不是目标对象本身的原型。
js
var obj = {};
var proto = {};
var handler = {
  getPrototypeOf(target) {
    console.log(target === obj); // true
    console.log(this === handler); // true
    return proto;
  },
};

var p = new Proxy(obj, handler);
console.log(Object.getPrototypeOf(p) === proto); // true

5 种触发 getPrototypeOf 代理方法的方式

js
var obj = {};
var p = new Proxy(obj, {
  getPrototypeOf(target) {
    return Array.prototype;
  },
});
console.log(
  Object.getPrototypeOf(p) === Array.prototype, // true
  Reflect.getPrototypeOf(p) === Array.prototype, // true
  p.__proto__ === Array.prototype, // true
  Array.prototype.isPrototypeOf(p), // true
  p instanceof Array, // true
);

两种情况下的异常

js
var obj = {};
var p = new Proxy(obj, {
  getPrototypeOf(target) {
    return "foo";
  },
});
Object.getPrototypeOf(p); // TypeError: "foo" is not an object or null

var obj = Object.preventExtensions({});
var p = new Proxy(obj, {
  getPrototypeOf(target) {
    return {};
  },
});
Object.getPrototypeOf(p); // TypeError: expected same prototype value

isExtensible()

isExtensible() 方法用于拦截对对象的 Object.isExtensible()。

js
const p = new Proxy(target, {
  isExtensible: function (target) {},
});

参数:

  • target:目标对象。

返回值:必须返回一个 Boolean 值或可转换成 Boolean 的值。

TIP

该方法会拦截目标对象的以下操作:

如果违背了以下的约束,proxy 会抛出 TypeError:

  • Object.isExtensible(proxy) 必须同 Object.isExtensible(target) 返回相同值。
js
var p = new Proxy(
  {},
  {
    isExtensible: function (target) {
      console.log("called");
      return true; // 也可以 return 1; 等表示为 true 的值
    },
  },
);

console.log(Object.isExtensible(p)); // "called"; outputs true

ownKeys()

ownKeys() 方法用于拦截 Reflect.ownKeys()

js
const p = new Proxy(target, {
  ownKeys: function (target) {},
});

参数:

  • target:目标对象

返回值:返回一个可枚举对象。

TIP

该拦截器可以拦截以下操作::

如果违反了下面的约束,proxy 将抛出错误 TypeError:

  • ownKeys 的结果必须是一个数组。
  • 数组的元素类型要么是一个 String ,要么是一个 Symbol.
  • 结果列表必须包含目标对象的所有不可配置(non-configurable)、自有(own)属性的 key.
  • 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的 key,不能有其他值。
js
var p = new Proxy(
  {},
  {
    ownKeys: function (target) {
      console.log("called");
      return ["a", "b", "c"];
    },
  },
);

console.log(Object.getOwnPropertyNames(p)); // "called"; outputs [ 'a', 'b', 'c' ]

preventExtensions()

preventExtensions() 方法用于设置对Object.preventExtensions()的拦截

js
const p = new Proxy(target, {
  preventExtensions: function (target) {},
});

参数:

  • target:所要拦截的目标对象。

返回值:返回一个布尔值。

TIP

这个 trap 可以拦截这些操作:

如果违反了下列规则,proxy 则会抛出一个 TypeError:

  • 如果目标对象是可扩展的,那么只能返回 false
js
var p = new Proxy(
  {},
  {
    preventExtensions: function (target) {
      console.log("called");
      Object.preventExtensions(target);
      return true;
    },
  },
);

console.log(Object.preventExtensions(p)); // "called"; outputs false

setPrototypeOf()

setPrototypeOf() 方法主要用来拦截 Object.setPrototypeOf()

js
const p = new Proxy(target, {
  setPrototypeOf: function (target, prototype) {},
});

参数:

  • target:被拦截目标对象。
  • prototype:对象新原型或为null.

返回值:如果成功修改了[[Prototype]], setPrototypeOf 方法返回 true,否则返回 false.

TIP

这个方法可以拦截以下操作:

如果违反了下列规则,则 proxy 将抛出一个 TypeError

  • 如果 target 不可扩展,原型参数必须与 Object.getPrototypeOf(target) 的值相同。
js
var handlerReturnsFalse = {
  setPrototypeOf(target, newProto) {
    return false;
  },
};

var newProto = {},
  target = {};

var p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // returns false

1.3 静态方法

Proxy.revocable()

Proxy.revocable() 方法可以用来创建一个可撤销的代理对象。

js
Proxy.revocable(target, handler);

参数:

  • target:将用 Proxy 封装的目标对象。可以是任何类型的对象,包括原生数组,函数,甚至可以是另外一个代理对象。
  • handler:一个对象,其属性是一批可选的函数,这些函数定义了对应的操作被执行时代理的行为。

返回值:返回一个包含了代理对象本身和它的撤销方法的可撤销 Proxy 对象。

TIP

该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke},其中:

  • proxy

    表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。

  • revoke

    撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出 TypeError 异常(注意,可代理操作一共有 14 种,执行这 14 种操作以外的操作不会抛出异常)。一旦被撤销,这个代理对象便不可能被直接恢复到原来的状态,同时和它关联的目标对象以及处理器对象都有可能被垃圾回收掉。再次调用撤销方法 revoke() 则不会有任何效果,但也不会报错。

2.Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

与大多数全局对象不同 Reflect 并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

Reflect 对象提供了以下静态方法,这些方法与 proxy handler 方法的命名相同。

2.1 静态方法

Reflect.apply()

静态方法 Reflect.apply() 通过指定的参数列表发起对目标 (target) 函数的调用。

js
Reflect.apply(target, thisArgument, argumentsList)

参数:

  • target:目标函数。
  • thisArgument:target 函数调用时绑定的 this 对象。
  • argumentsList:target 函数调用时传入的实参列表,该参数应该是一个类数组的对象。

返回值:返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果。

TIP

该方法与 ES5 中Function.prototype.apply()方法类似:调用一个方法并且显式地指定 this 变量和参数列表 (arguments) ,参数列表可以是数组,或类似数组的对象。

js
Function.prototype.apply.call(Math.floor, undefined, [1.75]);

使用 Reflect.apply 方法会使代码更加简洁易懂。

js
Reflect.apply(Math.floor, undefined, [1.75]);
// 1;

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4

Reflect.apply("".charAt, "ponies", [3]);
// "i"

Reflect.construct()

Reflect.construct() 方法的行为有点像 new 操作符 构造函数,相当于运行 new target(...args).

js
Reflect.construct(target, argumentsList[, newTarget])

参数:

  • target:被运行的目标构造函数
  • argumentsList:类数组,目标构造函数调用时的参数。
  • newTarget:作为新创建对象的原型对象的 constructor 属性,参考 new.target 操作符,默认值为 target

返回值:以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。

TIP

Reflect.construct允许你使用可变的参数来调用构造函数,这和使用new 操作符搭配对象展开符调用一样。

js
var obj = new Foo(...args);
var obj = Reflect.construct(Foo, args);
js
var d = Reflect.construct(Date, [1776, 6, 4]);
d instanceof Date; // true
d.getFullYear(); // 1776

INFO

Reflect.construct() vs Object.create()

在新语法 Reflect出现之前,是通过明确指定构造函数和原型对象(使用Object.create())来创建一个对象的。

js
function OneClass() {
  this.name = "one";
}

function OtherClass() {
  this.name = "other";
}

// 创建一个对象:
var obj1 = Reflect.construct(OneClass, args, OtherClass);

// 与上述方法等效:
var obj2 = Object.create(OtherClass.prototype);
OneClass.apply(obj2, args);

console.log(obj1.name); // 'one'
console.log(obj2.name); // 'one'

console.log(obj1 instanceof OneClass); // false
console.log(obj2 instanceof OneClass); // false

console.log(obj1 instanceof OtherClass); // true
console.log(obj2 instanceof OtherClass); // true

虽然两种方式结果相同,但在创建对象过程中仍一点不同。

当使用Object.create()Function.prototype.apply()时,如果不使用new操作符调用构造函数,构造函数内部的new.target值会指向undefined

当调用Reflect.construct()来创建对象,new.target值会自动指定到target(或者 newTarget,前提是 newTarget 指定了)。

js
function OneClass() {
  console.log("OneClass");
  console.log(new.target);
}
function OtherClass() {
  console.log("OtherClass");
  console.log(new.target);
}

var obj1 = Reflect.construct(OneClass, args);
// 输出:
//     OneClass
//     function OneClass { ... }

var obj2 = Reflect.construct(OneClass, args, OtherClass);
// 输出:
//     OneClass
//     function OtherClass { ... }

var obj3 = Object.create(OtherClass.prototype);
OneClass.apply(obj3, args);
// 输出:
//     OneClass
//     undefined

Reflect.defineProperty()

静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。

js
Reflect.defineProperty(target, propertyKey, attributes)

参数:

  • target:目标对象。
  • propertyKey:要定义或修改的属性的名称。
  • attributes:要定义或修改的属性的描述。

返回值:Boolean 值指示了属性是否被成功定义。

TIP

Object.defineProperty 返回一个对象,或者如果属性没有被成功定义,抛出一个 TypeError。相比之下,Reflect.defineProperty 方法只返回一个 Boolean,来说明该属性是否被成功定义。

js
let obj = {}
Reflect.defineProperty(obj, 'x', {value: 7})  // true
obj.x                                         // 7

Reflect.deleteProperty()

静态方法 Reflect.deleteProperty() 允许用于删除属性。它很像 delete 操作符,但它是一个函数。

js
Reflect.deleteProperty(target, propertyKey)

参数:

  • target:删除属性的目标对象。
  • propertyKey:需要删除的属性的名称。

返回值:Boolean 值表明该属性是否被成功删除。

js
var obj = { x: 1, y: 2 };
Reflect.deleteProperty(obj, "x"); // true
obj; // { y: 2 }

var arr = [1, 2, 3, 4, 5];
Reflect.deleteProperty(arr, "3"); // true
arr; // [1, 2, 3, , 5]

// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo"); // true

// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({ foo: 1 }), "foo"); // false

Reflect.get()

Reflect.get() 方法与从 对象 (target[propertyKey]) 中读取属性类似,但它是通过一个函数执行来操作的。

js
Reflect.get(target, propertyKey[, receiver])

参数:

  • target:需要取值的目标对象
  • propertyKey:需要获取的值的键值
  • receiver:如果target对象中指定了getterreceiver则为getter调用时的this值。

返回值:属性的值。

js
// Object
var obj = { x: 1, y: 2 };
Reflect.get(obj, "x"); // 1

// Array
Reflect.get(["zero", "one"], 1); // "one"

// Proxy with a get handler
var x = { p: 1 };
var obj = new Proxy(x, {
  get(t, k, r) {
    return k + "bar";
  },
});
Reflect.get(obj, "foo"); // "foobar"

Reflect.getOwnPropertyDescriptor()

静态方法 Reflect.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符。否则返回 undefined

属性描述符就是Object.defineProperty()的第三个参数中的配置项

js
Reflect.getOwnPropertyDescriptor(target, propertyKey)

参数:

  • target:需要寻找属性的目标对象。
  • propertyKey:获取自己的属性描述符的属性的名称。

返回值:如果属性存在于给定的目标对象中,则返回属性描述符;否则,返回 undefined

js
Reflect.getOwnPropertyDescriptor({ x: "hello" }, "x");
// {value: "hello", writable: true, enumerable: true, configurable: true}

Reflect.getOwnPropertyDescriptor({ x: "hello" }, "y");
// undefined

Reflect.getOwnPropertyDescriptor([], "length");
// {value: 0, writable: true, enumerable: false, configurable: false}

Reflect.getPrototypeof()

静态方法 Reflect.getPrototypeOf()Object.getPrototypeOf() 方法几乎是一样的。都是返回指定对象的原型(即内部的 [[Prototype]] 属性的值)。

js
Reflect.getPrototypeOf(target)

参数:

  • target:获取原型的目标对象。

返回值:给定对象的原型。如果给定对象没有继承的属性,则返回 null

js
Reflect.getPrototypeOf({}); // Object.prototype
Reflect.getPrototypeOf(Object.prototype); // null
Reflect.getPrototypeOf(Object.create(null)); // null

TIP

Object.getPrototypeOf() 比较

js
// 如果参数为 Object,返回结果相同
Object.getPrototypeOf({})   // Object.prototype
Reflect.getPrototypeOf({})  // Object.prototype

// 在 ES5 规范下,对于非 Object,抛异常
Object.getPrototypeOf('foo')   // Throws TypeError
Reflect.getPrototypeOf('foo')  // Throws TypeError

// 在 ES2015 规范下,Reflect 抛异常,Object 强制转换非 Object
Object.getPrototypeOf('foo')   // String.prototype
Reflect.getPrototypeOf('foo')  // Throws TypeError

// 如果想要模拟 Object 在 ES2015 规范下的表现,需要强制类型转换
Reflect.getPrototypeOf(Object('foo'))  // String.prototype

Reflect.has()

静态方法 Reflect.has() 作用与 in 操作符 相同。

js
Reflect.has(target, propertyKey)

参数:

  • target:获取原型的目标对象。
  • propertyKey:属性名,需要检查目标对象是否存在此属性

返回值:一个 Boolean 类型的对象指示是否存在此属性。。

js
Reflect.has({ x: 0 }, "x"); // true
Reflect.has({ x: 0 }, "y"); // false

// 如果该属性存在于原型链中,返回 true
Reflect.has({ x: 0 }, "toString");

// Proxy 对象的 .has() 句柄方法
obj = new Proxy(
  {},
  {
    has(t, k) {
      return k.startsWith("door");
    },
  },
);
Reflect.has(obj, "doorbell"); // true
Reflect.has(obj, "dormitory"); // false

Reflect.isExtensible()

静态方法 Reflect.isExtensible() 判断一个对象是否可扩展(即是否能够添加新的属性)。与它 Object.isExtensible() 方法相似,但有一些不同,详情可见 与 Object.isExtensible() 的不同点

js
Reflect.isExtensible(target)

参数:

  • target:检查是否可扩展的目标对象。

返回值:返回一个 Boolean 值表明该对象是否可扩展。

js
// New objects are extensible.
var empty = {};
Reflect.isExtensible(empty); // === true

// ...but that can be changed.
Reflect.preventExtensions(empty);
Reflect.isExtensible(empty); // === false

// Sealed objects are by definition non-extensible.
var sealed = Object.seal({});
Reflect.isExtensible(sealed); // === false

// Frozen objects are also by definition non-extensible.
var frozen = Object.freeze({});

Reflect.ownKeys()

静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。

js
Reflect.ownKeys(target)

参数:

  • target:获取自身属性键的目标对象。

返回值:由目标对象的自身属性键组成的 Array

js
Reflect.ownKeys({ z: 3, y: 2, x: 1 }); // [ "z", "y", "x" ]
Reflect.ownKeys([]); // ["length"]

var sym = Symbol.for("comet");
var sym2 = Symbol.for("meteor");
var obj = {
  [sym]: 0,
  str: 0,
  773: 0,
  0: 0,
  [sym2]: 0,
  "-1": 0,
  8: 0,
  "second str": 0,
};
Reflect.ownKeys(obj);
// [ "0", "8", "773", "str", "-1", "second str", Symbol(comet), Symbol(meteor) ]
// Indexes in numeric order,
// strings in insertion order,
// symbols in insertion order

Reflect.preventExtensions()

静态方法 Reflect.preventExtensions() 方法阻止新属性添加到对象 (例如:防止将来对对象的扩展被添加到对象中)。该方法与 Object.preventExtensions()相似,但有一些不同点。详情可见 differences

js
Reflect.preventExtensions(target)

参数:

  • target:阻止扩展的目标对象。

返回值:返回一个 Boolean 值表明目标对象是否成功被设置为不可扩展。

js
// Objects are extensible by default.
var empty = {};
Reflect.isExtensible(empty); // === true

// ...but that can be changed.
Reflect.preventExtensions(empty);
Reflect.isExtensible(empty); // === false

TIP

Object.preventExtensions() 的不同点

如果该方法的 target 参数不是一个对象(是原始值),那么将造成一个 TypeError 异常。对于Object.preventExtensions() 方法,非对象的 target 参数将被强制转换为对象。

js
Reflect.preventExtensions(1);
// TypeError: 1 is not an object

Object.preventExtensions(1);
// 1

Reflect.set()

静态方法 Reflect.set() 工作方式就像在一个对象上设置一个属性。

js
Reflect.set(target, propertyKey, value)
Reflect.set(target, propertyKey, value, receiver)

参数:

  • target:设置属性的目标对象。
  • propertyKey:设置的属性的名称。
  • value:设置的值。
  • receiver:如果遇到 setterreceiver则为setter调用时的this值。

返回值:返回一个 Boolean 值表明是否成功设置属性。

js
// Object
var obj = {};
Reflect.set(obj, "prop", "value"); // true
obj.prop; // "value"

// Array
var arr = ["duck", "duck", "duck"];
Reflect.set(arr, 2, "goose"); // true
arr[2]; // "goose"

// It can truncate an array.
Reflect.set(arr, "length", 1); // true
arr; // ["duck"];

// With just one argument, propertyKey and value are "undefined".
var obj = {};
Reflect.set(obj); // true
Reflect.getOwnPropertyDescriptor(obj, "undefined");
// { value: undefined, writable: true, enumerable: true, configurable: true }

Reflect.setPrototypeOf()

除了返回类型以外,静态方法 Reflect.setPrototypeOf()Object.setPrototypeOf() 方法是一样的。它可设置对象的原型(即内部的 [[Prototype]] 属性)为另一个对象或 null,如果操作成功返回 true,否则返回 false

js
Reflect.setPrototypeOf(target, prototype)

参数:

  • target:设置原型的目标对象。
  • prototype:对象的新原型(一个对象或 null)。

返回值:返回一个 Boolean 值表明是否原型已经成功设置。

js
Reflect.setPrototypeOf({}, Object.prototype); // true

// It can change an object's [[Prototype]] to null.
Reflect.setPrototypeOf({}, null); // true

// Returns false if target is not extensible.
Reflect.setPrototypeOf(Object.freeze({}), null); // false

// Returns false if it cause a prototype chain cycle.
var target = {};
var proto = Object.create(target);
Reflect.setPrototypeOf(target, proto); // false

3.Proxy和Reflect 配合使用

首先必须先理解 Proxy 中的 receiverReflect 中的 receiver

3.1 Proxy 中的 receiver

receiver 存在的意义就是为了正确的传递上下文,receiver 不仅仅代表的是 Proxy 代理对象本身,同时也许他会代表继承 Proxy 的那个对象

简单理解

上面我们学习setget的时候,参数receiver我们将它理解成为代理对象本身

js
js代码解读复制代码const obj = {
  name: "obj",
  age: 18,
  running() {
    console.log("running");
  },
};
const proxy = new Proxy(obj, {
  set(target, key, newValue, receicer) {
    target[key] = newValue;
    console.log(receicer == proxy); // true
  },
  get(target, key, receicer) {
    console.log(receicer == proxy); // true
    return target[key];
  },
})
proxy.name == 'proxy'
console.log(proxy.name);

深入理解

receiver 的确是可以表示代理对象,但其实receiver 是执行 getset 操作时的实际调用对象

  • get 捕获器中receiver 是访问属性时的实际调用者对象,如果是通过某个对象(例如子对象)访问代理对象的属性,这个对象就是 receiver
  • set 捕获器中receiver 是设置属性时的对象,如果是在一个继承关系链中通过子对象进行设置操作,receiver 就是子对象
  • 当使用 Object.setPrototypeOf 让一个对象继承另一个代理对象时,receiver 参数可以帮助区分是谁在调用(是原型对象还是继承者对象)

我们看下面例子来理解一下,例子中的receiver参数就是baz对象

js
js代码解读复制代码const obj = {
  name: "obj",
  age: 18,
};
const proxy = new Proxy(obj, {
  set(target, key, newValue, receiver) {
    console.log("set", receiver); // 只是打印 receiver 对象的引用,不涉及属性读取,不会触发get进入循环
      console.log(receiver == proxy); // false
      console.log(receiver == baz); // true
      target[key] = newValue;
    },
    get(target, key, receiver) {
          // console.log("get", receiver); // 相当于在外面写console.log(proxy),因此会进入循环,因为receiver是baz继承自 proxy,可使用Reflect解决
    console.log(receiver == proxy); // false 对象引用的比较不会陷入循环
    console.log(receiver == baz); // true
    return target[key];
  },
});
const baz = {
  name: "baz",
  // __proto__: proxy
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
baz.age = 30; // 执行set
console.log(baz.age); // 30,执行get

3.2 Reflect 中的 receiver

单独使用时

receiver 是在对象访问或修改时,指向当前操作对象的上下文。在某些情况下,它可以控制或影响 this 的指向

js
js代码解读复制代码const obj = {
name: "obj",
age: 18,
set height(value) {
  console.log("set", this); // {}  这里的this就是Reflect.set传入的receiver
},
get height() {
  console.log("get", this); // {name: 'obj', age: 18}  这里的this就是Reflect.get传入的receiver
  },
};
console.log(Reflect.set(obj, "height", "188", {}));
console.log(Reflect.get(obj, "height")); // 不传receiver,就会被设置为target,即obj

结合Proxy

receiver 的作用

  • 对象继承:当对象从另一个对象继承属性时,receiver 可以确保继承者(而非父类对象)是属性访问的上下文
  • 代理拦截:代理对象通过 Reflect 使用 receiver,可以将操作传递到目标对象,同时保留 this 的正确指向

看下面代码我们来一点点理解:

js
js代码解读复制代码const obj = {
  name: "obj",
  age: 18,
  set height(value) {
    console.log("set", this);
  },
  get height() {
    console.log("get", this); // {name: 'obj', age: 18}
    return 170 + this.age;
  },
};
const proxy = new Proxy(obj, {
  set(target, key, newValue, receiver) {
    console.log(receiver); // baz
    Reflect.set(target, key, newValue);
  },
  get(target, key, receiver) {
    console.log(target === obj); // true
    return Reflect.get(target, key);
  },
});
const baz = {
  age: 28,
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
console.log(baz.height); // 188

上面代码分析:

  • 不使用 Reflect.set()Reflect.get()receiver 参数时,默认设 receivertarget(即obj)
  • objgetter 方法取到的 this.ageobj.age 值为 18,因此我们取到的 baz.height 的值是 188 ,但值是错的按理应该是 198

那么我们修改一下代码:只需要把 receiver 传过去改变成正确的 this

js
js代码解读复制代码const obj = {
  name: "obj",
  age: 18,
  set height(value) {
    console.log("set", this);
  },
  get height() {
    console.log("get", this); // baz:{age: 28, height: 198}
    return 170 + this.age;
  },
};
const proxy = new Proxy(obj, {
  set(target, key, newValue, receiver) {
    console.log(receiver);
    Reflect.set(target, key, newValue, receiver);
  },
  get(target, key, receiver) {
    console.log(target === obj); // true
    console.log(receiver === baz); // true
    return Reflect.get(target, key, receiver);
  },
});
const baz = {
  age: 28,
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
console.log(baz.height); // 198

到这里相信都能很清楚的理解 Reflectreceiver 的作用了,简单总结就是它可以修改属性访问中的 this 指向,让其指向传入的 receiver 对象

4.简单实现监听

  • 实现代理对象的目的,不再直接操作原对象
  • Reflect有返回结果值,可以判断操作是否成功
  • Reflect.set/get 最后一个参数 receiver 可以决定对象访问器 setter/getterthis 指向
js
js代码解读复制代码const obj = {
  _name: "obj",
  set name(value) {
    console.log("set", this);
    this._name = value;
  },
  get name() {
    console.log("get", this);
    return this._name;
  },
};
const proxy = new Proxy(obj, {
  set(target, key, newValue, receiver) {
    const isSuccess = Reflect.set(target, key, newValue, receiver);
    if (!isSuccess) throw new Error(`set ${key}值失败`);
  },
  get(target, key, receiver) {
    const success = Reflect.get(target, key, receiver);
    if (!success) throw new Error(`get ${key}值失败`);
    return success;
  },
});
proxy.name = "proxy";
console.log(proxy.name); // proxy

const baz = {
  _name: "baz",
};
// baz 对象的原型变成了 proxy,即 baz 继承了 proxy
Object.setPrototypeOf(baz, proxy);
console.log(baz.name); // baz

MIT Licensed