成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于Vue的MVVM學(xué)習(xí)筆記

Alan / 2452人閱讀

摘要:發(fā)布訂閱現(xiàn)在每個(gè)人應(yīng)該都用微信吧,一個(gè)人可以關(guān)注多個(gè)公眾號(hào),多個(gè)人可以同時(shí)關(guān)注相同的公眾號(hào)。公眾號(hào)每周都會(huì)更新內(nèi)容,并推送給我們,把寫好的文章在微信管理平臺(tái)更新就好了,點(diǎn)擊推送,就相當(dāng)于發(fā)布。

什么是MVVM

MVVM——Model-View-ViewModle的縮寫,MVC設(shè)計(jì)模式的改進(jìn)版。Model是我們應(yīng)用中的數(shù)據(jù)模型,View是我們的UI層,通過ViewModle,可以把我們Modle中的數(shù)據(jù)映射到View視圖上,同時(shí),在View層修改了一些數(shù)據(jù),也會(huì)反應(yīng)更新我們的Modle。

上面的話,未免太官方了。簡(jiǎn)單理解就是雙向數(shù)據(jù)綁定,即當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候,視圖也就發(fā)生變化,當(dāng)視圖發(fā)生變化的時(shí)候,數(shù)據(jù)也會(huì)跟著同步變化。

MVVM這種思想的前端框架其實(shí)老早就有了,我記得是在13年,自己在公司的主要工作是做后臺(tái)管理系統(tǒng)的UI設(shè)計(jì)和開發(fā),當(dāng)時(shí)就思考,如何讓那些專注后臺(tái)的開發(fā),既簡(jiǎn)單又方便的使用前端開發(fā)的一些組件。當(dāng)時(shí)有三種方案:

使用Easy-ui,但easy-ui好像官方要求收費(fèi),當(dāng)然也可以破解使用

自己開發(fā)UI框架,其實(shí)當(dāng)時(shí)想做的東西就是后來(lái)BootStrap

使用谷歌的Angular,進(jìn)行二次開發(fā)

后來(lái)的評(píng)估是:

使用easy-ui,工作量太多

使用Angular和easy-ui不僅工作量很大,后臺(tái)也要做相應(yīng)的修改

自己寫UI框架,比較合適,當(dāng)時(shí)的做法是寫一些jQuery相關(guān)的插件,先給后臺(tái)一個(gè)js插件包,后續(xù)的UI修改,慢慢進(jìn)行。

當(dāng)時(shí)自己還是比較推崇Angular的,我記得后來(lái)還買了一本《基于MVC的Javascript Web富應(yīng)用開發(fā)》專門去了解這種模式在工作中可能用的情況,以及實(shí)現(xiàn)它的一些基本思路。

當(dāng)時(shí)熱點(diǎn)比較高的MVVM框架有:

Angular:谷歌出品,名氣很大,入門高,使用麻煩,它提供了很多新的概念。

Backbone.js,入門要求級(jí)別很高,我記得當(dāng)時(shí)淘寶有些項(xiàng)目應(yīng)用了這個(gè),《基于MVC富應(yīng)用開發(fā)》書里面也是以這個(gè)框架為主介紹MVC的。

Ember:大而全的框架,開始寫代碼之前就已經(jīng)有很多的工作要做了。

當(dāng)年的環(huán)境和條件都沒有現(xiàn)在好,無(wú)論從技術(shù)完善的情況,還是工作的實(shí)際情況上面看,都是如此——那時(shí)候前后端分離都是理想。

當(dāng)然現(xiàn)在環(huán)境好了,各種框架的出現(xiàn)也極大方便了我們,提高了我們開發(fā)的工作效率。時(shí)代總是在進(jìn)步,大浪淘沙,MVVM的框架現(xiàn)在比較熱門和流行的,我相信大家現(xiàn)在都知道,就是下面三種了:

Angular

Vue

React

現(xiàn)在Angular除了一些忠實(shí)的擁躉,基本上也就沒落了。Angular無(wú)論從入門還是實(shí)際應(yīng)用方面,都要比其他兩個(gè)框架發(fā)費(fèi)的時(shí)間成本更大。
Angular現(xiàn)在有種英雄末路的感覺,但不能不承認(rèn),之前它確實(shí)散發(fā)了光芒。

