前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS 中的 Reflect 和 Proxy

JS 中的 Reflect 和 Proxy

作者头像
羽月
发布2022-10-08 13:51:17
8470
发布2022-10-08 13:51:17
举报
文章被收录于专栏:羽月技术

ProxyReflect是 ES6 新增 API。

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect不是一个函数对象,因此它是不可构造的。Reflect的所有的方法都是静态的就和Math一样,目前它还没有静态属性。

Reflect对象的方法与Proxy对象的方法相同。

Reflect 一共有13个静态方法:

它可以分为一部分是是原来存在Object上的方法,将它转义到了Reflect上,并作了小改动,让方法更加合理。

  1. definePropertyObject.defineProperty类似,但是当对象无法定义时Object.defineProperty会报错而Reflect.defineProperty不会,它会返回false,成功时返回true,如果不是对象还是会报错。
  2. getPrototypeOf(target)Object.getPrototypeOf一样,返回指定对象的原型。
  3. setPrototypeOf(target, prototype)Object.setPrototypeOf一样,它将指定对象的原型设置为另外一个对象。
  4. getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor一样,如果在对象中存在,则返回给定的属性的属性描述符
  5. isExtensible(target)Object.isExtensible类似,判断一个对象是否可扩展(是否可以在它上面添加新的属性),它们的不同点是,当参数不是对象时(原始值),Object的将它强制转变为一个对象,Reflect是直接报错。
  6. preventExtensions(target)Object.preventExtensions类似,阻止新属性添加到对象,不同点和上一条一样。
  7. apply(func, thisArg, args)Function.prototype.apply.call(fn, obj, args)一样。
  8. ownKeys(target)Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))一样,返回一个包含所有自身属性(不包含继承属性)的数组

另一部分是将原来操作符的功能,变成函数行为。

  1. has(target, key)in操作符一样,让判断操作都变成函数行为。
  2. deleteProperty(target, key)delete操作符一样,让删除操作变成函数行为,返回布尔值代表成功或失败。
  3. construct(target, argumentsList[, newTarget])new操作符一样,target构造函数,第二参数是构造函数参数类数组,第三个是new.target的值。
  4. get(target, key[, receiver])obj[key]一样,第三个参数是当要取值的key部署了getter时,访问其函数的this绑定为receiver对象。
  5. set(target, key, value[, receiver]) 设置target对象的key属性等于value,第三个参数和set一样。返回一个布尔值。
代码语言:javascript
复制
// 老写法'assign' in Object // true// 新写法Reflect.has(Object, 'assign') // true// 老写法Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1// 新写法Reflect.apply(Math.floor, undefined, [1.75]) // 1// 旧写法delete myObj.foo;// 新写法Reflect.deleteProperty(myObj, 'foo');// new 的写法const instance = new Greeting('张三');// Reflect.construct 的写法const instance = Reflect.construct(Greeting, ['张三']);// 旧写法Object.defineProperty(MyDate, 'now', {  value: () => Date.now()
});// 新写法Reflect.defineProperty(MyDate, 'now', {  value: () => Date.now()
});Reflect.get(1, 'foo') // 报错Reflect.get(false, 'foo') // 报错Reflect.set(1, 'foo', {}) // 报错Reflect.set(false, 'foo', {}) // 报错// ---------------var myObject = {  foo: 1,  bar: 2,  get baz() {    return this.foo + this.bar;
  },
};var myReceiverObject = {  foo: 4,  bar: 4,
};Reflect.get(myObject, 'baz', myReceiverObject) // 8

Proxy

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等),等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 就像在目标对象之间的一个代理,任何对目标的操作都要经过代理。代理就可以对外界的操作进行过滤和改写。

Proxy是构造函数,它有两个参数targethandler

target是用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler是一个对象,其属性是当执行一个操作时定义代理的行为的函数。

代码语言:javascript
复制
var obj = new Proxy({}, {  get: function (target, key, receiver) {    console.log(`getting ${key}!`);    return Reflect.get(target, key, receiver);
  },  set: function (target, key, value, receiver) {    console.log(`setting ${key}!`);    return Reflect.set(target, key, value, receiver);
  }
});

