ES6英华:Proxy & Reflect

导语

本文主要引见了ES6中ProxyReflect的英华学问,并附有适当实例。Proxy意为代办器,经由过程操纵为对象天生的代办器,完成对对象各种操纵的阻拦式编程。Reflect是一个包办越发严厉、健全的操纵对象要领的模块。由于Proxy所能代办的要领和Reflect所包括的要领基础对应,而且在阻拦要领里应当运用对应的Reflect要领返回效果,所以将二者兼并在一起分享。

1 Proxy

1.1 登堂

先想个题目,怎样管控对象某一属性的读取和修正(不触及闭包建立私有属性)?
先建立不该被直接修改的容器属性:_属性名,再设置响应的settergetter函数或建立响应的操纵要领。

--- 设置 setter 和 getter 函数
let obj = {
  _id: undefined,
  get id() {
    return this._id;
  },
  set id(v) {
    this._id = v;
  }
};
obj.id = 3; // 相称:obj._id = 3
console.log(obj.id); // console.log(obj._id);

--- 建立猎取及修正要领
let obj = {
  _id: undefined,
  id() {
    if (!arguments.length) return this._id;
    this._id = arguments[0];
  }
};
obj.id(3); // 相称:obj._id = 3
console.log(obj.id()); // console.log(obj._id);

如许有显著的缺点:要为每一个须要管控的属性举行反复设置,而且现实上容器属性能够被恣意修正。
假如请求晋级,我们须要监听检察、删除、遍历对象属性的操纵,怎么办?ES6之前只能凉拌,ES6以后Proxy代你办。

let obj = { id: 0, name: 'Wmaker' };
let objProxy = new Proxy(obj, {
  get(target, attr) {
    console.log(`Get ${attr}`);
    return target[attr];
  },
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    target[attr] = val;
    return true;
  }
});
objProxy.id; // 打印出:Get id,相称:obj.id;
objProxy.name; // 打印出:Get name,相称:obj.name;

1.2 入室

如前节示例可知,Proxy是天生代办器的组织函数。传入的第一个参数为须要被代办的对象,第二个参数是须要阻拦操纵的设置对象(以后会列出一切可阻拦的操纵及其意义和注重事项)。设置对象中的每一个阻拦操纵,都有默许花样的传入参数,有些也请求有特定的返回值(下面会罗列出某些规律)。

天生的代办器是一个与被代办对象关联的代办实例,能够像操纵平常对象一样看待它。等于说,能够被delete掉某个属性,能够被遍历或猎取原型。一切作用于代办器的操纵,都相称直接作用于被代办对象,还可为其设置阻拦操纵。说的激怒点,苍先生能做好的,我们的小泽先生怎么会差呢?别的可代办的不单单是对象,属于对象的函数、数组都是无条件接收的。

为对象天生代办器以后,依旧能够操纵原对象,但这自然是不发起的。

参数
差别阻拦函数的传入参数不尽雷同,但平常为被代办对象,该操纵须要的参数等和代办器对象。
没必要影象每一个阻拦函数的参数,为脑瓜减减累赘,运用时先打印出arguments检察便会一览无余。

返回值
在阻拦要领里,应只管运用Reflect对应的要领举行操纵,并返回该要领的返回值。一方面是简朴,更主要的是在差别要领或某些环境下,对返回值有硬性的请求,不然直接报错。比方construct()必需返回一个对象,不然报错。再比方set()在严厉形式下,必需返回true或可转化成true的值,不管操纵是不是胜利。

"use strict";

--- 毛病的做法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    return target[attr] = val;
  }
});

objProxy.id = 1; // 操纵胜利。
objProxy.id = 0; // 操纵已胜利,但报错,不再往下实行。

--- 引荐的做法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    return Reflect.set(target, attr, val);
  }
});

objProxy.id = 1; // 操纵胜利。
objProxy.id = 0; // 操纵胜利。

阻拦要领的返回值并不会直接反应到外部,JS会举行某些考证和消除。
比方即便在ownKeys()中返回悉数的值,但现实到外部的只要响应的系列。

两次打印的效果不相等。

