摘要:存取描述符同時具有以下可選鍵值一個給屬性提供的方法,如果沒有則為。該方法返回值被用作屬性值。值得注意的是屬性描述符必須是數(shù)據(jù)描述符或者存取描述符兩種形式之一,不能同時是兩者。可以忽略方法的返回值。
前言
我們或多或少都聽過“數(shù)據(jù)綁定”這個詞,“數(shù)據(jù)綁定”的關(guān)鍵在于監(jiān)聽數(shù)據(jù)的變化,可是對于這樣一個對象:var obj = {value: 1},我們該怎么知道 obj 發(fā)生了改變呢?
definePropetyES5 提供了 Object.defineProperty 方法,該方法可以在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性,并返回這個對象。
語法
Object.defineProperty(obj, prop, descriptor)
參數(shù)
obj: 要在其上定義屬性的對象。 prop: 要定義或修改的屬性的名稱。 descriptor: 將被定義或修改的屬性的描述符。
舉個例子:
var obj = {}; Object.defineProperty(obj, "num", { value : 1, writable : true, enumerable : true, configurable : true }); // 對象 obj 擁有屬性 num,值為 1
雖然我們可以直接添加屬性和值,但是使用這種方式,我們能進(jìn)行更多的配置。
函數(shù)的第三個參數(shù) descriptor 所表示的屬性描述符有兩種形式:數(shù)據(jù)描述符和存取描述符。
兩者均具有以下兩種鍵值:
configurable
當(dāng)且僅當(dāng)該屬性的 configurable 為 true 時,該屬性描述符才能夠被改變,也能夠被刪除。默認(rèn)為 false。
enumerable
當(dāng)且僅當(dāng)該屬性的 enumerable 為 true 時,該屬性才能夠出現(xiàn)在對象的枚舉屬性中。默認(rèn)為 false。
數(shù)據(jù)描述符同時具有以下可選鍵值:
value
該屬性對應(yīng)的值??梢允侨魏斡行У?JavaScript 值(數(shù)值,對象,函數(shù)等)。默認(rèn)為 undefined。
writable
當(dāng)且僅當(dāng)該屬性的 writable 為 true 時,該屬性才能被賦值運算符改變。默認(rèn)為 false。
存取描述符同時具有以下可選鍵值:
get
一個給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。該方法返回值被用作屬性值。默認(rèn)為 undefined。
set
一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。該方法將接受唯一參數(shù),并將該參數(shù)的新值分配給該屬性。默認(rèn)為 undefined。
值得注意的是:
屬性描述符必須是數(shù)據(jù)描述符或者存取描述符兩種形式之一,不能同時是兩者。這就意味著你可以:
Object.defineProperty({}, "num", { value: 1, writable: true, enumerable: true, configurable: true });
也可以:
var value = 1; Object.defineProperty({}, "num", { get : function(){ return value; }, set : function(newValue){ value = newValue; }, enumerable : true, configurable : true });
但是不可以:
// 報錯 Object.defineProperty({}, "num", { value: 1, get: function() { return 1; } });
此外,所有的屬性描述符都是非必須的,但是 descriptor 這個字段是必須的,如果不進(jìn)行任何配置,你可以這樣:
var obj = Object.defineProperty({}, "num", {}); console.log(obj.num); // undefinedSetters 和 Getters
之所以講到 defineProperty,是因為我們要使用存取描述符中的 get 和 set,這兩個方法又被稱為 getter 和 setter。由 getter 和 setter 定義的屬性稱做”存取器屬性“。
當(dāng)程序查詢存取器屬性的值時,JavaScript 調(diào)用 getter方法。這個方法的返回值就是屬性存取表達(dá)式的值。當(dāng)程序設(shè)置一個存取器屬性的值時,JavaScript 調(diào)用 setter 方法,將賦值表達(dá)式右側(cè)的值當(dāng)做參數(shù)傳入 setter。從某種意義上講,這個方法負(fù)責(zé)“設(shè)置”屬性值??梢院雎?setter 方法的返回值。
舉個例子:
var obj = {}, value = null; Object.defineProperty(obj, "num", { get: function(){ console.log("執(zhí)行了 get 操作") return value; }, set: function(newValue) { console.log("執(zhí)行了 set 操作") value = newValue; } }) obj.value = 1 // 執(zhí)行了 set 操作 console.log(obj.value); // 執(zhí)行了 get 操作 // 1
這不就是我們要的監(jiān)控數(shù)據(jù)改變的方法嗎?我們再來封裝一下:
function Archiver() { var value = null; // archive n. 檔案 var archive = []; Object.defineProperty(this, "num", { get: function() { console.log("執(zhí)行了 get 操作") return value; }, set: function(value) { console.log("執(zhí)行了 set 操作") value = value; archive.push({ val: value }); } }); this.getArchive = function() { return archive; }; } var arc = new Archiver(); arc.num; // 執(zhí)行了 get 操作 arc.num = 11; // 執(zhí)行了 set 操作 arc.num = 13; // 執(zhí)行了 set 操作 console.log(arc.getArchive()); // [{ val: 11 }, { val: 13 }]watch API
既然可以監(jiān)控數(shù)據(jù)的改變,那我可以這樣設(shè)想,即當(dāng)數(shù)據(jù)改變的時候,自動進(jìn)行渲染工作。舉個例子:
HTML 中有個 span 標(biāo)簽和 button 標(biāo)簽
1
當(dāng)點擊按鈕的時候,span 標(biāo)簽里的值加 1。
傳統(tǒng)的做法是:
document.getElementById("button").addEventListener("click", function(){ var container = document.getElementById("container"); container.innerHTML = Number(container.innerHTML) + 1; });
如果使用了 defineProperty:
var obj = { value: 1 } // 儲存 obj.value 的值 var value = 1; Object.defineProperty(obj, "value", { get: function() { return value; }, set: function(newValue) { value = newValue; document.getElementById("container").innerHTML = newValue; } }); document.getElementById("button").addEventListener("click", function() { obj.value += 1; });
代碼看似增多了,但是當(dāng)我們需要改變 span 標(biāo)簽里的值的時候,直接修改 obj.value 的值就可以了。
然而,現(xiàn)在的寫法,我們還需要多帶帶聲明一個變量存儲 obj.value 的值,因為如果你在 set 中直接 obj.value = newValue 就會陷入無限的循環(huán)中。此外,我們可能需要監(jiān)控很多屬性值的改變,要是一個一個寫,也很累吶,所以我們簡單寫個 watch 函數(shù)。使用效果如下:
var obj = { value: 1 } watch(obj, "num", function(newvalue){ document.getElementById("container").innerHTML = newvalue; }) document.getElementById("button").addEventListener("click", function(){ obj.value += 1 });
我們來寫下這個 watch 函數(shù):
(function(){ var root = this; function watch(obj, name, func){ var value = obj[name]; Object.defineProperty(obj, name, { get: function() { return value; }, set: function(newValue) { value = newValue; func(value) } }); if (value) obj[name] = value } this.watch = watch; })()
現(xiàn)在我們已經(jīng)可以監(jiān)控對象屬性值的改變,并且可以根據(jù)屬性值的改變,添加回調(diào)函數(shù),棒棒噠~
proxy使用 defineProperty 只能重定義屬性的讀?。╣et)和設(shè)置(set)行為,到了 ES6,提供了 Proxy,可以重定義更多的行為,比如 in、delete、函數(shù)調(diào)用等更多行為。
Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,ES6 原生提供 Proxy 構(gòu)造函數(shù),用來生成 Proxy 實例。我們來看看它的語法:
var proxy = new Proxy(target, handler);
proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數(shù)表示所要攔截的目標(biāo)對象,handler參數(shù)也是一個對象,用來定制攔截行為。
var proxy = new Proxy({}, { get: function(obj, prop) { console.log("設(shè)置 get 操作") return obj[prop]; }, set: function(obj, prop, value) { console.log("設(shè)置 set 操作") obj[prop] = value; } }); proxy.time = 35; // 設(shè)置 set 操作 console.log(proxy.time); // 設(shè)置 get 操作 // 35
除了 get 和 set 之外,proxy 可以攔截多達(dá) 13 種操作,比如 has(target, propKey),可以攔截 propKey in proxy 的操作,返回一個布爾值。
// 使用 has 方法隱藏某些屬性,不被 in 運算符發(fā)現(xiàn) var handler = { has (target, key) { if (key[0] === "_") { return false; } return key in target; } }; var target = { _prop: "foo", prop: "foo" }; var proxy = new Proxy(target, handler); console.log("_prop" in proxy); // false
又比如說 apply 方法攔截函數(shù)的調(diào)用、call 和 apply 操作。
apply 方法可以接受三個參數(shù),分別是目標(biāo)對象、目標(biāo)對象的上下文對象(this)和目標(biāo)對象的參數(shù)數(shù)組,不過這里我們簡單演示一下:
var target = function () { return "I am the target"; }; var handler = { apply: function () { return "I am the proxy"; } }; var p = new Proxy(target, handler); p(); // "I am the proxy"
又比如說 ownKeys 方法可以攔截對象自身屬性的讀取操作。具體來說,攔截以下操作:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
下面的例子是攔截第一個字符為下劃線的屬性名,不讓它被 for of 遍歷到。
let target = { _bar: "foo", _prop: "bar", prop: "baz" }; let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== "_"); } }; let proxy = new Proxy(target, handler); for (let key of Object.keys(proxy)) { console.log(target[key]); } // "baz"
更多的攔截行為可以查看阮一峰老師的 《ECMAScript 6 入門》
值得注意的是,proxy 的最大問題在于瀏覽器支持度不夠,而且很多效果無法使用 poilyfill 來彌補(bǔ)。
watch API 優(yōu)化我們使用 proxy 再來寫一下 watch 函數(shù)。使用效果如下:
(function() { var root = this; function watch(target, func) { var proxy = new Proxy(target, { get: function(target, prop) { return target[prop]; }, set: function(target, prop, value) { target[prop] = value; func(prop, value); } }); if(target[name]) proxy[name] = value; return proxy; } this.watch = watch; })() var obj = { value: 1 } var newObj = watch(obj, function(key, newvalue) { if (key == "value") document.getElementById("container").innerHTML = newvalue; }) document.getElementById("button").addEventListener("click", function() { newObj.value += 1 });
我們也可以發(fā)現(xiàn),使用 defineProperty 和 proxy 的區(qū)別,當(dāng)使用 defineProperty,我們修改原來的 obj 對象就可以觸發(fā)攔截,而使用 proxy,就必須修改代理對象,即 Proxy 的實例才可以觸發(fā)攔截。
ES6 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預(yù)計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標(biāo)簽?zāi)0?、箭頭函數(shù)、Symbol、Set、Map 以及 Promise 的模擬實現(xiàn)、模塊加載方案、異步處理等內(nèi)容。
如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,請?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/99133.html
摘要:通過對這些底層內(nèi)置對象的代理陷阱和反射函數(shù),讓開發(fā)者能進(jìn)一步接近引擎的能力。顯然,與要求代理目標(biāo)對象必須是一個函數(shù),這兩個代理陷阱在函數(shù)的執(zhí)行方式上開啟了很多的可能性,結(jié)合使用就可以完全控制任意的代理目標(biāo)函數(shù)的行為。 代理(Proxy)可以攔截并改變 JS 引擎的底層操作,如數(shù)據(jù)讀取、屬性定義、函數(shù)構(gòu)造等一系列操作。ES6 通過對這些底層內(nèi)置對象的代理陷阱和反射函數(shù),讓開發(fā)者能進(jìn)一步接...
摘要:查找并返回對象的屬性例例屬性部署了讀取函數(shù)返回的是的參數(shù)對象注意如果的第一個參數(shù)不是對象,則會報錯。它返回一個布爾值,表示是否操作成功用于返回對象的所有屬性使用和實現(xiàn)觀察者模式請參考觀察者模式 1、什么是Reflect?為操作對象而提供的新API 2、為什么要設(shè)計Reflect?(1)將Object對象的屬于語言內(nèi)部的方法放到Reflect對象上,即從Reflect對象上拿Object...
摘要:的出現(xiàn),使用內(nèi)建對象的繼承得以實現(xiàn)。屬性不存在拋出異常是取值操作,而就是賦值操作,可以對屬性值進(jìn)行驗證。屬性必須為數(shù)字拋出異常接受兩個參數(shù)被讀取屬性的原對象,即代理的目標(biāo)。這個可以攔截內(nèi)部方法,通過返回數(shù)組的值可以覆寫其行為。 Proxy & Reflect extends的出現(xiàn),使用內(nèi)建對象的繼承得以實現(xiàn)。Proxy可以攔截JS引擎內(nèi)部目標(biāo)的底層對象操作,這些底層操作被攔截后會觸發(fā)響...
摘要:使用陷阱驗證屬性用于接收屬性代理的目標(biāo)的對象要寫入的屬性鍵被寫入的屬性的值操作發(fā)生的對象通常是代理屬性必須是數(shù)字拋錯用陷阱驗證對象結(jié)構(gòu)屬性不存在拋出錯誤使用陷阱隱藏已有屬性可以用操作符來檢測給定對象中是否包含有某個屬性,如果自有屬性或原型屬 使用set陷阱驗證屬性 let target = { name: target } let proxy = new Proxy(targe...
閱讀 1159·2021-11-16 11:45
閱讀 2806·2021-09-27 13:59
閱讀 1388·2021-08-31 09:38
閱讀 3215·2019-08-30 15:52
閱讀 1374·2019-08-29 13:46
閱讀 2144·2019-08-29 11:23
閱讀 1756·2019-08-26 13:47
閱讀 2600·2019-08-26 11:54