替換數(shù)據(jù)劫持對(duì)象
??上一篇實(shí)現(xiàn)了 mvvm 實(shí)現(xiàn)思路,可是不夠優(yōu)雅還有很多問題,我先解決這個(gè)問題數(shù)據(jù)劫持的問題。
之前的數(shù)據(jù)劫持
之前數(shù)據(jù)的劫持試是這么做的
// 重寫data 的 get set 更改數(shù)據(jù)的時(shí)候,觸發(fā)watch 更新視圖
myVue.prototype._observer = function (obj) {
var _this = this;
for (key in obj){ // 遍歷數(shù)據(jù)
//訂閱池
// _this._watcherTpl.a = [];
// _this._watcherTpl.b = [];
_this._watcherTpl[key] = {
_directives: []
};
let value = obj[key]; // 獲取屬`性值
let watcherTpl = _this._watcherTpl[key]; // 數(shù)據(jù)的訂閱池
Object.defineProperty(_this._data, key, { // 數(shù)據(jù)劫持
configurable: true, // 可以刪除
enumerable: true, // 可以遍歷
get() {
console.log(`${key}獲取值:${value}`);
return value; // 獲取值的時(shí)候 直接返回
},
set(newVal) { // 改變值的時(shí)候 觸發(fā)set
console.log(`${key}更新:${newVal}`);
if (value !== newVal) {
value = newVal;
//_this._watcherTpl.xxx.forEach(item)
//[{update:function(){}}]
watcherTpl._directives.forEach((item) => { // 遍歷訂閱池
item.update();
// 遍歷所有訂閱的地方(v-model+v-bind+{{}}) 觸發(fā)this._compile()中發(fā)布的訂閱Watcher 更新視圖
});
}
}
})
};
};
這么做是可以實(shí)現(xiàn)可是,可以看到有這么一些缺點(diǎn):
- 對(duì)象必須是存在的。
- 循環(huán)耗費(fèi)性能。
- 代碼可讀性可拓展性不是很好
- 等等..
那么我們能不能換一種方式去解決數(shù)據(jù)的劫持問題?
Proxy 橫空出世
Proxy 是 ECMAScript 2015 的新特性,唯一的 缺點(diǎn)是 兼容性不是非常好。但我們要團(tuán)結(jié)啊,哈哈哈。 廢棄 IE。。。
下面我們將使用 Proxy 實(shí)現(xiàn)數(shù)據(jù)的劫持 和 代理。關(guān)于 Proxy 可以看這么兩篇文章,一個(gè)是 阮一峰老師 的,不管阮一峰怎么樣,當(dāng)初竟然幫助過我們,我覺得就可以稱之為老師 ,還有一篇 抱歉,學(xué)會(huì) Proxy 真的可以為所欲為
// 重寫data 的 get set 更改數(shù)據(jù)的時(shí)候,觸發(fā)watch 更新視圖
myVue.prototype._observer = function (obj) {
const _this = this;
this._data = new Proxy(obj, { // 數(shù)據(jù)劫持
get(target, key, receiver) {
return Reflect.get(target, key, receiver); // 獲取值的時(shí)候 直接返回
},
set(target, key, newVal) { // 改變值的時(shí)候 觸發(fā)set
if (_this.value !== newVal) {
_this.value = newVal;
//先將數(shù)據(jù)更新完成后
let res = Reflect.set(target,key,newVal);
_this._watcherTpl[key]._directives.forEach((item) => { // 遍歷訂閱池
item.update();
});
return res
}
}
});
};
看到代碼不用說了,量級(jí)的差距,簡(jiǎn)潔多了,這里直接將 VUE 的data 變成了一個(gè) Proxy 對(duì)象。進(jìn)行數(shù)據(jù)的操作。
既然這里更改了,那么我們之前的訂閱池其實(shí)是廢除了,因?yàn)闆]有循環(huán)了不存在 key:
_this._watcherTpl[key] = {
_directives: []
};
所以我這里單獨(dú)在_compile 處理了訂閱池。
const attrVal = node.getAttribute('v-model'); // 獲取綁定的data
_this.hasDirectives(attrVal);
//工具類判斷是否有訂閱池
myVue.prototype.hasDirectives = function (attr) {
const _this = this;
// 沒有事件池 創(chuàng)建事件池
if (!_this._watcherTpl[attr]) {
_this._watcherTpl[attr] = {};
_this._watcherTpl[attr]._directives = [];
} else {
if (!_this._watcherTpl[attr]._directives) {
_this._watcherTpl[attr]._directives = []
}
}
};
這樣就解決了連接池的問題 ,這里的連接池使用的是數(shù)組,后面我們將會(huì)替換為map