從 0.5 開始造輪子 仿 vue 的 mvvm(二)

替換數(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

結(jié)語

github完整實(shí)現(xiàn)

在線地址,需要翻墻

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容