Vue響應式原理之Object.defineProperty()

已經用Vue好幾年了,也知道Vue 2.x的底層響應式用的是Object.defineProperty()這個方法來實現的,可一直沒有去深入瞭解過這個方法,今天就讓我們一起去探個究竟。

一、對象屬性的增刪改查

在開始之前,我們先簡單瞭解下如何對對象進行增、刪、改、查等操作。

爲了更加直觀,我們直接上個簡單例子。

首先創建一個空對象data

let data = {};

然後我們可以對其進行增刪改查操作。

1. 新增屬性

data.text = '想學習更多前端知識,請關注公衆號:前端微站';

2. 刪除屬性

delete data.text;

3. 修改屬性

data.text = '想學習更多前端知識,請關注公衆號:前端微站';  // 新增屬性
data.text = '關注公衆號前端微站,學習更多前端知識';  // 修改屬性

4. 查詢屬性

查詢有兩種,一種是通過屬性名(key)查詢屬性值(value), 另一種則正好相反,是通過屬性值(value)來查詢屬性名(key),不過這兩種查詢都可以通過for...in來遍歷查詢。

// 先給對象再新增一個屬性
data.name = '前端王睿';

// 通過 key 查詢 value
for(let key in data){
  if(key === 'name'){
    console.log(data[key]);  // 前端王睿
  }
}

// 通過 value 查詢 key
for(let key in data){
  if(data[key] === '前端王睿'){
    console.log(key);  // name
  }
}

二、對象的屬性描述符

上面所講到的對象操作應該是司空見慣、衆所周知的了,可是,你說好不容易找了個對象,說讓你改就改,讓你刪就刪,多沒面子!有沒有什麼辦法,我創建好了對象之後,就不讓再隨意改動呢,至少要我自己能夠配置呀!

當然有!這時Object.defineProperty()就派上用場了!我們可以通過Object.defineProperty()對對象屬性進行描述,也就是告訴我們哪個屬性是不能改的,哪個屬性是不能刪的,等等。這裏就要講到對象的屬性描述符,而屬性描述符又分爲 數據描述符存取描述符。數據描述符是一個具有值的屬性,該值可以是可寫的,也可以是不可寫的。存取描述符是由 getter 函數和 setter 函數所描述的屬性。

1. 數據描述符

我們同樣按照增、刪、改、查的順序對其進行一一列舉,可以看出通過Object.defineProperty()方式新增的對象屬性,默認都是不可被刪除、修改和枚舉的。

value
表示該對象屬性的值,默認值爲undefined。例如:

let data = {};
Object.defineProperty(data,'text',{
  value: '想學習更多前端知識,請關注公衆號:前端微站'
});
console.log(data);  //  {text: '想學習更多前端知識,請關注公衆號:前端微站'}

這看起來跟直接通過data.text = '想學習更多前端知識,請關注公衆號:前端微站'新增對象屬性的效果一樣,具體區別請接着往下看。

configurable
默認值爲false,表示該對象屬性不能被刪除,只有爲true時纔可刪除。例如:

let data = {};
Object.defineProperty(data,'text',{
  value: '想學習更多前端知識,請關注公衆號:前端微站'
});
delete data.text;  // 刪除失敗
console.log(data.text);   // 想學習更多前端知識,請關注公衆號:前端微站

刪除沒效果嘛!這時只需設置configurable值爲true即可繼續愉快地刪除了。

let data = {};
Object.defineProperty(data,'text',{
  value: '想學習更多前端知識,請關注公衆號:前端微站',
  configurable: true
});
delete data.text;  // 刪除成功
console.log(data.text);   // undefined

writable
默認值爲false,表示該對象屬性不能被修改,只有爲true時纔可修改。例如:

let data = {};
Object.defineProperty(data,'text',{
  value: '想學習更多前端知識,請關注公衆號:前端微站'
});
data.text = '關注公衆號前端微站,學習更多前端知識';  // 修改失敗
console.log(data.text);   // 想學習更多前端知識,請關注公衆號:前端微站