obj.count = 1//  setting count!++obj.count//  getting count!//  setting count!//  2

Proxy只有一个静态方法revocable(target, handler)可以用来创建一个可撤销的代理对象。两个参数和构造函数的相同。它返回一个包含了所生成的代理对象本身以及该代理对象的撤销方法的对象。

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

代码语言:javascript
复制
var revocable = Proxy.revocable({}, {  get(target, name) {    return "[[" + name + "]]";
  }
});// revocable -> {"proxy": proxy, "revoke": revoke}var proxy = revocable.proxy;
proxy.foo;              // "[[foo]]"revocable.revoke();     // 执行撤销方法proxy.foo;              // TypeErrorproxy.foo = 1           // 同样 TypeErrordelete proxy.foo;       // 还是 TypeErrortypeof proxy            // "object",因为 typeof 不属于可代理操作

handler参数是代理函数对象,它一共支持 13 种拦截函数。和Reflect的相同。如果没有定义某种操作,那么这种操作会被转发到目标对象身上。

代码语言:javascript
复制
const proxy = new Proxy({}, {  get: function(target, property, receiver) {    return receiver;    // receiver 总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。
  }
});
proxy.getReceiver === proxy // true

如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。

代码语言:javascript
复制
const target = Object.defineProperties({}, {  foo: {    value: 123,    writable: false,    configurable: false
  },
});const handler = {  get(target, propKey) {    return 'abc';
  }
};const proxy = new Proxy(target, handler);

proxy.foo// TypeError: Invariant check failed

apply方法拦截函数的调用callapply操作。

代码语言:javascript
复制
var target = function () { return 'I am the target'; };var handler = {  apply: function () {    return 'I am the proxy';
  }
};var p = new Proxy(target, handler);p()// "I am the proxy"

defineProperty方法拦截了Object.defineProperty操作。

代码语言:javascript
复制
var handler = {
  defineProperty (target, key, descriptor) {    return false;
  }
};var target = {};var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不会生效// defineProperty 方法返回 false,导致添加新属性总是无效。

注意,如果目标对象不可扩展(non-extensible),则defineProperty不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty方法不得改变这两个设置。

getPrototypeOf方法主要用来拦截获取对象原型,会以下这些操作:

  1. Object.prototype.__proto__
  2. Object.prototype.isPrototypeOf()
  3. Object.getPrototypeOf()
  4. Reflect.getPrototypeOf()
  5. instanceof

ownKeys方法用来拦截对象自身属性的读取操作,会拦截以下操作:

  1. Object.getOwnPropertyNames()
  2. Object.getOwnPropertySymbols()
  3. Object.keys()
  4. for...in

通过代理,你可以轻松地验证向一个对象的传值。

代码语言:javascript
复制
let validator = {  set: function(obj, prop, value) {    if (prop === 'age') {      if (!Number.isInteger(value)) {        throw new TypeError('The age is not an integer');
      }      if (value > 200) {        throw new RangeError('The age seems invalid');
      }
    }    // The default behavior to store the value
    obj[prop] = value;
  }
};let person = new Proxy({}, validator);

person.age = 100;console.log(person.age); 
// 100person.age = 'young'; 
// 抛出异常: Uncaught TypeError: The age is not an integerperson.age = 300; 
// 抛出异常: Uncaught RangeError: The age seems invalid

this 指向

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

代码语言:javascript
复制
const target = {  m: function () {    console.log(this === proxy);
  }
};const handler = {};const proxy = new Proxy(target, handler);

target.m() // falseproxy.m()  // true
代码语言:javascript
复制
const target = new Date();const handler = {};const proxy = new Proxy(target, handler);

proxy.getDate();// TypeError: this is not a Date object.// getDate 方法只能在Date对象实例上面拿到,// 如果this不是Date对象实例就会报错。// 这时,this绑定原始对象,就可以解决这个问题const target = new Date('2015-01-01');const handler = {  get(target, prop) {    if (prop === 'getDate') {      return target.getDate.bind(target);
    }    return Reflect.get(target, prop);
  }
};const proxy = new Proxy(target, handler);

proxy.getDate() // 1
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 羽月技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Reflect
  • Proxy
    • this 指向
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档