理解Object.defineProperty方法:https://blog.csdn.net/sinat_36521655/article/details/80403180
關於Object.defineProperty 的基礎知識:https://www.cnblogs.com/zjjDaily/p/11227623.html
Object.defineProperty 這個方法大家耳熟能詳,可以對 對象的屬性進行添加或修改的操作。即可以進行 數據劫持 。vue就是通過這個方法來劫持數據的。
平時我們創建對象的時候,一般通過對象字面量的方式創建:
var student = { name:"小明", age:10 }
對象的屬性在創建的時候,都帶有一些特征值(特性),JS通過這些特征值來定義它們的行為。ECMA-262 第 5 版在定義只有內部才用的特性(attribute)時,描述了屬性(property)的各種特征。ECMA-262 定義這些特性是為了實現 JavaScript 引擎用的,因此在 JavaScript 中不能直接訪問它們。為了表示特性是內部值,該規范把它們放在了兩對兒方括號中,例如[[Enumerable]]。ECMAScript 中有兩種屬性:數據屬性和訪問器屬性。-------------《JavaScript高級程序設計(第三版)》 第六章
- [[Configurable]] :能否通過 delete 刪除屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。
-
[[Enumerable]]:能否通過 for ·· in 或者 Object.keys() 枚舉。
-
[[Writable]]:屬性的值能否被修改。
-
[[Value]]:屬性的值,可以是任何有效的 JavaScript 值(數值,對象,函數等)
- [[Configurable]] :能否通過 delete 刪除屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。
- [[Enumerable]]:能否通過 for ·· in 或者 Object.keys() 枚舉。
- [[Get]]:在讀取屬性時調用的函數。
- [[Set]]:在寫入屬性時調用的函數。
1) 當使用對象字面量或者構造函數的形式創建屬性的時候,enumerable 、configurable 、 writable都為 true ,value、get、set都為undefined 。所以平時定義對象的時候,我們可以隨意增刪改查。
2) 當使用Object.defineProperty、Object.defineProperties 或 Object.create 函數的情況下添加的屬性。enumerable 、configurable、writable都為 false;value、get、set都為undefined。
可以通過Object.getOwnPropertyDescriptor(對象名,屬性名)來獲取屬性描述符的默認值。
Object.defineProperty :
Object.create :
怎么修改默認屬性默認值?
這種兩個方括號 [[ ]] 的方式,我感覺就和指向對象的原型的指針類似,ECMA-262 第 5 版 稱這個指針為 [[prototype]] ,也是沒有標准的方式訪問,但是主流瀏覽器都提供了__proto__屬性來訪問。
這上面的屬性描述符都有自己的默認值,但是如果我想修改某些數據描述符的默認值呢?它並不能直接訪問啊,比如 obj.age.[[Enumerable]] 這樣是不行的。既然不能直接訪問,那么我怎么去修改對象中某些屬性的指定特性呢?
以前可以使用非標准的方式: 對象.__defineGetter__( "屬性", function(){} ) 或者 對象.__defineSetter__( "屬性", function(){} ) 。不過這方法已經被廢棄了,雖然有些瀏覽器還支持,但是不建議使用。
這時候就需要用到 Object.defineProperty 這個方法了。
- obj,即需要修改屬性的對象。必填。
- prop,需要修改的屬性。必填。
- descriptor,屬性修飾符配置項,是個對象。屬性修飾符不填的情況下,這個參數也不能少,最少也要是一個 { } 空對象。
- 最終返回處理后的 obj 對象
- configurable
- enumerable
- writable
- value
b) 存取描述符
- configurable
- enumerable
- get
- set
上面的這些屬性都是可以直接訪問配置的。
數據描述符和存取描述符用法都很簡單。不過需要注意的是:
- 數據屬性符的writable或value 與 存取描述符的get或set不能同時存在 。會報錯。
- 存取描述符的get與set也可以不同時存在,如果只指定get表示屬性不能寫(意思進行賦值操作,最后屬性還是為undefined,即使最初屬性定義了初始值),只指定set表示屬性不能讀(意思是獲取屬性的時候是undefined,整個對象都為{ }。即使最初定義了一些屬性的)。
- 存取描述符的get與set是個函數,函數里的 this 指向的是 需要修改屬性的對象即obj
還有個Object.defineProperties() 可以劫持多個屬性。有興趣的可以去 MDN 看看
如果對象的屬性中還有對象,那么這時候需要深層遍歷,一般的方法是:
var obj = { name:"zjj", sex:'male', money:100, info:{ face:'smart' } } observe(obj) console.log(obj)
obj.sex = 'female' obj.info.face = 20; obj.info.hobit = 'girl';
console.log(obj) function observe(target){ if (!target || typeof target !== 'object') return; Object.keys(target).forEach(function(val){ defineProp(target,target[val],val) }) } function defineProp(curObj,curVal,curKey){ observe(curVal) //再次遍歷子屬性 Object.defineProperty(curObj,curKey,{ enumerable:true, configurable:true, get:function(){ console.log('獲取了屬性',curVal) return curVal }, set:function(newData){ console.log('設置了屬性',newData) curObj = newData; } }) }
這樣,目標對象中的屬性的值為對象的時候也能進行數據劫持了。不過我疑惑的點是:添加不存在的屬性時,為什么調用的是get方法???后面搞懂了再來解決這個問題
Object.defineProperty的缺點:
- 無法監控到數組下標的變化,導致直接通過數組的下標給數組設置值,不能實時響應。所以vue才設置了7個變異數組(push、pop、shift、unshift、splice、sort、reverse)的 hack 方法來解決問題。
- 只能劫持對象的屬性,因此我們需要對每個對象的每個屬性進行遍歷。如果能直接劫持一個對象,就不需要遞歸 + 遍歷了。所以 vue3.0 會使用 Proxy 來替代Object.defineProperty
Proxy:代理
聽說vue3.0 會用 proxy 替代 Object.defineProperty()方法。所以預先了解一些用法是有必要的。
proxy 能夠直接 劫持整個對象,而不是對象的屬性,並且劫持的方法有多種。而且最后會返回劫持后的新對象。所以相對來講,這個方法還是挺好用的。不過兼容性不太好。
關於proxy的介紹與用法,可以看看 阮一峰老師的 這篇文章
題外話:ECMAScript 與 JavaScript 的關系
參考:這里
Netscape 公司最初創建了一個用於瀏覽器的腳本語言,后與Sun 公司(創建了Java)聯合發布了該腳本語言,命名為Javascript;后來微軟也出了一個 JScript,用於IE3.0瀏覽器;還有Cenvi的ScriptEase。於是Netscape 公司決定將 JavaScript 提交給國際標准化組織 ECMA,希望 JavaScript 能夠成為國際標准。
1997年7月,ECMA的TC93(39號技術委員會)發布262號標准文件(ECMA-262)的第一版,規定了瀏覽器腳本語言的標准。由於商標和其它協議的原因,只有Netscape 公司能使用Javascript 這個名稱,所以最后將這種語言稱為 ECMAScript。而現在我們所說的 JavaScript 是 ECMAScript + DOM + BOM的集合。DOM和BOM是W3C制定的規范。
現在說的ES5就是 ECMAScript 5.0版,而ES6就是 ECMAScript 6 后更名為 ECMAScript 2015(簡稱ES2015),后面每一年的6月份都會發布一個新的版本,不過增加的內容並不多。ES7(ES2016)、ES8 (ES2017)、ES9 (ES2018),現在2019.7月了,這個時候都已經出了ES10(ES2019)。不過ES10還是一個草案,並沒有多少瀏覽器支持。主流的都是ES5 和 ES6。