接觸過Vue的人基本都知道,Vue的數據綁定是通過ES5的getter和setter實現的,查看Vue源碼,目錄結構如下
observer目錄下面的模塊實現Vue的數據綁定功能,從文件夾和文件的命名來看可以知道採用的是觀察者設計模式,看來有必要對常見的幾種設計模式補補課了。
這裏我不想詳細分析整個數據綁定的實現過程,網上分析的很好的文章有很多,我只想說說我在看完這部分代碼後的想法。Vue數據綁定其實就是通過對數據進行遞歸操作最後就是對兩種類型的數據進行getter和setter,一種就是除Object外的基本數據類型,另外一種就是數組。對於第一類是通過ES5的 Object.defineProperty添加get和set屬性。第二類就是通過自定義方法dependArray 專門對數組進行操作。打開看dependArray 的源碼:
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
代碼看起來超級簡潔,一點多餘的都看不出來,看來我平時在寫代碼的時候應該多思考一下,儘量減少冗餘的代碼。其實作者就是對數組的push,pop,shift, unshift,splice, sort,reverse七種方法進行了重寫,這裏需要注意的是作者並沒有重寫Array累的原型方法,而是通過改變Array實例的__proto__屬性,這樣避免了對Array原始屬性的破壞而造成的全局污染。但是如果我們操作數組的時候不用這幾種方法,例如:
this.person.favorite[0] = "apple";
這樣視圖是不會發生出發視圖改變的。居然這樣可不可給素組對象裏面的屬性加上set和get屬性呢?首先看看數組的結構:
看着是不是有點眼熟,他裏面也是以鍵值對的形式存在的。那麼以爲着我們是不是也可以像普通對象那樣去處理呢?身爲程序猿,要有no zuo no die 的精神,試試爲數組加上get和set屬性:
function Observer(value) {
this.value = value;
this.walk(value);
}
Observer.prototype.walk = function(obj) {
var keys = Object.keys(obj);
for(var i = 0; i < keys.length; i++) {
// 給所有屬性添加 getter、setter
defineReactive(obj, keys[i], obj[keys[i]]);
}
};
var dep = [];
function defineReactive(obj, key, val) {
// 有自定義的 property,則用自定義的 property
var property = Object.getOwnPropertyDescriptor(obj, key);
if(property && property.configurable === false) {
return;
}
var getter = property && property.get;
var setter = property && property.set;
// 遞歸的方式實現給屬性的屬性添加 getter、setter
var childOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
var value = getter ? getter.call(obj) : val;
dep.push(value);
return value;
},
set: function(newVal) {
var value = getter ? getter.call(obj) : val;
// set 值與原值相同,則不更新
if(newVal === value) {
return;
}
if(setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
// 給新賦值的屬性值的屬性添加 getter、setter
childOb = observe(newVal);
console.log("數組改變了位置["+key+"]的值改變了");
}
});
}
function observe(value) {
if(!value || typeof value !== 'object') {
return;
}
return new Observer(value);
}
運行結果:
看來可以啊!但是作者爲什麼不這樣幹呢?
下面我們看看如果我們改變一下person.fav的值:
這樣之後我們會發現當我們再次修改person.fav[0]位置上的值的時候,發現我們之前設置的setter屬性不起作用了。這個時候因爲對象的因爲在賦值爲[]的時候後數組的已經被刪除掉了,所以這個時候就不生效了,這是需要對新生成的數組重新進行definedProperty操作。但是當你重新定義後,你還是會遇見各種set無法生效的問題,比如我通過person.fav[100]=“xx”,這樣數組的長度就會達到100,或者直接通過person.fav.length=xx,這樣去增加或者減小數組的長度…當然我們可以打各種補丁去重新定義,但是這樣一樣每次數組變化我們都需要進行重新定義。這樣會嚴重影響前端的性能。
總結:
YY始終還是隻能停留在YY上,這樣是沒有必要的,因js的數組操作形式太多了,而且經常會對數組中的元素進行增加刪除操作,這樣會導致之前定義的key值無效,相反普通的json對象就不會經常增刪屬性,只是修改屬性的值。如果要對數組的key進行get和set操作,會嚴重影響前端的性能。