Vue MVVM理解及實(shí)現(xiàn)

MVVM

簡單地說就是數(shù)據(jù)驅(qū)動(dòng)視圖,視圖改變(事件)也可以改變數(shù)據(jù),就是雙向綁定的概念。

實(shí)現(xiàn)

為了監(jiān)聽數(shù)據(jù)的改變,從而響應(yīng)到視圖上,用的是Vue雙向綁定的核心Object.definePrototype(obj,key,{...}),編寫Observer.js來實(shí)現(xiàn)。若set被觸發(fā),通知所有的訂閱者(dep中存儲(chǔ)的是所有訂閱者實(shí)例對象),且在get中引入Dep.target,僅在添加watcher時(shí)主動(dòng)賦值,防止之后多次添加watcher,并且get返回當(dāng)前的val值。
Object.defineProperty(data,key,{
        enumerable: true, // 可枚舉,可被Object.value()等遍歷方法所遍歷
        configurable: true, // 可再重新定義,即可被修改,可被刪除, 默認(rèn)為false
        get: function(){
            // console.log(Dep.target)
            // Dep.target現(xiàn)在其實(shí)為null,但是在watcher.js中每次get都將watch自身添加到全局的Dep.target中
            // 等到value獲取完畢,再將Dep.target清空
            // Dep.target作為閉包,在函數(shù)中保持了dep的存在
            // 如果沒有Dep.target,dep會(huì)被清除,在set中就無法通過dep.nptify()來出發(fā)watcher了
            // Dep.target && dep.addSub(Dep.target)
            if(Dep.target){
                dep.addSub(Dep.target) // 添加一個(gè)訂閱者
            }
            return val
        },
        set: function(newVal){
            console.log('值變化')
            if(newVal === val) return
            val = newVal
            // 通知所有訂閱者
            dep.notify()
        }
    })
訂閱者watcher.js關(guān)聯(lián)著模板編譯,每個(gè)生成的訂閱者都包含一個(gè)修改模板的callback,一旦對應(yīng)發(fā)布者Observer.js中的set函數(shù)執(zhí)行,所有對應(yīng)訂閱者接收到通知,就會(huì)執(zhí)行該訂閱者被創(chuàng)建時(shí)包含的callback,修改視圖。
    update: function(){
        this.run() // 屬性值變化收到通知
    },
    run: function(){
        // 數(shù)據(jù)改變時(shí)
        var value = this.vm.data[this.exp] // 取到最新的值 || this.get()
        var oldVal = this.value // 存儲(chǔ)老值
        if(value !== oldVal){
            this.value = value
            this.cb.call(this.vm,value,oldVal) // 執(zhí)行compile中的回調(diào),更新視圖
        }
    }
在Vue中實(shí)現(xiàn)數(shù)據(jù)綁定有兩種途徑,一種是雙大括號{{}},一種是v-model,為了將數(shù)據(jù)綁定到頁面,同時(shí)為了給包含這兩種情況的模板添加訂閱者,編寫compile.js來實(shí)現(xiàn)。compile.js接收MVVM實(shí)例中掛載的根節(jié)點(diǎn)和該實(shí)例對象。先將所有節(jié)點(diǎn)剪切到fragment文檔片段中,再通過遍歷所有節(jié)點(diǎn)的方式,碰到包含數(shù)據(jù)綁定的節(jié)點(diǎn),就創(chuàng)建一個(gè)新的watcher,并包含改變視圖的callback從而與watcher.js關(guān)聯(lián),碰到其他類似事件綁定的節(jié)點(diǎn),則給其綁定事件監(jiān)聽器,從而實(shí)現(xiàn)v-on的事件綁定效果。另外使用文檔片段的好處是避免了頁面的頻繁的回流重繪,文檔節(jié)點(diǎn)使用完畢后返回頁面只需渲染一次即可。

核心代碼

    init(){
        if(this.el){
            this.fragment = this.nodeToFragment(this.el) 
            this.compileElement(this.fragment);
            this.el.appendChild(this.fragment);
        }
    },
    // 節(jié)點(diǎn)全部轉(zhuǎn)為文檔片段
    nodeToFragment(el){
        // 創(chuàng)建空的文檔片段
        var fragment = document.createDocumentFragment()
        var child = el.firstChild
        while(child){
            // 子節(jié)點(diǎn)推入文檔片段,appendChild會(huì)有剪切的效果?。。。?!
            fragment.appendChild(child)
            child = el.firstChild
        }
        return fragment
    },
    compileElement(el){
        // 創(chuàng)建好的文檔片段拿過來編譯
        var childNodes = el.childNodes
        var self = this
        // dom數(shù)組不是真正的數(shù)組,沒有遍歷方法

        // 多層嵌套slice處理效率過低導(dǎo)致執(zhí)行失效
        // [].slice.call(childNodes).forEach(function(node){})
        // 也好像不是????在控制臺測試了一下都運(yùn)行的飛快啊...
        Array.prototype.forEach.call(childNodes,function(node){
            // 處理{{}}的正則
            var reg = /\{\{(.*)\}\}/
            var text = node.textContent

            if(self.isElementNode(node)){
                self.compile(node)
            }else if(self.isTextNode(node) && reg.test(text)){
                // 檢測到雙括號
                self.compileText(node,reg.exec(text)[1])
            }

            // 遞歸編譯,編譯所有節(jié)點(diǎn)
            if(node.childNodes && node.childNodes.length){
                self.compileElement(node)
            }
        })
    }

參考鏈接

canfoo

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

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

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