Angular的1.x版本,是通過臟值檢測(cè)來(lái)實(shí)現(xiàn)雙向綁定的。

而最新的Angular版本和Vue,以及React都是通過數(shù)據(jù)劫持+發(fā)布訂閱模式來(lái)實(shí)現(xiàn)的。

臟值檢測(cè)

簡(jiǎn)單理解就是,把老數(shù)據(jù)和新數(shù)據(jù)進(jìn)行比較,就表示之前存在過,有過痕跡,通過比較新舊數(shù)據(jù),來(lái)判斷是否要更新。感興趣的可以看看這篇文章 構(gòu)建自己的AngularJS,第一部分:作用域和digest。

數(shù)據(jù)劫持 發(fā)布訂閱

數(shù)據(jù)劫持:在訪問或者修改對(duì)象的某個(gè)屬性時(shí),通過代碼攔截這個(gè)行為,進(jìn)行額外的操作或者修改返回結(jié)果。在ES5當(dāng)中新增了Object.defineProperty()可以幫我們實(shí)現(xiàn)這個(gè)功能。

發(fā)布訂閱:現(xiàn)在每個(gè)人應(yīng)該都用微信吧,一個(gè)人可以關(guān)注多個(gè)公眾號(hào),多個(gè)人可以同時(shí)關(guān)注相同的公眾號(hào)。關(guān)注的動(dòng)作就相當(dāng)于訂閱。公眾號(hào)每周都會(huì)更新內(nèi)容,并推送給我們,把寫好的文章在微信管理平臺(tái)更新就好了,點(diǎn)擊推送,就相當(dāng)于發(fā)布。更詳細(xì)的可以深入閱讀 javascript設(shè)計(jì)模式——發(fā)布訂閱模式

怎么實(shí)現(xiàn)一個(gè)MVVM

我們靜下心好好思考下,如果才能實(shí)現(xiàn)雙向數(shù)據(jù)綁定的功能??赡苄枰?/p>

一個(gè)初始化實(shí)例的類

一個(gè)存放數(shù)據(jù)的對(duì)象Object

一個(gè)可以把我們的數(shù)據(jù)映射到HTML頁(yè)面上的“模板解析”工具

一個(gè)更新數(shù)據(jù)的方法

一個(gè)通過監(jiān)聽數(shù)據(jù)的變化,更新視圖的方法

一個(gè)掛載模板解析的HTML標(biāo)簽

通過上面這樣的思考,我們可以簡(jiǎn)單的寫一下大概的方法。

class MVVM {
    constructor(data){
        this.$option = option;
        const data = this._data = this.$option.data;
        
        //數(shù)據(jù)劫持
        observe(data)
        
        //數(shù)據(jù)代理
        proxyData(data)
        
        //編譯模板
        const dom = this._el = this.$option.el;
        complie(dom,this);
        
        //發(fā)布訂閱
        
        //連接視圖和數(shù)據(jù)
        
        //實(shí)現(xiàn)雙向數(shù)據(jù)綁定   
    }
}

// Observe類
function Observe(){}


// Observe實(shí)例化函數(shù)
function observe(data){
    return new Observe(data);
}


// Compile類
function Compile(){}

// Compile實(shí)例化函數(shù)
function compile(el){
    return new Compile(el)
}
數(shù)據(jù)劫持

我們有下面這樣一個(gè)對(duì)象

let obj = {
    name:"mc",
    age:"29",
    friends:{
        name:"hanghang",
        name:"jiejie"
    }
}

我們要對(duì)這個(gè)對(duì)象執(zhí)行某些操作(讀取,修改),通常像下面就可以

// 取值
const name = obj.name;
console.log(obj.age)
const friends = obj.friends;

// 修改
obj.name = "mmcai";
obj.age =  30;

