<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

          共 11292字,需瀏覽 23分鐘

           ·

          2021-07-14 14:38

          來源 | https://segmentfault.com/a/1190000039956559


          前言

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

          Proxy (代理)

          代理是使用 Proxy 構(gòu)造函數(shù)創(chuàng)建的。這個構(gòu)造函數(shù)接收兩個參數(shù):目標對象和處理程序?qū)ο?。缺少其中任何一個參數(shù)都會拋出 TypeError。

          創(chuàng)建空代理

          如下面的代碼所示,在代理對象上執(zhí)行的任何操作實際上都會應用到目標對象。唯一可感知的不同
          就是代碼中操作的是代理對象。
          const target = {  id: 'target' }; const handler = {}; const proxy = new Proxy(target, handler); // id 屬性會訪問同一個值console.log(target.id); // target console.log(proxy.id); // target
          // 給目標屬性賦值會反映在兩個對象上// 因為兩個對象訪問的是同一個值target.id = 'foo'; console.log(target.id); // foo console.log(proxy.id); // foo
          // 給代理屬性賦值會反映在兩個對象上// 因為這個賦值會轉(zhuǎn)移到目標對象proxy.id = 'bar'; console.log(target.id); // bar console.log(proxy.id); // bar

          定義捕獲器

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

          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() 捕獲器會接收到目標對象,要查詢的屬性和代理對象三個參數(shù)。我們可以對上述代碼進行如下改造。

          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ū)ο笾兴锌梢圆东@的方法都有對應的反射(Reflect)API 方法。這些方法與捕獲器攔截的方法具有相同的名稱和函數(shù)簽名,而且也具有與被攔截方法相同的行為。因此,使用反射 API 也可以像下面這樣定義出空代理對象:

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

          我們也可以以此,來對將要訪問的屬性的返回值進行修飾。

          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

          可撤銷代理

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

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

          代理另一個代理

          代理可以攔截反射 API 的操作,而這意味著完全可以創(chuàng)建一個代理,通過它去代理另一個代理。這樣就可以在一個目標對象之上構(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

          代理的問題與不足

          1、代理中的this

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

          這樣看起來并沒有什么問題,this指向調(diào)用者。但是如果目標對象依賴于對象標識,那就可能碰到意料之外的問題。

          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

          這是因為 User 實例一開始使用目標對象作為 WeakMap 的鍵,代理對象卻嘗試從自身取得這個實例。

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

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

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

          在代理Date類型時:根據(jù) ECMAScript 規(guī)范,Date 類型方法的執(zhí)行依賴 this 值上的內(nèi)部槽位[[NumberDate]]。代理對象上不存在這個內(nèi)部槽位,而且這個內(nèi)部槽位的值也不能通過普通的 get()和 set()操作訪問到,于是代理攔截后本應轉(zhuǎn)發(fā)給目標對象的方法會拋出 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對象與Proxy對象一樣,也是 ES6 為了操作對象而提供的新 API。Reflect的設計目的:

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

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

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

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

          代理與反射API

          get()

          接收參數(shù):

          • target:目標對象。

          • property:引用的目標對象上的字符串鍵屬性。

          • receiver:代理對象或繼承代理對象的對象。
            返回:

          • 返回值無限制
            get()捕獲器會在獲取屬性值的操作中被調(diào)用。對應的反射 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:目標對象。

          • property:引用的目標對象上的字符串鍵屬性。

          • value:要賦給屬性的值。

          • receiver:接收最初賦值的對象。
            返回:

          • 返回 true 表示成功;返回 false 表示失敗,嚴格模式下會拋出 TypeError。

          set()捕獲器會在設置屬性值的操作中被調(diào)用。對應的反射 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:目標對象。

          • property:引用的目標對象上的字符串鍵屬性。

          返回:

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

          has()捕獲器會在 in 操作符中被調(diào)用。對應的反射 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,用來為對象定義屬性。

          接收參數(shù):

          • target:目標對象。

          • property:引用的目標對象上的字符串鍵屬性。

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

          返回:

          • defineProperty()必須返回布爾值,表示屬性是否成功定義。返回非布爾值會被轉(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,用于得到指定屬性的描述對象。

          接收參數(shù):

          • target:目標對象。

          • property:引用的目標對象上的字符串鍵屬性。

          返回:

          • getOwnPropertyDescriptor()必須返回對象,或者在屬性不存在時返回 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],用于刪除對象的屬性。

          接收參數(shù):

          • target:目標對象。

          • property:引用的目標對象上的字符串鍵屬性。

          返回:

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

          ownKeys()

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

          接收參數(shù):

          • target:目標對象。

          返回:

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

          getPrototypeOf()

          Reflect.getPrototypeOf方法用于讀取對象的__proto__屬性

          接收參數(shù):

          • target:目標對象。

          返回:

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

          等等。。

          代理模式

          跟蹤屬性訪問

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

          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)部實現(xiàn)對外部代碼是不可見的,因此要隱藏目標對象上的屬性也輕而易舉。

          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

          屬性驗證

          因為所有賦值操作都會觸發(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ù)驗證

          跟保護和驗證對象屬性類似,也可對函數(shù)和構(gòu)造函數(shù)參數(shù)進行審查。比如,可以讓函數(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 類似地,可以要求實例化時必須給構(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ù)綁定與可觀察對象

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

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

          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{}]

          另外,還可以把集合綁定到一個事件分派程序,每次插入新實例時都會發(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 實現(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é)尾

          本文主要參考阮一峰es6教程、js紅寶書第四版。


          學習更多技能

          請點擊下方公眾號


          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  乱伦小说亚洲图片 | v11av| 做爱网址 | 天堂国产在线 | 男女AV狠狠撸 |