资讯专栏INFORMATION COLUMN

ES6之Proxy & Reflection API

yearsj / 1485人阅读

摘要:的出现,使用内建对象的继承得以实现。属性不存在抛出异常是取值操作,而就是赋值操作,可以对属性值进行验证。属性必须为数字抛出异常接受两个参数被读取属性的原对象,即代理的目标。这个可以拦截内部方法,通过返回数组的值可以覆写其行为。

Proxy & Reflect

extends的出现,使用内建对象的继承得以实现。Proxy可以拦截JS引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数(traps),对于别人封装好的对象或内建对象,都可以自定义操作。
而反射(Reflect)API是以Reflect对象的形式出现的。API中的默认特征与相同的底层操作是一致的,而通过代理可以重写这些操作,每个代理Trap,对应一个命名和参数值都相同的Reflect方法。
Reflect 中所有方法及说明--MDN

</>复制代码

  1. const target = {};
  2. const proxy = new Proxy(target, {});
  3. proxy.title = "proxy";
  4. console.log(proxy.title);
  5. console.log(target.title);
  6. target.title = "target";
  7. console.log(target.title);
  8. console.log(proxy.title);

非常简单的Proxy,没有实现任何自定义的Trap 方法,都是默认实现。

GET Trap

</>复制代码

  1. let handler = {
  2. get: function(target, name){
  3. return name in target ? target[name] : 37;
  4. }
  5. };
  6. let p = new Proxy({}, handler);
  7. p.a = 1;
  8. p.b = undefined;
  9. console.log(p.a, p.b); // 1, undefined
  10. console.log("c" in p, p.c); // false, 37

来自于MDN的一个小例子,语法极其简单。get trap function 有3个参数:

TrapTarget 被读取属性的原对象,即代理的目标。

key 要读取的属性(字符串或Symbol)

receiver 发生操作的对象,通常为代理。

而get trap 能做什么用呢?或者说,我们在什么时候使用get呢?一个最主要的功能,验证对象结构

</>复制代码

  1. 对象结构:对象中所有可用的属性和方法集合。JS引擎通过对象结构来优化代码,通常会创建类来表示对象。

</>复制代码

  1. let proxy = new Proxy({}, {
  2. get(target, key, receiver) {
  3. if (!(key in receiver )) {
  4. throw new TypeError(`属性 ${key} 不存在`);
  5. }
  6. return Reflect.get(target, key, receiver);
  7. }
  8. });
  9. proxy.name = "proxy";
  10. console.log(proxy.name); // proxy
  11. console.log(proxy.age); // 抛出 TypeError 异常
SET Trap

get是取值操作,而set就是赋值操作,可以对属性值进行验证。

</>复制代码

  1. let target = {
  2. name: "target"
  3. }
  4. let proxy = new Proxy(target, {
  5. set(target, key, value, receiver) {
  6. if (!(key in receiver )) {
  7. if (isNaN(value)) {
  8. throw new TypeError("属性必须为数字");
  9. }
  10. }
  11. return Reflect.set(target, key, value, receiver);
  12. }
  13. });
  14. proxy.count = 10;
  15. console.log(proxy.count); // 10
  16. console.log(proxy.count); // 10
  17. proxy.name = "tom";
  18. console.log(proxy.name); // tom
  19. console.log(target.name); // tom
  20. proxy.secondName = "Lee"; // 抛出 TypeError 异常
Has Trap

has trap function 接受两个参数

TrapTarget 被读取属性的原对象,即代理的目标。

key 要读取的属性(字符串或Symbol)

可以用来隐藏已有属性。

</>复制代码

  1. // has trap
  2. let target = {
  3. name: "target",
  4. age: 24
  5. }
  6. let proxy = new Proxy(target, {
  7. has(target, key) {
  8. if (key === "age"){
  9. return false;
  10. } else {
  11. return Reflect.has(target, key);
  12. }
  13. }
  14. });
  15. console.log("age" in proxy); // false
  16. console.log("age" in target); // true
  17. console.log("name" in proxy); // true
  18. console.log("name" in target); // true
deleteProperty Trap

delete操作可以从对象中移除属性,如果成功则返回true, 失败返回 false 。在严格模式下,删除一个不可配置属性,则会抛出错误,而在非严格模式下,只是返回false.

</>复制代码

  1. let target = {
  2. name: "target",
  3. age: 24
  4. }
  5. let proxy = new Proxy(target, {
  6. deleteProperty(target, key) {
  7. if (key === "age"){
  8. return false;
  9. } else {
  10. return Reflect.deleteProperty(target, key);
  11. }
  12. }
  13. });
  14. console.log( "age" in proxy);
  15. const r1 = delete proxy.age;
  16. console.log(r1);
  17. console.log( "name" in proxy);
  18. const r2 = delete proxy.name;
  19. console.log(r2);