在VUE中,我們知道,如果data對(duì)象中的某個(gè)屬性,在template當(dāng)中綁定的話,當(dāng)我們修改了這個(gè)屬性值,我們的視圖也就更新了。這就是雙向數(shù)據(jù)綁定,數(shù)據(jù)變化,視圖更新,同時(shí)反過來(lái)也一樣。

要實(shí)現(xiàn)這個(gè)功能,我們就需要知道data當(dāng)中的數(shù)據(jù)是如何變動(dòng)了,ES5當(dāng)中提供了Object.defineProperty()函數(shù),我們可以通過這個(gè)函數(shù)對(duì)我們data對(duì)象當(dāng)中的數(shù)據(jù)進(jìn)行監(jiān)聽。當(dāng)數(shù)據(jù)變動(dòng),就會(huì)觸發(fā)這個(gè)函數(shù)里面的set方法,通過判斷數(shù)據(jù)是否變化,就可以執(zhí)行一些方法,更新我們的視圖了。所以我們現(xiàn)在需要實(shí)現(xiàn)一個(gè)數(shù)據(jù)監(jiān)聽器Observe,來(lái)對(duì)我們data中的所有屬性進(jìn)行監(jiān)聽。

// Observe類的實(shí)例化函數(shù)
function observe(data){
    // 判斷數(shù)據(jù)是否是一個(gè)對(duì)象
    if(typeof data !== "object"){
        return;
    }
    // 返回一個(gè)Observe的實(shí)例化對(duì)象
    return new Observe(data)
}

// Observer類的實(shí)現(xiàn)
class Observe{
    constructor(data){
        this.data = data;
        this.init(data)
    }
    
    init(data){
        for(let k in data){
            let val = data[k];
            
            //如果data是一個(gè)對(duì)象,我們遞歸調(diào)用自身
            if(typeof val === "object"){
                observe(val);
            }
            
            Object.defineProperty(data,k,{
                enumerable:true,
                get(){
                    return val;
                },
                set(newVal){
                    //如果值相同,直接返回
                    if(newVal === val){
                        return;
                    };
                    //賦值
                    val = newVal;
                    
                    //如果新設(shè)置的值是一個(gè)對(duì)象,遞歸調(diào)用observe方法,給新數(shù)據(jù)也添加上監(jiān)聽
                    if(typeof newVal === "object"){
                        observe(newVal);
                    }
                }
            })
        }
    }
    
}

了解了數(shù)據(jù)劫持,我們就可以明白,為什么我們實(shí)例化vue的時(shí)候,必須事先在data當(dāng)中定義好我們的需要的屬性了,因?yàn)槲覀冃略龅膶傩?,沒有經(jīng)過observe進(jìn)行監(jiān)聽,沒有通過observe監(jiān)聽,后面complie(模板解析)也就不會(huì)執(zhí)行。

所以,雖然你可以在data上面設(shè)置新的屬性,并讀取,但視圖卻不能更新。

數(shù)據(jù)代理

我們常見的代理有nginx,就是我們不直接去訪問(操作)我們實(shí)際要訪問的數(shù)據(jù),而是通過訪問一個(gè)代理,然后代理幫我們?nèi)ツ梦覀冋嬲枰臄?shù)據(jù)。

一般的特點(diǎn)是:

安全,不把真實(shí)內(nèi)容暴露

方便,可以把一些復(fù)雜的操作,通過代理進(jìn)行簡(jiǎn)化

...

下面是VUE簡(jiǎn)單的一個(gè)使用實(shí)例:

cosnt vm = new Vue({
    el:"#app",
    data:{
        name:"mmcai"
    }
});

我們的實(shí)例化對(duì)象vm,想要讀取data里面的數(shù)據(jù)的時(shí)候,不做任何處理的正常情況下,使用下面方式讀?。?/p>

const name = vm.data.name;

這樣操作起來(lái),顯然麻煩了一些,我們就可以通過數(shù)據(jù)代理,直接把data綁定到我們的實(shí)例上,所以在vue當(dāng)中,我們一般獲取數(shù)據(jù)像下面一樣:

cosnt vm = new Vue({
    el:"#app",
    data:{
        name:"mmcai"
    },
    created(){
        
        // 直接通過實(shí)例就可以訪問到data當(dāng)中的數(shù)據(jù)
        const name = this.name;
        
        // 通過this.data.name 也可以訪問,但是顯然,麻煩了一些
    }
});

同樣,我們通過Object.defineProperty函數(shù),把data對(duì)象中的數(shù)據(jù),綁定到我們的實(shí)例上就可以了,代碼如下:

class MVVM {
    constructor(option){
        //此處代碼省略
        this.$option = option;
        const data = this._data = this.$option.data;
        
        //調(diào)用代理
        this._proxyData(data);
    }
    
    _proxyData(data){
        const that = this;
        for(let k in data){
            let val = data[k];
            Object.defineProperty(that,k,{
                enumerable:true,
                get(){
                    return that._data[k];
                },
                set(newVal){
                    that._data[k] = newVal;
                }
            })
        }
    }
}
編譯模板

利用正則表達(dá)式識(shí)別模板標(biāo)識(shí)符,并利用數(shù)據(jù)替換其中的標(biāo)識(shí)符。
VUE里面的標(biāo)識(shí)符是 {{}} 雙大括號(hào),數(shù)據(jù)就是我們定義在data上面的內(nèi)容。

實(shí)現(xiàn)原理

確定我們的模板范圍

遍歷DOM節(jié)點(diǎn),循環(huán)找到我們的標(biāo)識(shí)符

將標(biāo)識(shí)符的內(nèi)容用數(shù)據(jù)進(jìn)行填充填充

遍歷解析需要替換的根元素el下的HTML標(biāo)簽,一定會(huì)使用遍歷對(duì)DOM節(jié)點(diǎn)進(jìn)行操作,對(duì)DOM操作就會(huì)引發(fā)頁(yè)面的重排和重繪,為了提高性能和效率,可以把el根節(jié)點(diǎn)下的所有節(jié)點(diǎn)替換為文檔碎片fragment進(jìn)行解析編譯操作,解析完成,再將fragment添加到根節(jié)點(diǎn)el中

如果想對(duì)文檔碎片進(jìn)行,更多的了解,可以查看文章底部的參考資料


class Complie{
    constructor(el,vm){
        this.$vm = vm;
        this.$el = document.querySelector(el);
        
        //第一步,把DOM轉(zhuǎn)換成文檔碎片
        this.$fragment = this.nodeToFragment(this.$el);
        
        //第二步,匹配標(biāo)識(shí)符,填充數(shù)據(jù)
        this.compileElement(this.$fragment);
        
        //把文檔碎片,添加到el根節(jié)點(diǎn)上面
        this.$el.appendChild(this.$fragment);  
    }
    
    // 把DOM節(jié)點(diǎn)轉(zhuǎn)換成文檔碎片
    nodeToFragment(el){
        let nodeFragment = document.createDocumentFragment();
        // 循環(huán)遍歷el下面的節(jié)點(diǎn),填充到文檔碎片nodeFragment中
        while(child = el.firstChild){
            nodeFragment.appendChild(child);
        }
        
        // 把文檔碎片返回
        return nodeFragment;
    }
    
    // 遍歷目標(biāo),查找標(biāo)識(shí)符,并替換
    compileElement(node){
        let reg = /{{(.*)}}/;
        Array.from(node.childNodes).forEach((node)=>{
            let text = node.textContent;
            if(node.nodeType === 3 && reg.test(text)){
                let arr = RegExp.$1.split(".");
                // vm 是實(shí)例的整個(gè)data對(duì)象
                let val = vm;
                arr.forEach((k)=>{
                    val = val[k]
                })
                
                node.textContent = text.replace(/{{(.*)}}/,val);
            }
            
            // 如果節(jié)點(diǎn)包含字節(jié)的,遞歸調(diào)用自身
            if(node.childNodes){
                this.compileElement(node)
            }
            
        })
    }
    
    
}