修改失敗了!這時給它設置writable值爲true即可繼續愉快地修改了。

let data = {};
Object.defineProperty(data,'text',{
  value: '想學習更多前端知識,請關注公衆號:前端微站',
  writable: true
});
data.text = '關注公衆號前端微站,學習更多前端知識';  // 修改成功
console.log(data.text);   // 關注公衆號前端微站,學習更多前端知識

enumerable
默認值爲false,表示該對象屬性不能被枚舉,只有爲true時纔可枚舉。例如:

let data = {
  text: '想學習更多前端知識,請關注公衆號:前端微站'
};
Object.defineProperty(data,'name',{
  value: '前端王睿'
});
console.log(data);  //  {text: "想學習更多前端知識,請關注公衆號:前端微站", name: "前端王睿"}
for(let key in data){
  console.log(data[key]);  // 想學習更多前端知識,請關注公衆號:前端微站
}

我們發現,data對象中雖然已經有兩個屬性,可我們發現最終卻只能遍歷出text這一個屬性。這時只需給name屬性設置enumerable值爲true即可愉快地枚舉出來了。

let data = {
  text: '想學習更多前端知識,請關注公衆號:前端微站'
};
Object.defineProperty(data,'name',{
  value: '前端王睿',
  enumerable: true  //  想學習更多前端知識,請關注公衆號:前端微站
});
console.log(data);  //  {text: "想學習更多前端知識,請關注公衆號:前端微站", name: "前端王睿"}
for(let key in data){
  console.log(data[key]);
  // 想學習更多前端知識,請關注公衆號:前端微站
  // 前端王睿
}

2. 存取描述符

以下這兩個函數就是Vue中用於進行數據監聽的Object.defineProperty()中屬性描述符的兩個核心方法。
get
屬性的 getter 函數,默認值爲undefined。當訪問該屬性時,會調用此函數,返回值會被用作該屬性的值。例如:

let data = {
  text: '想學習更多前端知識,請關注公衆號:前端微站'
};
Object.defineProperty(data,'text',{
  get(){
    return '關注公衆號前端微站,學習更多前端知識'
  }
});
console.log(data);  // {text: "關注公衆號前端微站,學習更多前端知識"}

可以看到,此時data其實已經被我們修改了!

set
屬性的 setter 函數,默認值爲undefined。當屬性值被修改時,會調用此函數,該方法接受一個參數,也就是被賦予的新值。例如:

let data = {
  text: '想學習更多前端知識,請關注公衆號:前端微站'
};
Object.defineProperty(data,'text',{
  set(value){
    console.log(value);  // 關注公衆號前端微站,學習更多前端知識
  }
});
data.text = '關注公衆號前端微站,學習更多前端知識';

*注意:valuewritable不能與存取描述符(getset)同時存在,不然會報錯!這是因爲兩者之間可能會存在互斥關係,例如:value值與get返回值不同,writablefalse而使用get卻可改變對象屬性的值,等等。

三、使用Object.defineProperty()寫個簡單的響應式渲染

<input id="input" type="text">
<p id="text"></p>
let oText = document.getElementById('text'),
    oInput = document.getElementById('input');
let data = {
  text: ''
};
Object.defineProperty(data,'text',{
  set(value){
    oText.innerHTML = value; // 當修改data.text值時,自動更改oText中的文字內容
  }
});
oInput.value = data.text = '前端微站';
oInput.addEventListener('keyup', function () {
  data.text = this.value;
});

重點總結:

Object.defineProperty()中的 數據描述符 可以用來禁止對象屬性的 刪除、修改和枚舉 操作
Object.defineProperty()中的 存取描述符 可以用來對 對象賦值獲取屬性值 進行攔截操作

結束語:

看我在這囉嗦了半天,可最終能不能掌握Object.defineProperty()的用法還是應該在實踐中多總結,而不是簡單地看完了就結束了。最後給大家安排個作業,利用Object.defineProperty()的存取描述符,實現一個超簡易的Vue,具體功能就是實現上面這個簡單的響應式渲染功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章