<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>

          深入了解 Proxy 代理

          共 13478字,需瀏覽 27分鐘

           ·

          2021-03-17 02:35

          代理對象封裝另一個對象并攔截操作,如讀取/寫入屬性和其他操作,可以選擇自己處理它們,或透明地允許對象處理它們。

          很多庫和一些瀏覽器框架都使用代理。在本文中,我們將看到許多實(shí)際應(yīng)用程序。

          Proxy

          語法如下:

          let proxy = new Proxy(target, handler)
          • target - 是一個要包裝的對象,可以是任何東西,包括函數(shù)。

          • handler - 代理配置:一個帶有“陷阱”的對象,攔截操作的方法。-例如,讀取target屬性時設(shè)置trap,寫入target屬性時設(shè)置trap,等等。

          對于代理上的操作,如果handler中有相應(yīng)的陷阱,那么它就會運(yùn)行,并且代理有機(jī)會處理它,否則操作就會在目標(biāo)上執(zhí)行。

          作為一個開始的例子,讓我們創(chuàng)建一個沒有任何陷阱的代理:

          let target = {};
          let proxy = new Proxy(target, {}); // empty handler

          proxy.test = 5// writing to proxy (1)
          alert(target.test); // 5, the property appeared in target!

          alert(proxy.test); // 5, we can read it from proxy too (2)

          for(let key in proxy) alert(key); // test, iteration works (3)

          由于沒有陷阱,代理上的所有操作都被轉(zhuǎn)發(fā)到目標(biāo)。

          • 寫操作 proxy.test=target上的值。

          • 讀取操作 proxy.test 從 target 返回值。

          • 迭代代理返回目標(biāo)值。

          正如我們所見,沒有任何陷阱,proxy是一個透明的目標(biāo)包裝器。

          Proxy是一種特殊的“外來對象”。它沒有自己的屬性。使用空處理程序,它透明地將操作轉(zhuǎn)發(fā)給target。

          為了激活更多的功能,讓我們添加陷阱。

          我們能用他們攔截什么?

          對于對象上的大多數(shù)操作,JavaScript規(guī)范中都有一個所謂的“內(nèi)部方法”,它描述了它在最低級別的工作方式。例如[[Get]],讀取屬性的內(nèi)部方法,[[Set]],寫入屬性的內(nèi)部方法,等等。這些方法僅在規(guī)范中使用,我們不能直接通過名稱調(diào)用它們。

          代理陷阱攔截這些方法的調(diào)用。它們在代理規(guī)范和下表中列出。

          對于每個內(nèi)部方法,在該表中都有一個陷阱:我們可以添加到新代理的handler參數(shù)的方法名來攔截操作:

          使用 get 方式獲取默認(rèn)值

          最常見的陷阱是用于讀/寫屬性的。

          為了攔截讀取,處理程序應(yīng)該有一個方法get(目標(biāo)、屬性、接收器)。

          當(dāng)一個屬性被讀取時,它會觸發(fā),參數(shù)如下:

          • target—是目標(biāo)對象,作為第一個參數(shù)傳遞給新代理,

          • property -屬性名稱,

          • receiver——如果目標(biāo)屬性是一個getter,那么receiver就是將在其調(diào)用中使用的對象。通常這是代理對象本身(或者從它繼承的對象,如果我們從代理繼承的話)。現(xiàn)在我們不需要這個論證,所以后面會更詳細(xì)地解釋。

          讓我們使用get來實(shí)現(xiàn)對象的默認(rèn)值。

          我們將創(chuàng)建一個數(shù)字?jǐn)?shù)組,對于不存在的值返回0。

          通常,當(dāng)一個人試圖獲取一個不存在的數(shù)組項(xiàng)時,他們得到的是未定義的,但是我們將把一個常規(guī)的數(shù)組包裝到代理中,以捕獲讀取,如果沒有這樣的屬性則返回0:

          let numbers = [012];

          numbers = new Proxy(numbers, {
            get(target, prop) {
              if (prop in target) {
                return target[prop];
              } else {
                return 0// default value
              }
            }
          });

          alert( numbers[1] ); // 1
          alert( numbers[123] ); // 0 (no such item)

          正如我們所見,誘捕陷阱很容易做到。

          我們可以使用代理來實(shí)現(xiàn)“默認(rèn)”值的任何邏輯。

          想象一下我們有一本詞典,里面有一些短語和它們的翻譯:

          let dictionary = {
            'Hello''Hola',
            'Bye''Adiós'
          };

          alert( dictionary['Hello'] ); // Hola
          alert( dictionary['Welcome'] ); // undefined

          現(xiàn)在,如果沒有短語,從字典中讀取將返回undefined。但在實(shí)踐中,不翻譯一個短語通常比不定義要好。我們讓它返回一個未翻譯的短語,而不是undefined。

          為了實(shí)現(xiàn)這一點(diǎn),我們將把dictionary封裝在一個攔截讀取操作的代理中:

          let dictionary = {
            'Hello''Hola',
            'Bye''Adiós'
          };

          dictionary = new Proxy(dictionary, {
            get(target, phrase) { // intercept reading a property from dictionary
              if (phrase in target) { // if we have it in the dictionary
                return target[phrase]; // return the translation
              } else {
                // otherwise, return the non-translated phrase
                return phrase;
              }
            }
          });

          // Look up arbitrary phrases in the dictionary!
          // At worst, they're not translated.
          alert( dictionary['Hello'] ); // Hola
          alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (no translation)

          使用 set 驗(yàn)證

          假設(shè)我們想要一個專門用于數(shù)字的數(shù)組。如果添加了另一種類型的值,應(yīng)該會出現(xiàn)錯誤。

          set trap在寫入屬性時觸發(fā)。

          set(target, property, value, receiver)
          • target—是目標(biāo)對象,作為第一個參數(shù)傳遞給新代理,

          • property -屬性名稱,

          • value -屬性值,

          • receiver——與get trap類似,只對setter屬性有效。

          如果設(shè)置成功,set trap應(yīng)該返回true,否則返回false(觸發(fā)TypeError)。

          讓我們使用它來驗(yàn)證新值:

          let numbers = [];

          numbers = new Proxy(numbers, { // (*)
            set(target, prop, val) { // to intercept property writing
              if (typeof val == 'number') {
                target[prop] = val;
                return true;
              } else {
                return false;
              }
            }
          });

          numbers.push(1); // added successfully
          numbers.push(2); // added successfully
          alert("Length is: " + numbers.length); // 2

          numbers.push("test"); // TypeError ('set' on proxy returned false)

          alert("This line is never reached (error in the line above)");

          請注意:數(shù)組的內(nèi)置功能仍然有效!值是通過push添加的。當(dāng)添加值時,length屬性自動增加。我們的代理不會破壞任何東西。

          我們不必重寫添加值的數(shù)組方法(如push和unshift等)來添加檢查,因?yàn)樗鼈冊趦?nèi)部使用由代理攔截的[[Set]]操作。

          因此,代碼是干凈和簡潔的。

          使用 ownKeys, getOwnPropertyDescriptor 進(jìn)行迭代

          Object.keys, for...in 和迭代對象屬性的大多數(shù)其他方法使用[[OwnPropertyKeys]]內(nèi)部方法(被ownKeys陷阱截獲)來獲得屬性列表。

          這些方法在細(xì)節(jié)上有所不同:

          • Object.getOwnPropertyNames(obj) 返回非符號鍵。

          • Object.getOwnPropertySymbols(obj) 返回符號鍵。

          • Object.keys/values()返回帶有可枚舉標(biāo)志的非符號鍵/值(屬性標(biāo)志在“屬性標(biāo)志和描述符”一文中解釋過)。

          • for..in 循環(huán)遍歷帶有enumerable標(biāo)志的非符號鍵和原型鍵。

          let user = {
            name"John",
            age30,
            _password"***"
          };

          user = new Proxy(user, {
            ownKeys(target) {
              return Object.keys(target).filter(key => !key.startsWith('_'));
            }
          });

          // "ownKeys" filters out _password
          for(let key in user) alert(key); // name, then: age

          // same effect on these methods:
          alert( Object.keys(user) ); // name,age
          alert( Object.values(user) ); // John,30

          不過,如果返回對象中不存在的鍵,則返回Object.keys不會列出它:

          let user = { };

          user = new Proxy(user, {
            ownKeys(target) {
              return ['a''b''c'];
            }
          });

          alert( Object.keys(user) ); // <empty>

          為什么?原因很簡單:Object.keys只返回帶有enumerable標(biāo)志的屬性。為了檢查它,它調(diào)用每個屬性的內(nèi)部方法[[GetOwnProperty]]來獲取它的描述符。這里,因?yàn)闆]有屬性,它的描述符是空的,沒有可枚舉標(biāo)志,所以它被跳過。

          為對象。要返回一個屬性,我們需要它存在于對象中,并帶有enumerable標(biāo)志,或者可以攔截對[[GetOwnProperty]]的調(diào)用(陷阱getOwnPropertyDescriptor做了這個工作),并返回一個帶有enumerable: true的描述符。

          let user = { };

          user = new Proxy(user, {
            ownKeys(target) { // called once to get a list of properties
              return ['a''b''c'];
            },

            getOwnPropertyDescriptor(target, prop) { // called for every property
              return {
                enumerabletrue,
                configurabletrue
                /* ...other flags, probable "value:..." */
              };
            }

          });

          alert( Object.keys(user) ); // a, b, c

          使用 deleteProperty 保護(hù)屬性

          有一個廣泛的約定,即以下劃線為前綴的屬性和方法是內(nèi)部的。它們不應(yīng)該從對象外部訪問。

          從技術(shù)上講,這是可能的:

          let user = {
            name"John",
            _password"secret"
          };

          alert(user._password); // secret

          讓我們使用代理來防止任何以_開頭的屬性的訪問。

          我們需要陷阱:

          • 讀取這樣的屬性時拋出錯誤,

          • 設(shè)置為寫入時拋出錯誤,

          • 刪除時拋出錯誤,

          • ownKeys排除以_開頭的屬性for..in和方法,如Object.keys

          let user = {
            name"John",
            _password"***"
          };

          user = new Proxy(user, {
            get(target, prop) {
              if (prop.startsWith('_')) {
                throw new Error("Access denied");
              }
              let value = target[prop];
              return (typeof value === 'function') ? value.bind(target) : value; // (*)
            },
            set(target, prop, val) { // to intercept property writing
              if (prop.startsWith('_')) {
                throw new Error("Access denied");
              } else {
                target[prop] = val;
                return true;
              }
            },
            deleteProperty(target, prop) { // to intercept property deletion
              if (prop.startsWith('_')) {
                throw new Error("Access denied");
              } else {
                delete target[prop];
                return true;
              }
            },
            ownKeys(target) { // to intercept property list
              return Object.keys(target).filter(key => !key.startsWith('_'));
            }
          });

          // "get" doesn't allow to read _password
          try {
            alert(user._password); // Error: Access denied
          catch(e) { alert(e.message); }

          // "set" doesn't allow to write _password
          try {
            user._password = "test"// Error: Access denied
          catch(e) { alert(e.message); }

          // "deleteProperty" doesn't allow to delete _password
          try {
            delete user._password; // Error: Access denied
          catch(e) { alert(e.message); }

          // "ownKeys" filters out _password
          for(let key in user) alert(key); // name

          請注意get陷阱的重要細(xì)節(jié),在(*)行:

          get(target, prop) {
            // ...
            let value = target[prop];
            return (typeof value === 'function') ? value.bind(target) : value; // (*)
          }

          為什么我們需要一個函數(shù)來調(diào)用`value.bind(target)``?

          原因是對象方法,如user.checkPassword(),必須能夠訪問_password:

            // ...
            checkPassword(value) {
              // object method must be able to read _password
              return value === this._password;
            }
          }

          使用 has in range

          let range = {
            start1,
            end10
          };

          我們想使用in操作符來檢查一個數(shù)字是否在范圍內(nèi)。

          has陷阱在調(diào)用中攔截。

          has(target, property)

          • target — 是目標(biāo)對象,作為第一個參數(shù)傳遞給新代理,

          • property -屬性名稱

          演示:

          let range = {
            start1,
            end10
          };

          range = new Proxy(range, {
            has(target, prop) {
              return prop >= target.start && prop <= target.end;
            }
          });

          alert(5 in range); // true
          alert(50 in range); // false

          包裝函數(shù):apply

          我們也可以用代理來封裝函數(shù)。

          apply(target, thisArg, args)陷阱將調(diào)用代理作為函數(shù):

          • target是目標(biāo)對象(functionJavaScript中的對象),

          • thisArgthis的值。

          • args是一個參數(shù)列表。

          function delay(f, ms{
            // return a wrapper that passes the call to f after the timeout
            return function(// (*)
              setTimeout(() => f.apply(thisarguments), ms);
            };
          }

          function sayHi(user{
            alert(`Hello, ${user}!`);
          }

          // after this wrapping, calls to sayHi will be delayed for 3 seconds
          sayHi = delay(sayHi, 3000);

          sayHi("John"); // Hello, John! (after 3 seconds)

          正如我們已經(jīng)看到的,這基本上是可行的。包裝器函數(shù)(*)在超時后執(zhí)行調(diào)用。

          但是包裝器函數(shù)不轉(zhuǎn)發(fā)屬性讀/寫操作或其他任何操作。包裝后,對原始函數(shù)的屬性的訪問將丟失,例如名稱、長度等:

          function delay(f, ms{
            return function({
              setTimeout(() => f.apply(thisarguments), ms);
            };
          }

          function sayHi(user{
            alert(`Hello, ${user}!`);
          }

          alert(sayHi.length); // 1 (function length is the arguments count in its declaration)

          sayHi = delay(sayHi, 3000);

          alert(sayHi.length); // 0 (in the wrapper declaration, there are zero arguments)

          理要強(qiáng)大得多,因?yàn)樗鼘⑺袃?nèi)容轉(zhuǎn)發(fā)給目標(biāo)對象。

          讓我們使用代理代替包裝函數(shù):

          function delay(f, ms{
            return new Proxy(f, {
              apply(target, thisArg, args) {
                setTimeout(() => target.apply(thisArg, args), ms);
              }
            });
          }

          function sayHi(user{
            alert(`Hello, ${user}!`);
          }

          sayHi = delay(sayHi, 3000);

          alert(sayHi.length); // 1 (*) proxy forwards "get length" operation to the target

          sayHi("John"); // Hello, John! (after 3 seconds)



          瀏覽 73
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  韩国一级网站 | 日逼视频软件 | 国内精品亚洲 | 伊人操插无码操插 | 色婷婷无码 |