const complie = (el,vm)=>{
    return new Compile(el,vm)
}
發(fā)布訂閱
在軟件架構(gòu)中,發(fā)布訂閱是一種消息范式,消息的發(fā)送者(成為發(fā)布者)不會(huì)將消息直接發(fā)送給特定的接收者(成為訂閱者)。二十將發(fā)布的消息分為不同的類別,無(wú)需了解哪些訂閱者是否存在。同樣的,訂閱者可以表達(dá)對(duì)一個(gè)或多個(gè)類別的興趣,直接受感興趣的消息,無(wú)需了解哪些發(fā)布者是否存在——維基。

上述的表達(dá)中,既然說(shuō)發(fā)布者不關(guān)心訂閱者,訂閱者也不關(guān)心發(fā)布者,那么他們是如何通信呢?

其實(shí)就是通過第三方,通常在函數(shù)中我們,稱他們?yōu)?strong>觀察者watcher

在VUE的里面,我們要確認(rèn)幾個(gè)概念,誰(shuí)是發(fā)布者,誰(shuí)是訂閱者,為什么需要發(fā)布訂閱?

上面我們說(shuō)了數(shù)據(jù)劫持Observe,也說(shuō)了Compile,其實(shí),Observe和Compile 他們即使發(fā)布者,也是訂閱者,幫助他們之間的通訊,就是watcher的工作。
通過下面的代碼,我們簡(jiǎn)單了解下,發(fā)布訂閱模式的實(shí)現(xiàn)情況。

// 創(chuàng)建一個(gè)類
// 發(fā)布訂閱,本質(zhì)上是維護(hù)一個(gè)函數(shù)的數(shù)組列表,訂閱就是放入函數(shù),發(fā)布就是讓函數(shù)執(zhí)行

class Dep{
    consturctor(){
        this.subs=[];
    }
    
    // 添加訂閱者
    addSub(sub){
        this.subs.push(sub);
    }
    
    // 通知訂閱者
    notify(){
        // 訂閱者,都有
        this.subs.forEach((sub=>sub.update());
    }
}

// 監(jiān)聽函數(shù),watcher
// 通過Watcher類創(chuàng)建的實(shí)例,都有update方法
class Watcher{
    
    // watcher的實(shí)例,都需要傳入一個(gè)函數(shù)
    constructor(fn){
        this.fn = fn;
    }
    
    // watcher的實(shí)例,都擁有update方法
    update(){
        this.fn();
    }
}

// 把函數(shù)作為參數(shù)傳入,實(shí)例化一個(gè)watcher
const watcher = new Watcher(()=>{
    consoole.log("1")
});

// 實(shí)例化Dep 類
const dep = new Dep();

// 將watcher放到dep維護(hù)的數(shù)組中,watcher實(shí)例本身具有update方法
// 可以理解成函數(shù)的訂閱
dep.addSub(watcher);

// 執(zhí)行,可以理解成,函數(shù)的發(fā)布,
// 不關(guān)心,addSub方法訂閱了誰(shuí),只要訂閱了,就通過遍歷循環(huán)subs數(shù)組,執(zhí)行數(shù)組每一項(xiàng)的update
dep.notify();

通過以上代碼的了解,我們繼續(xù)實(shí)現(xiàn)我們MVVM中的代碼,實(shí)現(xiàn)數(shù)據(jù)和視圖的關(guān)聯(lián)。
這種關(guān)聯(lián)的結(jié)果就是,當(dāng)我們修改data中的數(shù)據(jù)的時(shí)候,我們的視圖更新?;蛘呶覀円晥D中修改了相關(guān)內(nèi)容,我們的data也進(jìn)行相關(guān)的更新,所以這里主要的邏輯代碼,就是我們watcher當(dāng)中的update方法。

我們根據(jù)上面的內(nèi)容,對(duì)我們的Observe和Compile以及Watcher進(jìn)行修改,代碼如下:

class MVVM{
    constructor(option){
        this.$option = option;
        const data = this._data = this.$option.data;
        this.$el = this.$option.el;
        
        // 數(shù)據(jù)劫持
        this._observe(data);
        
        // 數(shù)據(jù)代理
        this._proxyData(data);
        
        //模板解析
        this._compile(this.$el,this)
    }
    
