代理和反射是ES6新增的兩個特性,兩者之間是協(xié)調(diào)合作的關(guān)系,它們的具體功能將在接下來的章節(jié)中分別講解。

          一、代理


            ES6引入代理(Proxy)地目的是攔截對象的內(nèi)置操作,注入自定義的邏輯,改變對象的默認行為。也就是說,將某些JavaScript內(nèi)部的操作暴露了出來,給予開發(fā)人員更多的權(quán)限。這其實是一種元編程(metaprogramming)的能力,即把代碼看成數(shù)據(jù),對代碼進行編程,改變代碼的行為。

            在ES6中,代理是一種特殊的對象,如果要使用,需要像下面這樣先生成一個Proxy實例。
          new Proxy(target, handler);

            構(gòu)造函數(shù)Proxy()有兩個參數(shù),其中target是要用代理封裝的目標對象,handler也是一個對象,它的方法被稱為陷阱(trap),用于指定攔截后的行為。下面是一個代理的簡單示例。
          var obj = {}, handler = { set(target, property, value, receiver) {
          target[property]= "hello " + value; } }, p = new Proxy(obj, handler); p.name =
          "strick"; console.log(p.name); //"hello strick"

            在上面的代碼中,p是一個Proxy實例,它的目標對象是obj,使用了屬性相關(guān)的陷阱:set()方法。當它寫入obj的name屬性時,會對其進行攔截,在屬性值之前加上“hello
          ”前綴。除了上例使用的set()方法,ES6還給出了另外12種可用的陷阱,在后面的章節(jié)中會對它們做簡單的介紹。

          1)陷阱

            表12羅列了目前所有可用的陷阱,第二列表示當前陷阱可攔截的行為,注意,只挑選了其中的幾個用于展示。

          表12? 十三種陷阱

          陷阱 攔截 返回值
          ?get() ?讀取屬性 ?任意值
          ?set() ?寫入屬性 ?布爾值
          ?has() ?in運算符 ?布爾值
          ?deleteProperty() ?delete運算符 ?布爾值
          ?getOwnPropertyDescriptor() ?Object.getOwnPropertyDescriptor() ?屬性描述符對象
          ?defineProperty() ?Object.defineProperty() ?布爾值
          ?preventExtensions() ?Object.preventExtensions() ?布爾值
          ?isExtensible() ?Object.isExtensible() ?布爾值
          ?getPrototypeOf()
          ?Object.getPrototypeOf()? ? ? ? ? ? ? __proto__

          ?Object.prototype.isPrototypeOf()??instanceof
          ?對象?
          ?setPrototypeOf() ?Object.setPrototypeOf()? ?布爾值
          ?apply()
          ?Function.prototype.apply()? ? ? ? ? ?函數(shù)調(diào)用

          ?Function.prototype.call()
          ?任意值
          ?construct() ?new運算符作用于構(gòu)造函數(shù) ?對象
          ?ownKeys()
          ?Object.getOwnPropertyNames()? ??Object.keys()

          ?Object.getOwnPropertySymbols()??for-in循環(huán)
          ?數(shù)組
            目前支持的攔截就上面幾種,像typeof運算符、全等比較等操作還不被ES6支持。接下來會挑選其中的兩次個陷阱,講解它們的簡單應用。


            在JavaScript中,當讀取對象上不存在的屬性時,不會報錯而是返回undefined,這其實在某些情況下會發(fā)生歧義,現(xiàn)在利用陷阱中的get()方法就能改變默認行為,如下所示。
          var obj = { name: "strick" }, handler = { get(target, property, receiver) { if
          (propertyin target) return target[property]; throw "未定義的錯誤"; } }, p = new
          Proxy(obj, handler); p.name;   //"strick" p.age; //未定義的錯誤

            在get()方法中有3個參數(shù),target是目標對象(即obj),property是讀取的屬性的名稱(即“name”和“age”),receiver是當前的Proxy實例(即p)。在讀取屬性時,會用in運算符判斷當前屬性是否存在,如果存在就返回相應的屬性值,否則就會拋出錯誤,這樣就能避免歧義的出現(xiàn)。?
          ?


            在眾多陷阱中,只有apply()和construct()的目標對象得是函數(shù)。以apply()方法為例,它有3個參數(shù),target是目標函數(shù),thisArg是this的指向,argumentsList是函數(shù)的參數(shù)序列,它的具體使用如下所示。
          function getName(name) { return name; } var obj = { prefix: "hello " }, handler
          = { apply(target, thisArg, argumentsList) { if(thisArg && thisArg.prefix) return
          target(thisArg.prefix + argumentsList[0]); return target(...argumentsList); }
          }, p= new Proxy(getName, handler); p("strick");     //"strick" p.call(obj,
          "strick");//"hello strick"
            p是一個Proxy實例,p("strick")是一次普通的函數(shù)調(diào)用,此時雖然攔截了,但是仍然會把參數(shù)原樣傳過去;而p.call(obj,
          "strick")是間接的函數(shù)調(diào)用,此時會給第一個參數(shù)添加前綴,從而改變函數(shù)最終的返回值。

          2)撤銷代理


            Proxy.revocable()方法能夠創(chuàng)建一個可撤銷的代理,它能接收兩個參數(shù),其含義與構(gòu)造函數(shù)Proxy()中的相同,但返回值是一個對象,包含兩個屬性,如下所列。

          (1)proxy:新生成的Proxy實例。

          (2)revoke:撤銷函數(shù),它沒有參數(shù),能把與它一起生成的Proxy實例撤銷掉。


            下面是一個簡單的示例,obj是目標對象,handler是陷阱對象,傳遞給Proxy.revocable()后,通過對象解構(gòu)將返回值賦給了proxy和revoke兩個變量。
          var obj = {}, handler = {}; let {proxy, revoke} = Proxy.revocable(obj,
          handler); revoke();delete proxy.name; //類型錯誤 typeof proxy; //"object"

            在調(diào)用revoke()函數(shù)后,就不能再對proxy進行攔截了。像上例使用delete運算符,就會拋出類型錯誤,但像typeof之類的不可攔截的運算符還是可以成功執(zhí)行的。

          3)原型

            代理可以成為其它對象的原型,就像下面這樣。
          var obj = { name: "strick" }, handler = { get(target, property, receiver) { if
          (property == "name") return "hello " + target[property]; return true; } }, p =
          new Proxy({}, handler); Object.setPrototypeOf(obj, p); //obj的原型指向Proxy實例
          obj.name;       //"strick" obj.age;        //true

            p是一個Proxy實例,它會攔截屬性的讀取操作,obj的原型指向了p,注意,p的目標對象不是obj。當obj讀取name屬性時,不會觸發(fā)攔截,因為name是自有屬性,所以不會去原型上查找,最終得到的結(jié)果是沒有前綴的“strick”。之前的代理都是直接作用于相關(guān)對象(例如上面的obj),因此只要執(zhí)行可攔截的動作就會被處理,但現(xiàn)在中間隔了個原型,有了更多的限制。而在讀取age屬性時,由于自有屬性中沒有它,因此就會去原型上查找,從而觸發(fā)了攔截操作,返回了true。

          二、反射


            反射(Reflect)向外界暴露了一些底層操作的默認行為,它是一個沒有構(gòu)造函數(shù)的內(nèi)置對象,類似于Math對象,其所有方法都是靜態(tài)的。代理中的每個陷阱都會對應一個同名的反射方法(例如Reflect.set()、Reflect.ownKeys()等),而每個反射方法又都會關(guān)聯(lián)到對應代理所攔截的行為(例如in運算符、Object.defineProperty()等),這樣就能保證某個操作的默認行為可隨時被訪問到。反射讓對象的內(nèi)置行為變得更加嚴謹、合理與便捷,具體表現(xiàn)如下所列。


           ?。?)參數(shù)的檢驗更為嚴格,Object的getPrototypeOf()、isExtensible()等方法會將非對象的參數(shù)自動轉(zhuǎn)換成相應的對象(例如字符串轉(zhuǎn)換成String對象,如下代碼所示),而關(guān)聯(lián)的反射方法卻不會這么做,它會直接拋出類型錯誤。
          Object.getPrototypeOf("strick") === String.prototype;     //true
          Reflect.getPrototypeOf("strick");      //類型錯誤

           ?。?)更合理的返回值,Object.setPrototypeOf()會返回它的第一個參數(shù),而Reflect的同名方法會返回一個布爾值,后者能更直觀的反饋設(shè)置是否成功,兩個方法的對比如下所示。
          var obj = {}; Object.setPrototypeOf(obj, String) === obj;    //true
          Reflect.setPrototypeOf(obj, String);   //true
           ?。?)用方法替代運算符,反射能以調(diào)用方法的形式完成new、in、delete等運算符的功能,在下面的示例中,先使用運算符,再給出對應的反射方法。
          function func() { } new func(); Reflect.construct(func, []); var people = {
          name:"strick" }; "name" in people; Reflect.has(people, "name"); delete
          people["name"]; Reflect.deleteProperty(people, "name");
            (4)避免冗長的方法調(diào)用,以apply()方法為例,如下所示。
          Function.prototype.apply.call(Math.ceil, null, [2.5]);   //3
          Reflect.apply(Math.ceil,null, [2.5]);      //3

            上面代碼的第一條語句比較繞,需要將其分解成兩部分:Function.prototype.apply()和call()。ES5規(guī)定apply()和call()兩個方法在最后都要調(diào)用一個有特殊功能的內(nèi)部函數(shù),如下代碼所示,func參數(shù)表示調(diào)用這兩個方法的函數(shù)。
          [[Call]](func, thisArg, argList)

            內(nèi)部函數(shù)的功能就是在調(diào)用func()函數(shù)時,傳遞給它的參數(shù)序列是argList,其內(nèi)部的this指向了thisArg。當執(zhí)行第一條語句時,傳遞給[[Call]]函數(shù)的三個參數(shù)如下所示。
          [[Call]](Function.prototype.apply, Math.ceil, [null, [2.5]])

            接下來會調(diào)用原型上的apply()方法,由于其this指向了Math.ceil(即當前調(diào)用apply()方法的是Math.ceil),因此[[Call]]函數(shù)的第一個參數(shù)就是Math.ceil,如下所示。
          [[Call]](Math.ceil, null, [2.5]) //相當于 Math.ceil.apply(null, [2.5])
          ?

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                91精品国产日韩91久久久久久360 | 我要操操操 | 成人午夜爱爱3p高潮影院 | 国产精品视频热久9 | 九九韩剧网最新电视剧免费观看 |