<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          快速掌握ES6的Proxy、Reflect

          共 11302字,需瀏覽 23分鐘

           ·

          2021-05-16 01:43

          來(lái)源 | https://segmentfault.com/a/1190000039956559


          前言

          ES6新增的代理和反射為開(kāi)發(fā)者提供了攔截并向基本操作嵌入額外行為的能力。
          具體地說(shuō),可以給目標(biāo)對(duì)象定義一個(gè)關(guān)聯(lián)的代理對(duì)象,而這個(gè)代理對(duì)象可以作為抽象的目標(biāo)對(duì)象來(lái)使用。
          在對(duì)目標(biāo)對(duì)象的各種操作影響目標(biāo)對(duì)象之前,可以在代理對(duì)象中對(duì)這些操作加以控制。

          Proxy (代理)

          代理是使用 Proxy 構(gòu)造函數(shù)創(chuàng)建的。這個(gè)構(gòu)造函數(shù)接收兩個(gè)參數(shù):目標(biāo)對(duì)象和處理程序?qū)ο蟆H鄙倨渲腥魏我粋€(gè)參數(shù)都會(huì)拋出 TypeError。

          創(chuàng)建空代理

          如下面的代碼所示,在代理對(duì)象上執(zhí)行的任何操作實(shí)際上都會(huì)應(yīng)用到目標(biāo)對(duì)象。唯一可感知的不同
          就是代碼中操作的是代理對(duì)象。
          const target = {  id: 'target' }; const handler = {}; const proxy = new Proxy(target, handler); // id 屬性會(huì)訪問(wèn)同一個(gè)值console.log(target.id); // target console.log(proxy.id); // target
          // 給目標(biāo)屬性賦值會(huì)反映在兩個(gè)對(duì)象上// 因?yàn)閮蓚€(gè)對(duì)象訪問(wèn)的是同一個(gè)值target.id = 'foo'; console.log(target.id); // foo console.log(proxy.id); // foo
          // 給代理屬性賦值會(huì)反映在兩個(gè)對(duì)象上// 因?yàn)檫@個(gè)賦值會(huì)轉(zhuǎn)移到目標(biāo)對(duì)象proxy.id = 'bar'; console.log(target.id); // bar console.log(proxy.id); // bar

          定義捕獲器

          捕獲器可以理解為處理程序?qū)ο笾卸x的用來(lái)直接或間接在代理對(duì)象上使用的一種“攔截器”,每次在代理對(duì)象上調(diào)用這些基本操作時(shí),代理可以在這些操作傳播到目標(biāo)對(duì)象之前先調(diào)用捕獲器函數(shù),從而攔截并修改相應(yīng)的行為。

          const target = {  foo: 'bar' };const handler = {  // 捕獲器在處理程序?qū)ο笾幸苑椒麨殒I get() {  return 'handler override';  } };const proxy = new Proxy(target, handler); console.log(target.foo); // bar console.log(proxy.foo); // handler override

          get() 捕獲器會(huì)接收到目標(biāo)對(duì)象,要查詢的屬性和代理對(duì)象三個(gè)參數(shù)。我們可以對(duì)上述代碼進(jìn)行如下改造。

          const target = {  foo: 'bar' };const handler = {  // 捕獲器在處理程序?qū)ο笾幸苑椒麨殒I get(trapTarget, property, receiver) {  console.log(trapTarget === target);  console.log(property);  console.log(receiver === proxy);  return trapTarget[property] } };const proxy = new Proxy(target, handler); proxy.foo; // true // foo // trueconsole.log(proxy.foo); // bar console.log(target.foo); // bar

          處理程序?qū)ο笾兴锌梢圆东@的方法都有對(duì)應(yīng)的反射(Reflect)API 方法。這些方法與捕獲器攔截的方法具有相同的名稱和函數(shù)簽名,而且也具有與被攔截方法相同的行為。因此,使用反射 API 也可以像下面這樣定義出空代理對(duì)象:

          const target = {  foo: 'bar' }; const handler = {  get() {      // 第一種寫(xiě)法     return Reflect.get(...arguments);      // 第二種寫(xiě)法     return Reflect.get } }; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar console.log(target.foo); // bar

          我們也可以以此,來(lái)對(duì)將要訪問(wèn)的屬性的返回值進(jìn)行修飾。

          const target = {  foo: 'bar',  baz: 'qux' }; const handler = {  get(trapTarget, property, receiver) {  let decoration = '';  if (property === 'foo') {  decoration = ' I love you';  }  return Reflect.get(...arguments) + decoration;  } }; const proxy = new Proxy(target, handler); console.log(proxy.foo); // bar I love you console.log(target.foo); // bar console.log(proxy.baz); // qux console.log(target.baz); // qux

          可撤銷代理

          有時(shí)候可能需要中斷代理對(duì)象與目標(biāo)對(duì)象之間的聯(lián)系。對(duì)于使用 new Proxy()創(chuàng)建的普通代理來(lái)說(shuō),這種聯(lián)系會(huì)在代理對(duì)象的生命周期內(nèi)一直持續(xù)存在。

          Proxy 也暴露了 revocable()方法,這個(gè)方法支持撤銷代理對(duì)象與目標(biāo)對(duì)象的關(guān)聯(lián)。撤銷代理的操作是不可逆的。而且,撤銷函數(shù)(revoke())是冪等的,調(diào)用多少次的結(jié)果都一樣。撤銷代理之后再調(diào)用代理會(huì)拋出 TypeError。

          const target = {  foo: 'bar' }; const handler = {  get() {  return 'intercepted';  } }; const { proxy, revoke } = Proxy.revocable(target, handler); console.log(proxy.foo); // intercepted console.log(target.foo); // bar revoke(); console.log(proxy.foo); // TypeError

          代理另一個(gè)代理

          代理可以攔截反射 API 的操作,而這意味著完全可以創(chuàng)建一個(gè)代理,通過(guò)它去代理另一個(gè)代理。這樣就可以在一個(gè)目標(biāo)對(duì)象之上構(gòu)建多層攔截網(wǎng):

          const target = {  foo: 'bar' }; const firstProxy = new Proxy(target, {  get() {  console.log('first proxy');  return Reflect.get(...arguments);  } }); const secondProxy = new Proxy(firstProxy, {  get() {  console.log('second proxy');  return Reflect.get(...arguments);  } }); console.log(secondProxy.foo); // second proxy // first proxy // bar

          代理的問(wèn)題與不足

          1、代理中的this

          const target = {  thisValEqualsProxy() {  return this === proxy;  } } const proxy = new Proxy(target, {}); console.log(target.thisValEqualsProxy()); // false console.log(proxy.thisValEqualsProxy()); // true

          這樣看起來(lái)并沒(méi)有什么問(wèn)題,this指向調(diào)用者。但是如果目標(biāo)對(duì)象依賴于對(duì)象標(biāo)識(shí),那就可能碰到意料之外的問(wèn)題。

          const wm = new WeakMap(); class User {  constructor(userId) {      wm.set(this, userId);  }  set id(userId) {      wm.set(this, userId);  }  get id() {      return wm.get(this);  } }const user = new User(123); console.log(user.id); // 123 const userInstanceProxy = new Proxy(user, {}); console.log(userInstanceProxy.id); // undefined

          這是因?yàn)?User 實(shí)例一開(kāi)始使用目標(biāo)對(duì)象作為 WeakMap 的鍵,代理對(duì)象卻嘗試從自身取得這個(gè)實(shí)例。

          要解決這個(gè)問(wèn)題,就需要重新配置代理,把代理 User 實(shí)例改為代理 User 類本身。之后再創(chuàng)建代
          理的實(shí)例就會(huì)以代理實(shí)例
          作為 WeakMap 的鍵了:

          const UserClassProxy = new Proxy(User, {}); const proxyUser = new UserClassProxy(456); console.log(proxyUser.id);

          2、代理與內(nèi)部槽位

          在代理Date類型時(shí):根據(jù) ECMAScript 規(guī)范,Date 類型方法的執(zhí)行依賴 this 值上的內(nèi)部槽位[[NumberDate]]。代理對(duì)象上不存在這個(gè)內(nèi)部槽位,而且這個(gè)內(nèi)部槽位的值也不能通過(guò)普通的 get()和 set()操作訪問(wèn)到,于是代理攔截后本應(yīng)轉(zhuǎn)發(fā)給目標(biāo)對(duì)象的方法會(huì)拋出 TypeError:

          const target = new Date(); const proxy = new Proxy(target, {}); console.log(proxy instanceof Date); // true proxy.getDate(); // TypeError: 'this' is not a Date object

          Reflect(反射)

          Reflect對(duì)象與Proxy對(duì)象一樣,也是 ES6 為了操作對(duì)象而提供的新 API。Reflect的設(shè)計(jì)目的:

          1. 將Object對(duì)象的一些明顯屬于語(yǔ)言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對(duì)象上。

          2. 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無(wú)法定義屬性時(shí),會(huì)拋出一個(gè)錯(cuò)誤,而Reflect.defineProperty(obj, name, desc)則會(huì)返回false。

          3. 讓Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。

          4. Reflect對(duì)象的方法與Proxy對(duì)象的方法一一對(duì)應(yīng),只要是Proxy對(duì)象的方法,就能在Reflect對(duì)象上找到對(duì)應(yīng)的方法。這就讓Proxy對(duì)象可以方便地調(diào)用對(duì)應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說(shuō),不管Proxy怎么修改默認(rèn)行為,你總可以在Reflect上獲取默認(rèn)行為。

          代理與反射API

          get()

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          • property:引用的目標(biāo)對(duì)象上的字符串鍵屬性。

          • receiver:代理對(duì)象或繼承代理對(duì)象的對(duì)象。
            返回:

          • 返回值無(wú)限制
            get()捕獲器會(huì)在獲取屬性值的操作中被調(diào)用。對(duì)應(yīng)的反射 API 方法為 Reflect.get()。

          const myTarget = {}; const proxy = new Proxy(myTarget, {  get(target, property, receiver) {  console.log('get()');  return Reflect.get(...arguments)  } }); proxy.foo; // get()

          set()

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          • property:引用的目標(biāo)對(duì)象上的字符串鍵屬性。

          • value:要賦給屬性的值。

          • receiver:接收最初賦值的對(duì)象。
            返回:

          • 返回 true 表示成功;返回 false 表示失敗,嚴(yán)格模式下會(huì)拋出 TypeError。

          set()捕獲器會(huì)在設(shè)置屬性值的操作中被調(diào)用。對(duì)應(yīng)的反射 API 方法為 Reflect.set()。

          const myTarget = {}; const proxy = new Proxy(myTarget, {  set(target, property, value, receiver) {  console.log('set()');  return Reflect.set(...arguments)  } }); proxy.foo = 'bar'; // set()

          has()

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          • property:引用的目標(biāo)對(duì)象上的字符串鍵屬性。

          返回:

          • has()必須返回布爾值,表示屬性是否存在。返回非布爾值會(huì)被轉(zhuǎn)型為布爾值。

          has()捕獲器會(huì)在 in 操作符中被調(diào)用。對(duì)應(yīng)的反射 API 方法為 Reflect.has()。

          const myTarget = {}; const proxy = new Proxy(myTarget, {  has(target, property) {  console.log('has()');  return Reflect.has(...arguments)  } }); 'foo' in proxy; // has()

          defineProperty()

          Reflect.defineProperty方法基本等同于Object.defineProperty,用來(lái)為對(duì)象定義屬性。

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          • property:引用的目標(biāo)對(duì)象上的字符串鍵屬性。

          • descriptor:包含可選的 enumerable、configurable、writable、value、get 和 set定義的對(duì)象。

          返回:

          • defineProperty()必須返回布爾值,表示屬性是否成功定義。返回非布爾值會(huì)被轉(zhuǎn)型為布爾值。

          const myTarget = {}; const proxy = new Proxy(myTarget, {  defineProperty(target, property, descriptor) {  console.log('defineProperty()');  return Reflect.defineProperty(...arguments)  } }); Object.defineProperty(proxy, 'foo', { value: 'bar' }); // defineProperty()

          getOwnPropertyDescriptor()

          Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定屬性的描述對(duì)象。

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          • property:引用的目標(biāo)對(duì)象上的字符串鍵屬性。

          返回:

          • getOwnPropertyDescriptor()必須返回對(duì)象,或者在屬性不存在時(shí)返回 undefined。

          const myTarget = {}; const proxy = new Proxy(myTarget, {  getOwnPropertyDescriptor(target, property) {  console.log('getOwnPropertyDescriptor()');  return Reflect.getOwnPropertyDescriptor(...arguments)  } }); Object.getOwnPropertyDescriptor(proxy, 'foo'); // getOwnPropertyDescriptor()

          deleteProperty()

          Reflect.deleteProperty方法等同于delete obj[name],用于刪除對(duì)象的屬性。

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          • property:引用的目標(biāo)對(duì)象上的字符串鍵屬性。

          返回:

          • deleteProperty()必須返回布爾值,表示刪除屬性是否成功。返回非布爾值會(huì)被轉(zhuǎn)型為布爾值。

          ownKeys()

          Reflect.ownKeys方法用于返回對(duì)象的所有屬性,基本等同于Object.getOwnPropertyNames與Object.getOwnPropertySymbols之和。

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          返回:

          • ownKeys()必須返回包含字符串或符號(hào)的可枚舉對(duì)象。

          getPrototypeOf()

          Reflect.getPrototypeOf方法用于讀取對(duì)象的__proto__屬性

          接收參數(shù):

          • target:目標(biāo)對(duì)象。

          返回:

          • getPrototypeOf()必須返回對(duì)象或 null。

          等等。。

          代理模式

          跟蹤屬性訪問(wèn)

          通過(guò)捕獲 get、set 和 has 等操作,可以知道對(duì)象屬性什么時(shí)候被訪問(wèn)、被查詢。把實(shí)現(xiàn)相應(yīng)捕獲器的某個(gè)對(duì)象代理放到應(yīng)用中,可以監(jiān)控這個(gè)對(duì)象何時(shí)在何處被訪問(wèn)過(guò):

          const user = {  name: 'Jake' }; const proxy = new Proxy(user, {  get(target, property, receiver) {  console.log(`Getting ${property}`);  return Reflect.get(...arguments);  },  set(target, property, value, receiver) {  console.log(`Setting ${property}=${value}`);  return Reflect.set(...arguments);  } }); proxy.name; // Getting name proxy.age = 27; // Setting age=27

          隱藏屬性

          代理的內(nèi)部實(shí)現(xiàn)對(duì)外部代碼是不可見(jiàn)的,因此要隱藏目標(biāo)對(duì)象上的屬性也輕而易舉。

          const hiddenProperties = ['foo', 'bar']; const targetObject = {  foo: 1,  bar: 2,  baz: 3 }; const proxy = new Proxy(targetObject, {  get(target, property) {  if (hiddenProperties.includes(property)) {  return undefined;  } else {  return Reflect.get(...arguments);  }  },  has(target, property) {  if (hiddenProperties.includes(property)) {  return false;  } else {  return Reflect.has(...arguments);  }  } }); // get() console.log(proxy.foo); // undefined console.log(proxy.bar); // undefined console.log(proxy.baz); // 3 // has() console.log('foo' in proxy); // false console.log('bar' in proxy); // false console.log('baz' in proxy); // true

          屬性驗(yàn)證

          因?yàn)樗匈x值操作都會(huì)觸發(fā) set()捕獲器,所以可以根據(jù)所賦的值決定是允許還是拒絕賦值:

          const target = {  onlyNumbersGoHere: 0 }; const proxy = new Proxy(target, {  set(target, property, value) {  if (typeof value !== 'number') {  return false;  } else {  return Reflect.set(...arguments);  }  } }); proxy.onlyNumbersGoHere = 1; console.log(proxy.onlyNumbersGoHere); // 1 proxy.onlyNumbersGoHere = '2'; console.log(proxy.onlyNumbersGoHere); // 1

          函數(shù)與構(gòu)造函數(shù)參數(shù)驗(yàn)證

          跟保護(hù)和驗(yàn)證對(duì)象屬性類似,也可對(duì)函數(shù)和構(gòu)造函數(shù)參數(shù)進(jìn)行審查。比如,可以讓函數(shù)只接收某種類型的值:

          function median(...nums) {      return nums.sort()[Math.floor(nums.length / 2)]; } const proxy = new Proxy(median, {      apply(target, thisArg, argumentsList) {          for (const arg of argumentsList) {              if (typeof arg !== 'number') {                  throw 'Non-number argument provided';              }          }  return Reflect.apply(...arguments);  } }); console.log(proxy(4, 7, 1)); // 4 console.log(proxy(4, '7', 1)); // Error: Non-number argument provided 類似地,可以要求實(shí)例化時(shí)必須給構(gòu)造函數(shù)傳參:class User {  constructor(id) {      this.id_ = id;  } } const proxy = new Proxy(User, {  construct(target, argumentsList, newTarget) {      if (argumentsList[0] === undefined) {          throw 'User cannot be instantiated without id';      } else {          return Reflect.construct(...arguments);      }  } }); new proxy(1); new proxy(); // Error: User cannot be instantiated without id

          數(shù)據(jù)綁定與可觀察對(duì)象

          通過(guò)代理可以把運(yùn)行時(shí)中原本不相關(guān)的部分聯(lián)系到一起。這樣就可以實(shí)現(xiàn)各種模式,從而讓不同的代碼互操作。

          比如,可以將被代理的類綁定到一個(gè)全局實(shí)例集合,讓所有創(chuàng)建的實(shí)例都被添加到這個(gè)集合中:

          const userList = []; class User {  constructor(name) {  this.name_ = name;  } } const proxy = new Proxy(User, {  construct() {  const newUser = Reflect.construct(...arguments);  userList.push(newUser);  return newUser;  } }); new proxy('John'); new proxy('Jacob'); new proxy('Jingleheimerschmidt'); console.log(userList); // [User {}, User {}, User{}]

          另外,還可以把集合綁定到一個(gè)事件分派程序,每次插入新實(shí)例時(shí)都會(huì)發(fā)送消息:

          const userList = []; function emit(newValue) {  console.log(newValue); } const proxy = new Proxy(userList, {  set(target, property, value, receiver) {  const result = Reflect.set(...arguments);  if (result) {  emit(Reflect.get(target, property, receiver));  }  return result;  } }); proxy.push('John'); // John proxy.push('Jacob'); // Jacob

          使用 Proxy 實(shí)現(xiàn)觀察者模式

          const queuedObservers = new Set();
          const observe = fn => queuedObservers.add(fn);const observable = obj => new Proxy(obj, {set});
          function set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); queuedObservers.forEach(observer => observer()); return result;}
          const person = observable({ name: '張三', age: 20});
          function print() { console.log(`${person.name}, ${person.age}`)}
          observe(print);person.name = '李四';// 輸出// 李四, 20

          結(jié)尾

          本文主要參考阮一峰e(cuò)s6教程、js紅寶書(shū)第四版。


          學(xué)習(xí)更多技能

          請(qǐng)點(diǎn)擊下方公眾號(hào)


          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线视频中文字幕一区 | 操逼一级好看毛片 | 天天色天天射天天干 | 操逼av在线 | 骚逼逼影院 |