本篇實現的僅僅是物件監聽,不包含陣列。關於陣列的下一篇會講。
一、用什麼來監聽物件?
Vue給了我們很好的回答,那就是defineProperty。defineProperty方法有以下幾個引數:
- obj: 你要設定屬性的物件;
- propName: 你設定的屬性的名稱;
- descriptor: 描述符物件;
這個descriptor物件可以設定的屬性就比較多了:
- value: 設定屬性的值;
- writable: 預設為false,如果該值為false,則不能再修改屬性的值;
- enumerable: 預設為false,如果該值為false,則不能被列舉;
- configurable: 預設為false,如果該值為false,那麼descriptor不能再被修改,也不能使用delete;
- get與set:存取器方法,預設為undefined。
其他的descriptor物件理解應該不難,看了MDN上的教程,應該會發現對於set和get方法的使用,總是會在外部宣告一個變數來維護值的變化,是不是很變扭,然後就會有人嘗試用this來呼叫屬性,這時就會報執行棧溢位,因為它遞迴的執行自己,導致棧溢位。
這裡我們可以通過set和get內部維護的'_'開頭的私有變數來解決:
const obj = {};
Object.defineProperty(obj,'name', {
get () {
console.log('讀取name的值為 => ' + this._name);
return this._name;
},
set (newValue) {
this._name = newValue;
console.log('設定name的值為 => ' + this._name);
}
})
obj.name = 'xiaoming'; // '設定name的值為 => xiaoming'
obj.name; // '讀取name的值為 => xiaoming'複製程式碼
兩種方法看著喜好用吧。
二、實現物件的監聽
首先我們得有一個物件:
const obj = {
a: 1,
b: {
c: 2,
d: 3
}
};複製程式碼
接下來我們要將它變成一個響應式物件,讓它的取值與賦值操作多在我們的監聽下,首先我們要通過一個Observer來包裝我們需要響應化的物件:
function Observer(value) {
/**
* 建構函式防止沒有使用'new'關鍵字建立
*/
if (!(this instanceof Observer)) {
return new Observer(value);
}
this.value = value;
// 這一篇僅僅討論物件的監聽
if (!Array.isArray(value)) {
this.walk(value);
}
}
Observer.prototype.walk = function (obj) {
const keys = Object.keys(obj);
for (let i = 0, max = keys.length; i < max; i++) {
const key = keys[i];
//將物件響應化
defineReactive(obj, key, obj[key]);
}
}複製程式碼
接下來核心就是採用defineProperty方法來監聽set和get方法,但是這裡我們要注意的是採用遞迴建立Observer物件的方式,解決物件巢狀的問題:
function defineReactive(obj, key, val) {
//獲取物件的描述器物件
const property = Object.getOwnPropertyDescriptor(obj, key);
//這裡上面強調了configurable屬性的重要性
if (property && property.configurable === false) {
return;
}
const setter = property && property.set;
const getter = property && property.get;
//解決物件巢狀的問題
observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get () {
const value = getter ? getter.call(obj) : val;
log('讀取',obj,key,value);
return value;
},
set (newValue) {
const value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newValue);
} else {
val = newValue;
}
log('設定',obj,key,value);
}
})
}
function observe(obj) {
if (!isObject(obj)) {
return;
}
return new Observer(obj);
}複製程式碼
這裡基本上物件的監聽已經完成了,下面來試試:
Observer(obj);
obj.b.d = 10;複製程式碼
但是這裡還存在一個問題,因為我們還有這種操作:
obj.f = 10;複製程式碼
這時obj.f不再被我們監聽,對於這種情況在Vue中我們要通過Vue.set方法設定,這裡我們也可以寫了set方法:
function set(target, key, val) {
if(!target.hasOwnProperty(key)) {
target[key] = val;
defineReactive(target, key, val);
}
}複製程式碼
這裡是個簡易的set方法,主要思想就是使新屬性響應化。
三、總結
通過原始碼的學習,使你在使用Vue的一些api更加得心應手。
PS:原始碼用的flow.js,看起來有點費勁,但是我應該會堅持下去。(下一篇會講陣列的監聽)