咱們常據說vue是用getter與setter實現數據監控的,那麼getter與setter究竟是什麼東西,它與defineProperty是什麼關係,平時有哪些用處呢?本文將爲你們一一道來。javascript
按照一向的「由淺到深」行文原則,咱們先溫習一下對象的屬性。咱們知道對象有自身的屬性以及原型上的屬性,它們均可以經過obj.key這樣的方式訪問到。vue
要設置/修改對象的屬性也是很簡單的,只需obj.key='value'便可。要注意的是,若是key位於原型上,那麼此時會在對象自身設置該值,而不是修改原型上的。java
另外須要注意的是,原型上的屬性有時候會被for in給「不當心」遍歷出來,例以下面的代碼:chrome
var arr = [1,2,3]; arr.__proto__.test = 4; for(i in arr){ console.log(arr[i]); } //輸出:1234
因此咱們通常在用for in的時候都要加上hasOwnProperty判斷,或者是拋棄for in,用forEach.瀏覽器
defineProperty是掛載在Object上的一個方法,做用是:爲對象定義一個屬性,或是修改已有屬性的值,並設置該屬性的描述符。該方法返回修改後的對象。框架
若是沒有後半句做用的話,那它與obj.key = 'value'這種賦值語句沒什麼兩樣。他的完整語法是這樣:Object.defineProperty(obj, prop, descriptor)
iphone
obj: 目標對象
prop: 屬性名稱
descriptor: 屬性描述符mvvm
前兩個就沒必要講了,須要重點理解的是第三參數。屬性描述符用於定義該屬性的一些特性。具體來說分了兩類:數據描述符(data descriptor)、訪問描述符(accessor descriptor).函數
這兩類描述符有兩個必選項:插件
configurable
從字面意思看它表示「可配置」,含義是:當它爲true時,該屬性的描述符可被修改,而且該屬性可被delete刪除。同理,當它爲false時,咱們沒法再次調用defineProperty去修改描述符,也不可經過delete刪除。
enumerable
從字面意思看它表示「可枚舉」,含義是:當它爲true時,該屬性可被迭代器枚舉出來。好比使用for in或者是Object.keys。
接下來就是數據描述符(data descriptor)了,有兩個:
value
這個就是該屬性的值啦,即經過obj.key訪問時返回。任何js數據類型均可以使用(number,string,object,function等)。
writable
這個也很好理解,表示該屬性是否可寫。當它爲false時,屬性不可被任何賦值語句重寫。然而,此時還能夠調用defineProperty來修改value,固然前提是configurable爲true啦。
剩下的就是訪問描述符啦,先賣個關子講兩個注意事項。
通常狀況,咱們會建立一個descriptor對象,而後傳給defineProperty方法。以下:
var descriptor = { writable: false } Object.defineProperty(obj, 'key', descriptor);
這種狀況是有風險的,若是descriptor的原型上面有相關特性,也會經過原型鏈被訪問到,算入在對key的定義中。好比:
descriptor.__proto__.enumerable = true; Object.defineProperty(obj, 'key', descriptor); Object.getOwnPropertyDescriptor(obj,'key'); //返回的enumerable爲true
爲了不發生這樣的意外狀況,官方建議使用Object.freeze凍結對象,或者是使用Object.create(null)建立一個純淨的對象(不含原型)來使用。
接下來的注意點是默認值,首先咱們會想普通的賦值語句會生成怎樣的描述符,如obj.key="value"
。
可使用Object.getOwnPropertyDescriptor來返回一個屬性的描述符:
obj = {}; obj.key = "value"; Object.getOwnPropertyDescriptor(obj, 'key'); /*輸出 { configurable:true, enumerable:true, value:"value", writable:true, } */
這也是複合咱們預期的,經過賦值語句添加的屬性,相關描述符都爲true,可寫可配置可枚舉。可是使用defineProperty定義的屬性,默認值就不是這樣了,其規則是這樣的:
configurable: false
enumerable: false
writable: false
value: undefined
因此這裏仍是要注意下的,使用的時候把描述符寫全,省得默認都成false了。
所謂getter與setter實際上是兩個概念,並無這樣的屬性。與之對應的是兩個訪問描述符(access descriptor):
你可能會想,既有value又有get函數,那麼屬性的值是什麼呢?那你就想多了,這種狀況在定義的時候就直接報錯了,自己邏輯就矛盾嘛。
看到這裏你可能就眼前一亮了,爲屬性賦值的時候會自動執行一個函數,那豈不是就能監控到數據的變化,從而實現mvvm的雙向綁定?其實vue的數據監控用到的核心原理也就是這個啦。若是你用過knockout可能感覺會更深,knockout能作到在IE6都支持雙向綁定,就是強制讓屬性值爲函數類型,必須手動執行函數才能拿到值。
還好如今有了瀏覽器的默認支持,ES5開始就支持gettter、setter了,如今移動端基本徹底可用,pc端須要IE9+。
這麼好用的方法,咱們平時好像也不怎麼用呀?寫業務代碼可能用到的確實少,可是當你要寫一個公共模塊乃至寫一個框架時,就可能用到啦。
好比你寫一個公共模塊,會往window上掛一些全局屬性,而且你不但願別人在其餘地方不當心覆蓋這個屬性,那就能夠用defineProperty讓該屬性不可寫、不可配置。貼一個咱們項目中的代碼:
//向全局掛載通用方法 for(let key in methods){ if(methods.hasOwnProperty(key)){ Object.defineProperty(WIN, key, { value : methods[key] }); } }
另一個用途呢,就是你本身想幹壞事。覆蓋別人寫的代碼,好比寫chrome插件刷頁面。或者說是想篡改瀏覽器的一些信息。
好比你想把瀏覽器的userAgent給改了,直接寫navigator.userAgent = 'iPhoneX'
.你再輸出一下userAgent,發現並無修改。這是爲何呢?咱們用這行代碼看一下:
Object.getOwnPropertyDescriptor(window, 'navigator'); //輸出 { configurable:true, enumerable:true, get:ƒ (), set:undefined }
緣由就找到了,navigator是有setter的,每次取值總會執行這個set函數來作返回。可是好消息是什麼呢?configurable爲true,那就意味這咱們能夠經過defineProperty來修改這個屬性,代碼就至關簡單了:
Object.defineProperty(navigator, 'userAgent', {get: function(){return 'iphoneX'}}) console.log(navigator.userAgent); //輸出iphoneX
喏,篡改瀏覽器userAgent的方法我教給你了。