let obj = { id: 0, [Symbol.iterator]() {} };
let objProxy = new Proxy(obj, {
  ownKeys(target) {
    let res = Reflect.ownKeys(target);
    console.log('1', res);
    return res;
  }
});

console.log('2', Object.keys(objProxy));

限定性的连续
当被代办对象自身已有某些限定,比方不可扩大或属性不可写不可设置等。对象自身的操纵已受到了限定,这时候假如实行响应的代办操纵,自然会报错。比方当属性不可写时,假如代办并实行了set()操纵,则会直接报错。

let obj = { id: 0 };
Object.defineProperty(obj, 'name', {
  value: 'Wmaker'
});

let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    return Reflect.set(target, attr, val);
  }
});

objProxy.id = 1; // 操纵胜利。
objProxy.name = 'Limo'; // 报错。

this
有些原生对象的内部属性或要领,只要经由过程准确的this才接见,所以没法举行代办。
比方日期对象,new Proxy(new Date(), {}).getDate(),报错提醒:这不是个Date对象。

也有变通的方法,比方关于须要准确 this 的要领能够如许做。

let p = new Proxy(new Date(), {
  get(target, attr) {
    const v = Reflect.get(target, attr);
    return typeof v === 'function'
      ? v.bind(target)
      : v;
  }
});
p.getTime(); // 没有毛病。

处于设置对象中的this直接指向设置对象,不是被代办对象或代办器对象。
处于被代办对象中的this,分为存在于要领和存在于getter/setter中两种。二者猎取this的体式格局差别,我们以实例申明。

--- 例一,没有 set 阻拦操纵。
let obj = {
  get id() {
    console.log('o', this);
  },
  fn() {
    console.log('o', this);
  }
};

let objProxy = new Proxy(obj, {});

objProxy.id; // 打印出 objProxy 。
objProxy.fn(); // 打印出 objProxy 。


--- 例二,有 set 阻拦操纵。现实运用了 target[attr] 猎取属性值。
let obj = {
  get id() {
    console.log('o', this);
  },
  fn() {
    console.log('o', this);
  }
};

let objProxy = new Proxy(obj, {
  get(target, attr) {
    console.log('p', this);
    return target[attr];
  }
});

objProxy.id;
// 打印出设置对象和 obj。
// 由于现实是经由过程被代办对象即 target 接见到 id 值的。

objProxy.fn();
// 打印出设置对象和 objProxy。
// 能够等价的将要领转化成 (objProxy.fn).call(objProxy)。
// 虽然要领也是经由过程 target 接见到的,但关于要领来讲,环境变量一开始就肯定了。

原型为代办器
假如对象的原型为代办器,当操纵举行到原型时,现实是操纵原型的代办器对象。这时候,其行动和平常代办器一致。

let obj = Object.create(new Proxy({}, {
  get(target, attr) {
    console.log('In proxy.');
    return Reflect.get(target, attr);
  }
}));

obj.name; // 打印出 In proxy. 。
// 当在实例上找不到对应属性时,转到了原型上,这时候便被阻拦了。

1.3 代办种别

这里仅仅是罗列出某些重点,细致的请看手册(规范和行动处于更改中)。

get
阻拦属性的读取操纵,包括数组取值。

set
阻拦属性的赋值操纵,包括数组赋值。
严厉形式下,必需返回可转化为true的值。
严厉形式下,假如代办对象有某些限定(属性不可写等),实行响应的阻拦操纵自然会报错。

apply
阻拦函数的挪用、callapply操纵。

has
阻拦推断对象是不是具有某属性。
只对inReflect.has()要领有用,for in属于遍历系列。

construct
阻拦new敕令,必需返回一个对象。

deleteProperty
阻拦delete属性操纵。
严厉形式下,必需返回可转化为true的值。
严厉形式下,假如代办对象有某些限定(属性不可写等),实行响应的阻拦操纵自然会报错。

defineProperty
阻拦Object.defineProperty(),不会阻拦defineProperties
严厉形式下,假如代办对象有某些限定(属性不可设置等),实行响应的阻拦操纵自然会报错。