    // 數(shù)據(jù)代理
    _proxyData(data){
        for(let k in data){
            let val = data[k];
            Object.defineProperty(this,k,{
                enumerable:true,
                get(){
                    return this._data[k];
                },
                set(newVal){
                    this._data[k] = newVal;
                }
            })
        }
    }
    

    
}




// 數(shù)據(jù)劫持
class Observe{
    constructor(data){
        this.init(data);
    }
    
    init(data){
        let dep = new Dep();
        for(let k in data){
            let val = data[k];
            
            // val 可能是一個(gè)對(duì)象,遞歸調(diào)用
            if(typeof val === "object"){
                observe(val);
            }
            Object.defineProperty(data,k,{
                enumerable:true,
                get(){
                    // 訂閱,
                    
                    // Dep.target 是Watcher的實(shí)例
                    Dep.target && dep.addSub(Dep.target);
                    return val;
                },
                set(newVal){
                    if(newVal === val){
                        return;
                    }
                    
                    val = newVal;
                    observe(newVal);
                    
                    
                    dep.notify();
                }
                
            })
        }
    }
}

// 數(shù)據(jù)劫持實(shí)例
function observe(data){
    if(typeof data !== "object"){
        return
    };
    return new Observe(data);
}
    


// 模板編譯
class Compile{
    constructor(el,vm){
        vm.$el = document.querySelector(el);
    
        //1.把DOM節(jié)點(diǎn),轉(zhuǎn)換成文檔碎片
        const Fragment = this.nodeToFragment(vm.$el)
        
        //2.通過正則匹配,填充數(shù)據(jù)
        this.replace(Fragment,vm);
        
        //3.把填充過數(shù)據(jù)的文檔碎片,插入模板根節(jié)點(diǎn)
        vm.$el.appendChild(Fragment);
        
        
    }
    
    // DOM節(jié)點(diǎn)轉(zhuǎn)換
    nodeToFragment(el){
        // 創(chuàng)建文檔碎片,
        const fragment = document.createDocumentFragment();
        //遍歷DOM節(jié)點(diǎn),把DOM節(jié)點(diǎn),添加到文檔碎片上
        while(child ===el.firstChild){
            fragment.appendChild(child);    
        }
        // 返回文檔碎片
        return fragment;
    }
    
    //匹配標(biāo)識(shí),填充數(shù)據(jù)
    replace(fragment,vm){
        // 使用Array.from方法,把DOM節(jié)點(diǎn),轉(zhuǎn)化成數(shù)據(jù),進(jìn)行循環(huán)遍歷
        Array.from(fragment.childNodes).forEach((node)=>{
            // 遍歷節(jié)點(diǎn),拿到每個(gè)內(nèi)容節(jié)點(diǎn)
            let text = node.textContent;
            // 定義標(biāo)識(shí)符的正則
            let reg = /{{(.*)}}/;
            
            //如果節(jié)點(diǎn)是文本,且節(jié)點(diǎn)的內(nèi)容當(dāng)中匹配到了模板標(biāo)識(shí)符
            
            // 數(shù)據(jù)渲染視圖
            if(node.nodeType===3 && reg.test(text)){
                // 用數(shù)據(jù)替換標(biāo)識(shí)符
                let arr = RegExp.$1.split(".");
                let val = vm;
                arr.forEach((item)=>{
                    val = val[item];
                })
                // 添加一個(gè)watcher,當(dāng)我們的數(shù)據(jù)發(fā)生變化的時(shí)候,更新我們的view
                new Watcher(vm,RegExp.$1,(newVal)=>{
                    node.textContent = text.replace(reg,newVal); 
                })
                
                //把數(shù)據(jù)填充到節(jié)點(diǎn)上
                node.textContent = text.replace(reg,val);
            }
            
            // 視圖更新數(shù)據(jù)
            if(node.nodeType === 1){
                let nodeAttrs = node.attributes;
                Array.from(nodeAttrs).forEach((attr)=>{
                    let name = attr.name;
                    // 獲取標(biāo)識(shí)符的內(nèi)容,也就是v-mode="a"的內(nèi)容
                    let exp = attr.value;
                    if(name.indexOf("v-model")===0){
                        node.value = vm[exp];
                    };
                    new Watcher(vm,exp,(newVal)=>{
                        node.value = newVal;
                    });
                    
                    node.addEventListener("input",function(e){
                        let newVal = e.target.value;
                        vm[exp] = newVal;
                    });
                });
            }
            
            // 如果節(jié)點(diǎn)包含子節(jié)點(diǎn),遞歸調(diào)用自身
            if(node.childNodes){
                this.replace(node,vm);
            }
        })
    }
}

// 模板編譯實(shí)例
function compile(el,vm){
    return new Compile(el,vm)
}

// 發(fā)布訂閱
class Dep{
    constructor(){
        this.subs = [];
    }
    
