• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當前位置: 首頁 - 科技 - 知識百科 - 正文

    ES6 如何改變JS內置行為的代理與反射

    來源:懂視網(wǎng) 責編:小采 時間:2020-11-27 22:01:12
    文檔

    ES6 如何改變JS內置行為的代理與反射

    ES6 如何改變JS內置行為的代理與反射:代理(Proxy)可以攔截并改變 JS 引擎的底層操作,如數(shù)據(jù)讀取、屬性定義、函數(shù)構造等一系列操作。ES6 通過對這些底層內置對象的代理陷阱和反射函數(shù),讓開發(fā)者能進一步接近 JS 引擎的能力。 一、代理與反射的基本概念 什么是代理和反射呢? 代理是用來替
    推薦度:
    導讀ES6 如何改變JS內置行為的代理與反射:代理(Proxy)可以攔截并改變 JS 引擎的底層操作,如數(shù)據(jù)讀取、屬性定義、函數(shù)構造等一系列操作。ES6 通過對這些底層內置對象的代理陷阱和反射函數(shù),讓開發(fā)者能進一步接近 JS 引擎的能力。 一、代理與反射的基本概念 什么是代理和反射呢? 代理是用來替

    攔截行為使用了一個能夠響應特定操作的函數(shù)( 被稱為陷阱),每個代理陷阱對應一個反射(Reflect)方法。

    ES6 的反射 API 以 Reflect 對象的形式出現(xiàn),對象每個方法都與對應的陷阱函數(shù)同名,并且接收的參數(shù)也與之一致。以下是 Reflect 對象的一些方法:

    代理陷阱 覆寫的特性 方法
    get 讀取一個屬性的值 Reflect.get()
    set 寫入一個屬性 Reflect.set()
    has in 運算符 Reflect.has()
    deleteProperty delete 運算符 Reflect.deleteProperty()
    getPrototypeOf Object.getPrototypeOf() Reflect.getPrototypeOf()
    isExtensible Object.isExtensible() Reflect.isExtensible()
    defineProperty Object.defineProperty() Reflect.defineProperty
    apply 調用一個函數(shù) Reflect.apply()
    construct 使用 new 調用一個函數(shù) Reflect.construct()

    每個陷阱函數(shù)都可以重寫 JS 對象的一個特定內置行為,允許你攔截并修改它。

    綜合來說,想要控制或改變JS的一些底層操作,可以先創(chuàng)建一個代理對象,在這個代理對象上掛載一些陷阱函數(shù),陷阱函數(shù)里面有反射方法。通過接下來的應用示例可以更清晰的明白代理的過程。

    二、開始一個簡單的代理

    當你使用 Proxy 構造器來創(chuàng)建一個代理時,需要傳遞兩個參數(shù):目標對象(target)以及一個處理器( handler),

    先創(chuàng)建一個僅進行傳遞的代理如下:

    // 目標對象
    let target = {}; 
    // 代理對象
    let proxy = new Proxy(target, {});
    
    proxy.name = "hello";
    console.log(proxy.name); // "hello"
    console.log(target.name); // "hello"
    
    target.name = "world";
    console.log(proxy.name); // "world"
    console.log(target.name); // "world
    
    

    上例中的 proxy 代理對象將所有操作直接傳遞給 target 目標對象,代理對象 proxy 自身并沒有存儲該屬性,它只是簡單將值傳遞給 target 對象,proxy.name 與 target.name 的屬性值總是相等,因為它們都指向 target.name。

    此時代理陷阱的處理器為空對象,當然處理器可以定義了一個或多個陷阱函數(shù)。

    2.1 set 驗證對象屬性的存儲

    假設你想要創(chuàng)建一個對象,并要求其屬性值只能是數(shù)值,這就意味著該對象的每個新增屬性

    都要被驗證,并且在屬性值不為數(shù)值類型時應當拋出錯誤。

    這時需要使用 set 陷阱函數(shù)來攔截傳入的 value,該陷阱函數(shù)能接受四個參數(shù):

  • trapTarget :將接收屬性的對象( 即代理的目標對象)
  • key :需要寫入的屬性的鍵( 字符串類型或符號類型)
  • value :將被寫入屬性的值;
  • receiver :操作發(fā)生的對象( 通常是代理對象)
  • set 陷阱對應的反射方法和默認特性是Reflect.set(),和陷阱函數(shù)一樣接受這四個參數(shù),并會基于操作是否成功而返回相應的結果:

    let targetObj = {};
    let proxyObj = new Proxy(targetObj, {
     set: set
    });
    
    /* 定義 set 陷阱函數(shù) */
    function set (trapTarget, key, value, receiver) {
     if (isNaN(value)) {
     throw new TypeError("Property " + key + " must be a number.");
     }
     return Reflect.set(trapTarget, key, value, receiver);
    }
    
    /* 測試 */
    proxyObj.count = 123;
    console.log(proxyObj.count); // 123
    console.log(targetObj.count); // 123
    
    proxyObj.anotherName = "proxy" // TypeError: Property anotherName must be a number.
    

    示例中set 陷阱函數(shù)成功攔截傳入的 value 值,你可以嘗試一下,如果注釋或不return Reflect.set()會發(fā)生什么?,答案是攔截陷阱就不會有反射響應。

    需要注意的是,直接給 targetObj 目標對象賦值時是不會觸發(fā) set 代理陷阱的,需要通過給代理對象賦值才會觸發(fā) set 代理陷阱與反射。

    2.2 get 驗證對象屬性的讀取

    JS 非常有趣的特性之一,是讀取不存在的屬性時并不會拋出錯誤,而會把undefined當作該屬性的值。

    對于大型的代碼庫,當屬性名稱存在書寫錯誤時(不會拋錯)會導致嚴重的問題。這時使用 get 代理陷阱驗證對象結構(Object Shape),訪問不存在的屬性時就拋出錯誤,使對象結構驗證變得簡單。

    get 陷阱函數(shù)會在讀取屬性時被調用,即使該屬性在對象中并不存在,它能接受三個參數(shù):

  • trapTarget :將會被讀取屬性的對象( 即代理的目標對象)
  • key :需要讀取的屬性的鍵( 字符串類型或符號類型)
  • receiver :操作發(fā)生的對象( 通常是代理對象)
  • Reflect.get()方法接受與之相同的參數(shù),并返回默認屬性的默認值。

    let proxyObj = new Proxy(targetObj, {
     set: set,
     get: get
    });
    
    /* 定義 get 陷阱函數(shù) */
    function get(trapTarget, key, receiver) {
     if (!(key in receiver)) {
     throw new TypeError("Property " + key + " doesn't exist.");
     }
     return Reflect.get(trapTarget, key, receiver);
    }
    
    console.log(proxyObj.count); // 123
    console.log(proxyObj.newcount) // TypeError: Property newcount doesn't exist.
    
    

    這段代碼允許添加新的屬性,并且此后可以正常讀取該屬性的值,但當讀取的屬性并

    不存在時,程序拋出了一個錯誤,而不是將其默認為undefined。

    還可以使用 has 陷阱驗證in運算符,使用 deleteProperty 陷阱函數(shù)避免屬性被delete刪除。
    注:in運算符用于判斷對象中是否存在某個屬性,如果自有屬性或原型屬性匹配這個名稱字符串或Symbol,那么in運算符返回 true。

    targetObj = {
     name: 'targetObject'
    };
    console.log("name" in targetObj); // true
    console.log("toString" in targetObj); // true
    

    其中 name 是對象自身的屬性,而 toString 則是原型屬性( 從 Object 對象上繼承而來),所以檢測結果都為 true。

    has 陷阱函數(shù)會在使用in運算符時被調用,并且會傳入兩個參數(shù)(同名反射Reflect.has()方法也一樣):

  • trapTarget :需要讀取屬性的對象( 代理的目標對象)
  • key :需要檢查的屬性的鍵( 字符串類型或 Symbol符號類型)
  • deleteProperty 陷阱函數(shù)會在使用delete運算符去刪除對象屬性時下被調用,并且也會被傳入兩個參數(shù)(Reflect.deleteProperty() 方法也接受這兩個參數(shù)):

  • trapTarget :需要刪除屬性的對象( 即代理的目標對象) ;
  • key :需要刪除的屬性的鍵( 字符串類型或符號類型) 。
  • 一些思考:分析過 Vue 源碼的都了解過,給一個 Vue 實例中掛載的 data,是通過Object.defineProperty代理 vm._data 中的對象屬性,實現(xiàn)雙向綁定...... 同理可以考慮使用 ES6 的 Proxy 的 get 和 set 陷阱實現(xiàn)這個代理。

    三、對象屬性陷阱

    3.1 數(shù)據(jù)屬性與訪問器屬性

    ES5 最重要的特征之一就是引入了 Object.defineProperty() 方法定義屬性的特性。屬性的特性是為了實現(xiàn)javascript引擎用的,屬于內部值,因此不能直接訪問他們。

    屬性分為數(shù)據(jù)屬性和訪問器屬性。使用Object.defineProperty()方法修改數(shù)據(jù)屬性的特性值的示例如下:

    let obj1 = {
     name: 'myobj',
    }
    /* 數(shù)據(jù)屬性*/
    Object.defineProperty(obj1,'name',{
     configurable: false, // default true
     writable: false, // default true
     enumerable: true, // default true
     value: 'jenny' // default undefined
    })
    console.log(obj1.name) // 'jenny'
    

    其中[[Configurable]] 表示能否通過 delete 刪除屬性從而重新定義為訪問器屬性;[[Enumerable]] 表示能否通過for-in循環(huán)返回屬性;[[Writable]] 表示能否修改屬性的值; [[Value]] 包含這個屬性的數(shù)據(jù)值。

    對于訪問器屬性,該屬性不包含數(shù)據(jù)值,包含一對getter和setter函數(shù),定義訪問器屬性必須使用Object.defineProperty()方法:

    let obj2 = {
     age: 18
    }
    /* 訪問器屬性 */
    Object.defineProperty(obj2,'_age',{
     configurable: false, // default true
     enumerable: false, // default true
     get () { // default undefined
     return this.age
     },
     set (num) { // default undefined
     this.age = num
     }
    })
    /* 修改訪問器屬性調用 getter */
    obj2._age = 20 
    console.log(obj2.age) // 20
    
    /* 
    輸出訪問器屬性 */ console.log(Object.getOwnPropertyDescriptor(obj2,'_age')) // { get: [Function: get], // set: [Function: set], // enumerable: false, // configurable: false }

    [[Get]] 在讀取屬性時調用的函數(shù), [[Set]] 再寫入屬性時調用的函數(shù)。使用訪問器屬性的常用方式,是設置一個屬性的值導致其他屬性發(fā)生變化。

    3.2 檢查屬性的修改

    代理允許你使用 defineProperty 同名函數(shù)陷阱函數(shù)攔截Object.defineProperty()的調用,defineProperty 陷阱函數(shù)接受下列三個參數(shù):

  • trapTarget :需要被定義屬性的對象( 即代理的目標對象);
  • key :屬性的鍵( 字符串類型或符號類型);
  • descriptor :為該屬性準備的描述符對象。
  • defineProperty 陷阱函數(shù)要求在操作后返回一個布爾值用于判斷操作是否成功,如果返回了 false 則拋出錯誤,故可以使用該功能來限制哪些屬性可以被Object.defineProperty() 方法定義。

    例如,如果想阻止定義Symbol符號類型的屬性,你可以檢查傳入的屬性值,若是則返回 false:

    /* 定義代理 */
    let proxy = new Proxy({}, {
     defineProperty(trapTarget, key, descriptor) {
     if (typeof key === "symbol") {
     return false;
     }
     return Reflect.defineProperty(trapTarget, key, descriptor);
     }
    });
    
    Object.defineProperty(proxy, "name", {
     value: "proxy"
    });
    console.log(proxy.name); // "proxy"
    
    let nameSymbol = Symbol("name");
    // 拋出錯誤
    Object.defineProperty(proxy, nameSymbol, {
     value: "proxy"
    })

    四、函數(shù)代理

    4.1 構造函數(shù) & 立即執(zhí)行

    函數(shù)的兩個內部方法:[[Call]] 與[[Construct]]會在函數(shù)被調用時調用,通過代理函數(shù)來為這兩個內部方法設置陷阱,從而控制函數(shù)的行為。

    [[Construct]]會在函數(shù)被使用new運算符調用時執(zhí)行,代理觸發(fā)construct()陷阱函數(shù),并和Reflect.construct()一樣接收到下列兩個參數(shù):

  • trapTarget :被執(zhí)行的函數(shù)( 即代理的目標對象) ;
  • argumentsList :被傳遞給函數(shù)的參數(shù)數(shù)組。
  • [[Call]]會在函數(shù)被直接調用時執(zhí)行,代理觸發(fā)apply()陷阱函數(shù),它和Reflect.apply()都接收三個參數(shù):

  • trapTarget :被執(zhí)行的函數(shù)( 代理的目標函數(shù)) ;
  • thisArg :調用過程中函數(shù)內部的 this 值;
  • argumentsList :被傳遞給函數(shù)的參數(shù)數(shù)組。
  • 每個函數(shù)都包含call()和apply()方法,用于重置函數(shù)運行的作用域即 this 指向,區(qū)別只是接收參數(shù)的方式不同:call()的參數(shù)需要逐個列舉、apply()是參數(shù)數(shù)組。

    顯然,apply 與 construct 要求代理目標對象必須是一個函數(shù),這兩個代理陷阱在函數(shù)的執(zhí)行方式上開啟了很多的可能性,結合使用就可以完全控制任意的代理目標函數(shù)的行為。

    4.2 驗證函數(shù)的參數(shù)

    看到apply()和construct()陷阱的參數(shù)都有被傳遞給函數(shù)的參數(shù)數(shù)組argumentsList,所以可以用來驗證函數(shù)的參數(shù)。

    例如需要保證所有參數(shù)都是某個特定類型的,并且不能通過 new 構造使用,示例如下:

    /* 定義 sum 目標函數(shù) */
    function sum(...values) {
     return values.reduce((previous, current) => previous + current, 0);
    }
    /* 定義 apply 陷阱函數(shù) */
    function applyRef (trapTarget, thisArg, argumentList) {
     argumentList.forEach((arg) => {
     if (typeof arg !== "number") {
     throw new TypeError("All arguments must be numbers.");
     }
     });
     return Reflect.apply(trapTarget, thisArg, argumentList);
    }
    /* 定義 construct 陷阱函數(shù) */
    function constructRef () {
     throw new TypeError("This function can't be called with new.");
    }
    /* 定義 sumProxy 代理函數(shù) */
    let sumProxy = new Proxy(sum, {
     apply: applyRef,
     construct: constructRef
    });
    
    console.log(sumProxy(1, 2, 3, 4)); // 10
    
    // console.log(sumProxy(1, "2", 3, 4)); // TypeError: All arguments must be numbers.
    // let result = new sumProxy() // TypeError: This function can't be called with new.
    
    

    sum() 函數(shù)會將所有傳遞進來的參數(shù)值相加,此代碼通過將 sum() 函數(shù)封裝在 sumProxy() 代理中,如果傳入?yún)?shù)的值不是數(shù)值類型,該函數(shù)仍然會嘗試加法操作,但在函數(shù)運行之前攔截了函數(shù)調用,觸發(fā)apply陷阱函數(shù)以保證每個參數(shù)都是數(shù)值。

    出于安全的考慮,這段代碼使用 construct 陷阱拋出錯誤,以確保該函數(shù)不會被使用 new 運算符調用

    實例對象 instance 對象會被同時判定為 proxy 與 target 對象的實例,是因為 instanceof 運算符使用了原型鏈來進行推斷,而原型鏈查找并沒有受到這個代理的影響,因此 proxy 對象與 target 對象對于 JS 引擎來說就有同一個原型。

    4.3 調用類的構造函數(shù)

    ES6 中新引入了class類的概念,類使用constructor構造函數(shù)封裝數(shù)據(jù),并規(guī)定必須始終使用 new 來調用,原因是類構造器的內部方法 [[Call]] 被明確要求拋出錯誤。

    代理可以攔截對于 [[Call]] 方法的調用,你可以借助代理調用的類構造器。例如在缺少 new 的情況下創(chuàng)建一個新實例,就使用 apply 陷阱函數(shù)實現(xiàn):

    class Person {
     constructor(name) {
     this.name = name;
     }
    }
    let PersonProxy = new Proxy(Person, {
     apply: function(trapTarget, thisArg, argumentList) {
     return new trapTarget(...argumentList);
     }
    });
    let me = PersonProxy("Jenny");
    console.log(me.name); // "Jenny"
    console.log(me instanceof Person); // true
    console.log(me instanceof PersonProxy); // true
    

    類構造器即類的構造函數(shù),使用代理時它的行為就像函數(shù)一樣,apply陷阱函數(shù)重寫了默認的構造行為。

    關于類的更多有趣的用法,可參考 【ES6】更易于繼承的類語法

    總結來說,代理的用途非常廣泛,因為它提供了修改 JS 內置對象的所有行為的入口。上述例子只是簡單的一些應用入門,還有更多復雜的示例,推薦閱讀《深入理解ES6》。

    聲明:本網(wǎng)頁內容旨在傳播知識,若有侵權等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    ES6 如何改變JS內置行為的代理與反射

    ES6 如何改變JS內置行為的代理與反射:代理(Proxy)可以攔截并改變 JS 引擎的底層操作,如數(shù)據(jù)讀取、屬性定義、函數(shù)構造等一系列操作。ES6 通過對這些底層內置對象的代理陷阱和反射函數(shù),讓開發(fā)者能進一步接近 JS 引擎的能力。 一、代理與反射的基本概念 什么是代理和反射呢? 代理是用來替
    推薦度:
    標簽: 代理 js 的代理
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 久久国产热这里只有精品| 色偷偷88欧美精品久久久| 精品国精品国产自在久国产应用男| 亚洲精品无码成人片久久| 国产亚洲美女精品久久久| 97久久久精品综合88久久| 午夜精品一区二区三区在线视 | 无码久久精品国产亚洲Av影片| 国产精品内射久久久久欢欢 | 精品一卡2卡三卡4卡免费视频| 久久亚洲精品国产精品| 亚洲国产精品ⅴa在线观看| 精品无码久久久久久国产| 国产精品1024在线永久免费| 国产精品一久久香蕉产线看| 91视频国产精品| 99久久久精品免费观看国产| 精品久久久久久无码中文字幕一区| 在线亚洲欧美中文精品| 亚洲高清国产拍精品青青草原| 久久se这里只有精品| 国产午夜精品一本在线观看| 91午夜精品亚洲一区二区三区| 99在线精品免费视频九九视| 国产成人精品日本亚洲网站| 久久精品国产精品亚洲毛片 | 亚洲国产成人精品久久久国产成人一区二区三区综 | 国产AV国片精品有毛| 国内精品久久久久久99| 少妇人妻无码精品视频| 色久综合网精品一区二区| 午夜不卡久久精品无码免费| 日韩欧精品无码视频无删节 | 国产精品国产三级国产专播| 国产精品夜色一区二区三区| 久久精品成人国产午夜| 久久青草国产精品一区| 51国偷自产精品一区在线视频| 色综合久久精品中文字幕首页| 91精品国产91久久久久久| www.精品|