文章出處

我們常聽說vue是用getter與setter實現數據監控的,那么getter與setter到底是什么東西,它與defineProperty是什么關系,平時有哪些用處呢?本文將為大家一一道來。

對象的屬性

按照一貫的“由淺到深”行文原則,我們先溫習一下對象的屬性。我們知道對象有自身的屬性以及原型上的屬性,它們都可以通過obj.key這樣的方式訪問到。

要設置/修改對象的屬性也是很簡單的,只需obj.key='value'即可。要注意的是,如果key位于原型上,那么此時會在對象自身設置該值,而不是修改原型上的。

另外需要注意的是,原型上的屬性有時候會被for in給“不小心”遍歷出來,例如下面的代碼:

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

defineProperty是掛載在Object上的一個方法,作用是:為對象定義一個屬性,或是修改已有屬性的值,并設置該屬性的描述符。該方法返回修改后的對象。

如果沒有后半句作用的話,那它與obj.key = 'value'這種賦值語句沒什么兩樣。他的完整語法是這樣:Object.defineProperty(obj, prop, descriptor)

obj: 目標對象
prop: 屬性名稱
descriptor: 屬性描述符

前兩個就不必講了,需要重點理解的是第三參數。屬性描述符用于定義該屬性的一些特性。具體來講分了兩類:數據描述符(data descriptor)、訪問描述符(accessor descriptor).

這兩類描述符有兩個必選項:

  1. configurable
    從字面意思看它表示“可配置”,含義是:當它為true時,該屬性的描述符可被修改,并且該屬性可被delete刪除。同理,當它為false時,我們無法再次調用defineProperty去修改描述符,也不可通過delete刪除。

  2. enumerable
    從字面意思看它表示“可枚舉”,含義是:當它為true時,該屬性可被迭代器枚舉出來。比如使用for in或者是Object.keys。

接下來就是數據描述符(data descriptor)了,有兩個:

  1. value
    這個就是該屬性的值啦,即通過obj.key訪問時返回。任何js數據類型都可以使用(number,string,object,function等)。

  2. 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

所謂getter與setter其實是兩個概念,并沒有這樣的屬性。與之對應的是兩個訪問描述符(access descriptor):

  1. get
    它是一個函數,訪問該屬性時會自動調用,函數的返回值即為該屬性的value。默認為undefined。

你可能會想,既有value又有get函數,那么屬性的值是什么呢?那你就想多了,這種情況在定義的時候就直接報錯了,本身邏輯就矛盾嘛。

  1. set
    它是一個函數,為該屬性賦值時會自動調用,并且新值會被當做參數傳入。

看到這里你可能就眼前一亮了,為屬性賦值的時候會自動執行一個函數,那豈不是就能監控到數據的變化,從而實現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的方法我教給你了。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()