    // 訂閱函數(shù)
    addSub(fn){
        this.subs.push(fn);
    }
    
    // 發(fā)布執(zhí)行函數(shù)
    notify(){
        this.subs.forEach((fn)=>{
            fn();
        })
    }
}

// Dep實(shí)例
function dep(){
    return new Dep();
}

// 觀察者
class Watcher{
    // vm,我們的實(shí)例
    // exp,我們的標(biāo)識(shí)符
    // fn,回調(diào)
    constructor(vm,exp,fn){
        this.fn = fn;
        this.vm = vm;
        this.exp = exp;
        Dep.target = this;
        let val = vm;
        let arr = exp.split(".");
        arr.forEach((k)=>{
            val = val[k]
        });
        // 完成之后,我們把target 刪除;
        Dep.target = null;
    }
    update(){
        let val = this.vm;
        let arr = this.exp.split(".");
        arr.forEach((k)=>{
            val = val[k];
        })
        this.fn();
    }
}


function watcher(){
    return new Watcher()
}

Wathcer干了那些好事:

在自身實(shí)例化的時(shí)候,往訂閱器(dep)里面添加自己

自身有一個(gè)update方法

待data屬性發(fā)生修改的時(shí)候,dep.notify()通知的時(shí)候,可以調(diào)用自身的update()方法,在update()方法出發(fā)綁定的回調(diào)

Watcher連接了兩個(gè)部分,包括Observe和Compile;

在Observe方法執(zhí)行的時(shí)候,我們給data的每個(gè)屬性都添加了一個(gè)dep,這個(gè)dep被閉包在get/set函數(shù)內(nèi)。

當(dāng)我們new Watcher,在之后訪問data當(dāng)中屬性的時(shí)候,就會(huì)觸發(fā)通過Object.defineProperty()函數(shù)當(dāng)中的get方法。
get方法的調(diào)用,就會(huì)在屬性的訂閱器實(shí)例dep中,添加當(dāng)前Watcher的實(shí)例。

當(dāng)我們嘗試修改data屬性的時(shí)候,就會(huì)出發(fā)dep.notify()方法,該方法會(huì)調(diào)用每個(gè)Watcher實(shí)例的update方法,從而更新我們的視圖。

結(jié)束語(yǔ)

回顧下整個(gè)MVVM實(shí)現(xiàn)的整個(gè)過程

使用Object.defineProperty()函數(shù),給每個(gè)data屬性添加get/set,并為每個(gè)屬性創(chuàng)建一個(gè)dep實(shí)例,監(jiān)聽數(shù)據(jù)變化

同樣使用Object.defineProperty()函數(shù),把data對(duì)象的屬性,綁定到我們MVVM實(shí)例vm對(duì)象上,簡(jiǎn)化使用

通過document.createDocumentFragment,把我們el節(jié)點(diǎn)下的dom轉(zhuǎn)換成文檔碎片

遍歷文檔碎片,找到模板標(biāo)識(shí)符,進(jìn)行數(shù)據(jù)的替換,添加Watcher觀察者,當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候,再次更新我們的文檔碎片

把文檔碎片插入到我們的el節(jié)點(diǎn)中。

我們修改data,執(zhí)行dep.notify()方法,然后調(diào)用Watcher實(shí)例上的update方法,更新視圖。

我這里有一個(gè)簡(jiǎn)短的視頻,是某培訓(xùn)機(jī)構(gòu)講解MVVM的內(nèi)容,大家有興趣,可以自取。

視頻鏈接

提取碼:1i0r

如果失效,可以私聊我。

參考

廖雪峰談MVVM

...,讓MVVM原理還給你

觀察者模式與發(fā)布訂閱模式

基于vue實(shí)現(xiàn)一個(gè)簡(jiǎn)單的MVVM框架

文檔碎片

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104707.html

相關(guān)文章