原型 Trap

这个要了解一点:Object.get/setPrototypeOf 与 Reflect.get/setPrototypeOf 的区别。
从功能上来讲,两个操作是一致的。但从实现上来讲,Object.get/setPrototypeOf 是高级方法,创建伊始就给开发者使用,而Reflect.get/setPrototypeOf 是更底层的操作,使开发者可以访问之前只在内部操作的[[Get/SetPrototypeOf]]。
面对两套方法选择时,可能会让人无所适从,不知道选哪个更好。有一点可以确认的是,Object提供的方法更高级,是对Reflect方法的包裹器,最终还是会调用Reflect方法,但在此之前,会执行一些额外的步骤,并通过检查返回值,来确定下一步怎么操作。

可扩展 Trap

对象可扩展,Object.isExtensible和Object.preventExtesions 方法,在ES5下已经实现,而ES6中通过 Reflect.isExtensible和Reflect.preventExtensions来实现。

</>复制代码

  1. var p = new Proxy({}, {
  2. isExtensible: function(target) {
  3. console.log("called");
  4. return true;//也可以return 1;等表示为true的值
  5. }
  6. });
  7. console.log(Object.isExtensible(p)); // "called"
  8. // true
  9. var p = new Proxy({}, {
  10. preventExtensions: function(target) {
  11. console.log("called");
  12. Object.preventExtensions(target);
  13. return true;
  14. }
  15. });
  16. console.log(Object.preventExtensions(p)); // "called"
  17. // false
属性描述符

在代理中可以分别使用defineProperty陷阱和getOwnPropertyDescriptor陷阱拦截Object.defineProperty方法和Object.getOwnPropertyDescriptor方法的调用。

</>复制代码

  1. defineProperty
    Object.definePropertyReflect.defineProperty方法返回值不同,前一个返回第一个参数,后一方法,则返回操作的结果是否成功。

</>复制代码

  1. let target = {};
  2. let r1 = Object.defineProperty(target, "name", { value: "target" });
  3. console.log(r1 === target); // true
  4. let r2 = Reflect.defineProperty(target, "name", { value: "target"});
  5. console.log(r2); // true

</>复制代码

  1. getOwnPropertyDescriptor
    Object.getOwnPropertyDescriptor 如果原始值被传入第一个参数,内部将会对这个值进行强制转换,转成Object对象,而Reflect.getOwnPropertyDescriptor方法,则会报出一个错误。
ownKeys

这个trap可以拦截内部[[OwnPropertyKeys]]方法,通过返回数组的值可以覆写其行为。返回的数组被用于Object.keys(),Object.getOwnPropertyNames(),Object.getOwnPropertySymbols()和Object.assign()。

</>复制代码

  1. let proxy = new Proxy({}, {
  2. ownKeys(target) {
  3. return Reflect.ownKeys(target).filter(key => {
  4. // console.log(key);
  5. // return true; // 可以和下面的返回做个对比,看看输出有什么不同
  6. return typeof key !== "string" || key[0] !== "_";
  7. })
  8. }
  9. });
  10. let nameSymbol = Symbol("name");
  11. proxy.name = "Tom";
  12. proxy._name = "Jerry";
  13. proxy[nameSymbol] = "Tom & Jerry";
  14. const names = Object.getOwnPropertyNames(proxy),
  15. keys = Object.keys(proxy),
  16. symbols = Object.getOwnPropertySymbols(proxy);
  17. console.log(names.length);
  18. console.log(names);
  19. console.log(keys.length);
  20. console.log(keys);
  21. console.log(symbols.length);
  22. console.log(symbols);
apply & construct

MDN apply
MDN construct
这两个代理的目标,都是函数。

</>复制代码

  1. const target = function() {
  2. return 42;
  3. }
  4. const proxy = new Proxy(target, {
  5. apply: function(tt, ta, args) {
  6. return Reflect.apply(tt, ta, args);
  7. },
  8. constructor(tt, ta, args) {
  9. return Reflect.constructor(tt, ta, args);
  10. }
  11. });
  12. console.log(typeof proxy);
  13. console.log(proxy());
  14. const instance = new proxy();
  15. console.log(instance instanceof proxy);
  16. console.log(instance instanceof target);

所以,这两个trap,通常可以用来

验证函数参数

不用 new 调用构造函数

覆写抽象基类构造函数

可调用类构造函数

例子就不一一写了,知道用法,很方便就能实现。

可撤销代理

方法 Proxy.revocable() 创建一个可撤销的 Proxy 对象.
MDN revocable