getOwnPropertyDescriptor
阻拦Object.getOwnPropertyDescriptor(),不会阻拦getOwnPropertyDescriptors
必需返回一个对象或undefined,即返回与原要领雷同的返回值,不然报错。

getPrototypeOf
阻拦猎取对象原型,必需返回一个对象或许null
假如对象不可扩大,必需返回实在的原型对象。
直接接见__proto__,经由过程各种要领等等都邑触发阻拦。

setPrototypeOf
阻拦Object.setPrototypeOf()要领。

isExtensible
阻拦Object.isExtensible()操纵。
返回值必需与目标对象的isExtensible属性保持一致,不然会抛出毛病。

preventExtensions
阻拦Object.preventExtensions(),返回值会自动转成布尔值。

ownKeys
阻拦对象自身属性的遍历操纵。
比方keys(),getOwnPropertyNames(),getOwnPropertySymbols()等等。
返回值必需是数组,项只能是字符串或Symbol,不然报错。
返回值会依据挪用要领的需求举行过滤,比方Object.keys()里不会有symbole
假如目标对象自身包括不可设置的属性,则该属性必需被返回,不然报错。
假如目标对象不可扩大,返回值必需包括原对象的一切属性,且不能包括过剩的属性,不然报错。

1.4 Proxy.revocable

此静态要领也用于天生代办器对象的,但它还会返回一个接纳代办器的要领。
运用场景:不允许直接接见目标对象,必需经由过程代办接见。而且一旦接见完毕,就收回代办权,不允许再次接见。

let {proxy, revoke} = Proxy.revocable(obj, {});
revoke();
此时其内部属性值 [[IsRevoked]] 为 true,不能再操纵代办器,不然报错。

2 Reflect

2.1 作用

终究目标是成为言语内部要领的宿主对象。
比方说defineProperty, getPrototypeOf, preventExtensions都很显著属于内部要领,不该挂在Object下。

供应替换敕令式操纵的响应函数。
运用Reflect.has(obj, attr)替换in操纵
运用Reflect.deleteProperty(obj, attr)替换delete操纵

使函数的行动越发完美和严厉。
在没法定义属性时,Reflect.defineProperty返回false而不是抛出毛病。
在请求范例是对象的参数为非对象时,会直接报错而不是挪用Object()举行转化。

Porxy可阻拦的要领对应,方便在完成自定义行动时,直接挪用以完成默许行动。

2.2 静态要领

这里仅仅是罗列出某些重点,细致的请看手册

Reflect.get
Reflect.get(obj, attr, receiver)
返回属性值,没有则返回undefined
receiver是设置gettersetter里的this指向,默许指向obj

Reflect.set
Reflect.set(obj, attr, value, receiver)
设置属性值,返回布尔值。
注重:当该属性不是getter时,传入receiver同等于设置receiver对象上的属性值。

Reflect.has
Reflect.has(obj, attr)
同等attr in obj

Reflect.deleteProperty
Reflect.deleteProperty(obj, attr)
同等delete obj[attr]

Reflect.construct
Reflect.construct(func, args)
args同等于运用apply要领传入的参数数组。
供应了不运用new来挪用组织函数的要领,同等new func(...args)

Reflect.getPrototypeOf
作用及参数同等Object.getPrototypeOf(obj)

Reflect.setPrototypeOf
作用及参数同等Object.setPrototypeOf(obj, newProto)

Reflect.apply
作用及参数同等Function.prototype.apply.call(func, thisArg, args)

Reflect.defineProperty
作用及参数同等Object.defineProperty(obj, attr, descriptor)

Reflect.getOwnPropertyDescriptor
作用及参数同等Object.getOwnPropertyDescriptor(obj, attr)

Reflect.isExtensible
Reflect.isExtensible(obj)
返回一个布尔值,示意当前对象是不是可扩大。

Reflect.preventExtensions
Reflect.preventExtensions(obj)
设置对象为不可扩大,返回布尔值。

Reflect.ownKeys
Reflect.ownKeys(obj)
返回对象自身的一切属性。
同等于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

    原文作者:wmaker
    原文地址: https://segmentfault.com/a/1190000015581013
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