  • mvvm框架--san.js 學(xué)習(xí)筆記(一)

    摘要:最近,由于公司項(xiàng)目需要,使用百度框架開發(fā)了一個(gè)兼容的小項(xiàng)目。是框架,和有一些類似。和相比,優(yōu)勢(shì)是能兼容,但沒有那么簡(jiǎn)單易用,學(xué)習(xí)最好有一些框架的基礎(chǔ)。當(dāng)初我自己好奇,嘗試用做了一個(gè)小型項(xiàng)目,這里記錄一下。 最近,由于公司項(xiàng)目需要,使用百度mvvm框架san開發(fā)了一個(gè)兼容ie6的小項(xiàng)目。san是mvvm框架,和vue有一些類似。和vue相比,優(yōu)勢(shì)是能兼容ie6,但沒有vue那么簡(jiǎn)單易用,...

    zhangrxiang 評(píng)論0 收藏0
  • 學(xué)習(xí)MVVM及框架雙向綁定筆記

    摘要:的數(shù)據(jù)劫持版本內(nèi)部使用了來(lái)實(shí)現(xiàn)數(shù)據(jù)與視圖的雙向綁定,體現(xiàn)在對(duì)數(shù)據(jù)的讀寫處理過程中。這樣就形成了數(shù)據(jù)的雙向綁定。 MVVM由以下三個(gè)內(nèi)容組成 View:視圖模板 Model:數(shù)據(jù)模型 ViewModel:作為橋梁負(fù)責(zé)溝通View和Model,自動(dòng)渲染模板 在JQuery時(shí)期,如果需要刷新UI時(shí),需要先取到對(duì)應(yīng)的DOM再更新UI,這樣數(shù)據(jù)和業(yè)務(wù)的邏輯就和頁(yè)面有強(qiáng)耦合。 在MVVM中,U...

    VioletJack 評(píng)論0 收藏0
  • Vue2.5筆記Vue實(shí)例與生命周期

    摘要:總結(jié)這邊文章主要是介紹了下的實(shí)例與生命周期,在實(shí)例化的過程中我們可以添加許多可選對(duì)象,比如生命周期鉤子函數(shù)等,讓實(shí)例產(chǎn)生我們想要的行為。 理解與認(rèn)識(shí) Vue 的實(shí)例是我們學(xué)習(xí) Vue 非常重要的一步,也是非常必須的,因?yàn)閷?shí)例是它的一個(gè)起點(diǎn),也是它的一個(gè)入口,只有我們創(chuàng)建一個(gè) Vue 實(shí)例之后,我們才行利用它進(jìn)行一些列的操作。 首先 Vue 沒有完全遵守 MVVM 的架構(gòu)模式,但是它的設(shè)...

    Ashin 評(píng)論0 收藏0
  • Vue.js 源碼學(xué)習(xí)筆記

    摘要:實(shí)際上,我在看代碼的過程中順手提交了這個(gè),作者眼明手快,當(dāng)天就進(jìn)行了修復(fù),現(xiàn)在最新的代碼里已經(jīng)不是這個(gè)樣子了而且狀態(tài)機(jī)標(biāo)識(shí)由字符串換成了數(shù)字常量,解析更準(zhǔn)確的同時(shí)執(zhí)行效率也會(huì)更高。 最近饒有興致的又把最新版?Vue.js?的源碼學(xué)習(xí)了一下,覺得真心不錯(cuò),個(gè)人覺得 Vue.js 的代碼非常之優(yōu)雅而且精辟,作者本身可能無(wú) (bu) 意 (xie) 提及這些。那么,就讓我來(lái)吧:) 程序結(jié)構(gòu)梳...

    darkbaby123 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<