</>复制代码

  1. var revocable = Proxy.revocable({}, {
  2. get: function(target, name) {
  3. return "[[" + name + "]]";
  4. }
  5. });
  6. var proxy = revocable.proxy;
  7. console.log(proxy.foo); // "[[foo]]"
  8. revocable.revoke();
  9. console.log(proxy.foo); // TypeError is thrown
  10. proxy.foo = 1 // TypeError again
  11. delete proxy.foo; // still TypeError
  12. typeof proxy // "object", typeof doesn"t trigger any trap
在原型上进行Proxy应用

如果把代理当原型,将发生什么事呢?在JS中,一旦涉及到原型,事情往往就没那么简单了。如果原型是代理,而代理是透明的,仅当默认操作执行到原型上时,都会调用代理trap,这大大限制代理的能力。

</>复制代码

  1. const target = {};
  2. const newTarget = Object.create(new Proxy(target, {
  3. defineProperty(trapTarget, name, desc) {
  4. return false;
  5. }
  6. }));
  7. // 按原型执行顺序,这个方法可以被调用到吗?
  8. Object.defineProperty(newTarget, "name", { value: "newTarget" });
  9. console.log(newTarget.name);
  10. console.log(newTarget.hasOwnProperty("name"));
  11. // 可以看出,defineProperty方法,没有得到调用

在Object.create中,可以用以下几个trap

get

set

has

将代理用途类的原型

先看一个例子

</>复制代码

  1. function NoSuchProperty() {}
  2. NoSuchProperty.prototype = new Proxy({}, {
  3. get(tt, key, receiver) {
  4. throw new ReferenceError(`${key} doesn"t exist `);
  5. }
  6. });
  7. class Square extends NoSuchProperty {
  8. constructor(length, width) {
  9. super();
  10. this.length = length;
  11. this.width = width;
  12. }
  13. }
  14. let shape = new Square(2, 6);
  15. const a1 = shape.length * shape.width;
  16. console.log(a1); // 12
  17. let a2 = shape.length * shape.wdth; // 抛出异常

说明:wdth是个拼写错误,但按JS原型机制,shape中没有时,会去原型中查找,所以就会抛出异常。虽然代理不是shape的直接原型,存在于shape对象的原型链中。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106120.html

相关文章

  • 《深入理解ES6》笔记——代理(Proxy)和反射(ReflectionAPI(12)

    摘要:方法与代理处理程序的方法相同。使用给目标函数传入指定的参数。当然,不用反射也可以读取的值。的例子我们可以理解成是拦截了方法,然后传入参数,将返回值赋值给,这样我们就能在需要读取这个返回值的时候调用。这种代理模式和的代理有异曲同工之妙。 反射 Reflect 当你见到一个新的API,不明白的时候,就在浏览器打印出来看看它的样子。 showImg(https://segmentfault....

    ZHAO_ 评论0 收藏0
  • 《深入理解ES6》笔记——代理(Proxy)和反射(ReflectionAPI(12)

    摘要:方法与代理处理程序的方法相同。使用给目标函数传入指定的参数。当然,不用反射也可以读取的值。的例子我们可以理解成是拦截了方法,然后传入参数,将返回值赋值给,这样我们就能在需要读取这个返回值的时候调用。这种代理模式和的代理有异曲同工之妙。 反射 Reflect 当你见到一个新的API,不明白的时候,就在浏览器打印出来看看它的样子。 showImg(https://segmentfault....

    shiina 评论0 收藏0
  • 深入理解ES6笔记(十一)代理(Proxy)和反射(ReflectionAPI(12)

    摘要:是陷阱函数对应的反射方法,同时也是操作的默认行为。对象外形指的是对象已有的属性与方法的集合,由于该属性验证只须在读取属性时被触发,因此只要使用陷阱函数。无论该属性是对象自身的属性还是其原型的属性。 主要知识点:代理和反射的定义、常用的陷阱函数、可被撤销的代理、将代理对象作为原型使用、将代理作为类的原型showImg(https://segmentfault.com/img/bVbfWr...

    explorer_ddf 评论0 收藏0
  • ES6中的代理(Proxy)和反射(Reflection

    摘要:代理和反射的定义调用可常见代替其它目标对象的代理,它虚拟化了目标,所以二者看起来功能一致。代理可拦截引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数。 代理和反射的定义 调用 new Proxy() 可常见代替其它目标 (target) 对象的代理,它虚拟化了目标,所以二者看起来功能一致。 代理可拦截JS引擎内部目标的底层对象操作,这些底层操作被拦截后会触发...

    Markxu 评论0 收藏0

发表评论

0条评论

yearsj

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<