原來(lái)vue的原理是這樣的

vue.js中有兩個(gè)核心功能:響應(yīng)式數(shù)據(jù)綁定宅倒,組件系統(tǒng)。主流的mvc框架都實(shí)現(xiàn)了單向數(shù)據(jù)綁定,而雙向綁定無(wú)非是在單向綁定基礎(chǔ)上給可輸入元素添加了change事件瓮具,從而動(dòng)態(tài)地修改model和view收奔。
介紹vue原理之前掌呜,我們先簡(jiǎn)單回顧一下什么是mvc。
[阮老師mvc詳解鏈接點(diǎn)這里]

MVC

阮鏈接

mvc模型

視圖(View):用戶(hù)界面坪哄。
控制器(Controller):業(yè)務(wù)邏輯
模型(Model):數(shù)據(jù)保存

通信方式

View 傳送指令到 Controller
Controller 完成業(yè)務(wù)邏輯后质蕉,要求 Model 改變狀態(tài)
Model 將新的數(shù)據(jù)發(fā)送到 View,用戶(hù)得到反饋

MVP

mvp模型
  1. 各部分之間的通信翩肌,都是雙向的模暗。

  2. View 與 Model 不發(fā)生聯(lián)系,都通過(guò) Presenter 傳遞念祭。

  3. View 非常薄兑宇,不部署任何業(yè)務(wù)邏輯,稱(chēng)為"被動(dòng)視圖"(Passive View)粱坤,即沒(méi)有任何主動(dòng)性隶糕,而 Presenter非常厚祝钢,所有邏輯都部署在那里。

MVVM

MVVM模型

可以看到若厚,MVVM 模式將 Presenter 改名為 ViewModel拦英,基本上與 MVP 模式完全一致。唯一的區(qū)別是测秸,它采用雙向綁定(data-binding):View的變動(dòng)疤估,自動(dòng)反映在 ViewModel,反之亦然霎冯。AngularEmber 都采用這種模式铃拇。

好了,接下來(lái)來(lái)介紹本章的重點(diǎn)沈撞,vue的原理慷荔。

1.vuejs雙向綁定

html代碼

 <input type="text" id="a">
    <span id="b"></span>

js代碼

       var obj = {};
        Object.defineProperty(obj, 'hello', {
            set: function (newVal) {
              document.getElementById('a').value = newVal;
              document.getElementById('b').innerHTML = newVal;
            }
        })
        document.addEventListener('keyup', function (e) {
          obj.hello = e.target.value;
        });

效果1:


效果1

這個(gè)效果就是在文本框中輸入的值會(huì)顯示在旁邊的<span>標(biāo)簽里。這個(gè)例子就是雙向綁定的實(shí)現(xiàn)缠俺,但是僅僅為了說(shuō)明原理显晶,這個(gè)和我們平時(shí)用的vue.js還有差距,下面是我們常見(jiàn)的vue.js寫(xiě)法

html代碼

    {{ text }} 

js代碼

    el: 'app',
    data: {
      text: 'hello world'
    }
  }) 

為了實(shí)現(xiàn)這樣的容易理解的代碼vue.js背后做了很多工作壹士,我們一一分解磷雇。

  1. 輸入框以及文本節(jié)點(diǎn)與data中的數(shù)據(jù)綁定
  2. 輸入框變化的時(shí)候,data中的數(shù)據(jù)同步變化躏救。即MVVM中 view => viewmodel的變化
  3. data中的數(shù)據(jù)變化時(shí)唯笙,文本節(jié)點(diǎn)的內(nèi)容同步變化。即MVVM中viewmode => view的變化

2 數(shù)據(jù)初始化綁定

介紹數(shù)據(jù)初始化綁定之前先說(shuō)一下DocumentFragment盒使。DocumentFragment(文檔片段)可以看做是節(jié)點(diǎn)容器崩掘,它可以包含多個(gè)子節(jié)點(diǎn),可以把它插入到DOM中少办,只有它的子節(jié)點(diǎn)會(huì)插入目標(biāo)節(jié)點(diǎn)苞慢,所以可以把它看做是一組節(jié)點(diǎn)容器。使用DocumentFragment處理節(jié)點(diǎn)速度和性能優(yōu)于直接操作DOM凡泣。Vue進(jìn)行編譯的時(shí)候就是將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到DocumentFragment中枉疼,經(jīng)過(guò)處理后再將DocumentFragment整體返回到掛載目標(biāo)。實(shí)例代碼如下:

 var dom = nodeToFragment(document.getElementById("app"));
        console.log(dom);
        function nodeToFragment (node, vm) {
            var flag = document.createDocumentFragment();
            var child;
            while (child = node.firstChild) {
                flag.appendChild(child); // 劫持node的所有節(jié)點(diǎn)
            }
            return flag;
        }
        document.getElementById("app").appendChild(dom);

有了文檔片段之后再看看初始化綁定鞋拟。

html代碼:

    <input type="text" v-model="text">
    {{text}}
</div> 

js代碼

function compile (node, vm) {
        var reg = /\{\{(.*)\}\}/;
        // 節(jié)點(diǎn)類(lèi)型為元素
        if (node.nodeType === 1) {
            var attr = node.attributes;
            // 解析屬性
            for (var i = 0; i < attr.length; i++) {
                if (attr[i].nodeName === 'v-model') {
                    var name = attr[i].nodeValue; // 獲取v-model綁定的屬性名
                    node.value = vm.data[name]; // 將data的值賦給該node
                    node.removeAttribute('v-model');
                }
            }
        }
        // 節(jié)點(diǎn)類(lèi)型為text
        if (node.nodeType === 3) {
            if (reg.test(node.nodeValue)) {
                var name = RegExp.$1; // 獲取匹配到的字符串
                name = name.trim()
                node.nodeValue = vm.data[name]; // 將該data的值付給該node
            }
        }
    }

    function nodeToFragment (node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
            compile(child, vm);
            // 將子節(jié)點(diǎn)劫持到文檔片段中
            flag.appendChild(child);
        }
        return flag;
    }

    // 構(gòu)造函數(shù)
    function Vue (options) {
        this.data = options.data;
        var id = options.el;
        var dom = nodeToFragment(document.getElementById(id), this);
        // 編譯完成后把dom返回到app中
        document.getElementById(id).appendChild(dom);
    }

    var vm = new Vue({
        el: 'app',
        data: {
            text: 'hello world'
        }
    }); 

效果2

我們看到hello word已經(jīng)綁定到input標(biāo)簽和節(jié)點(diǎn)中了

先看compile方法骂维,這個(gè)方法主要負(fù)責(zé)給node節(jié)點(diǎn)賦值

  1. compile方法接收兩個(gè)參數(shù),第一個(gè)是DOM節(jié)點(diǎn)贺纲,第二個(gè)vm是當(dāng)前對(duì)象
  2. 判斷dom節(jié)點(diǎn)類(lèi)型航闺,如果是1,表示元素(這里判斷不太嚴(yán)謹(jǐn),只是為了說(shuō)明原理)潦刃,在node節(jié)點(diǎn)的所有屬性中查找nodeName為“v-model”的屬性侮措,找到屬性值,這里是“text”乖杠。用當(dāng)前對(duì)象中名字為“text”的屬性值給節(jié)點(diǎn)賦值分扎,最后刪除這個(gè)屬性,就是刪除節(jié)點(diǎn)的v-model屬性胧洒。
  3. 判斷dom節(jié)點(diǎn)類(lèi)型畏吓,如果是3,表示是節(jié)點(diǎn)內(nèi)容卫漫,用正則表達(dá)式判斷是“{{text}}”這樣的字符串菲饼,用當(dāng)前對(duì)象中名字為“text”的屬性值給節(jié)點(diǎn)賦值,直接覆蓋掉“{{text}}”

nodeToFragment方法負(fù)責(zé)創(chuàng)建文檔片段列赎,并將compile處理過(guò)的子節(jié)點(diǎn)劫持到這個(gè)文檔片段中

  1. 創(chuàng)建一個(gè)文檔片段
  2. 循環(huán)查找傳入的node節(jié)點(diǎn)宏悦,調(diào)用compile方法給節(jié)點(diǎn)賦值
  3. 將賦值后的節(jié)點(diǎn)劫持到文檔片段中

Vue構(gòu)造函數(shù)

  1. 用傳入?yún)?shù)的data屬性給當(dāng)前對(duì)象的data屬性賦值
  2. 用傳入?yún)?shù)的id標(biāo)記查找掛載節(jié)點(diǎn),調(diào)用nodeToFragment方法獲取劫持后的文檔片段包吝,這個(gè)過(guò)程稱(chēng)為編譯
  3. 編譯完成后饼煞,將文檔片段插入到指定的當(dāng)前節(jié)點(diǎn)中

實(shí)例化vue

  1. 實(shí)例化一個(gè)vue對(duì)象,el屬性為掛載節(jié)點(diǎn)的id漏策,data屬性為要綁定的屬性及屬性值

響應(yīng)式數(shù)據(jù)綁定

初始化綁定只是實(shí)現(xiàn)了第一步派哲,然后我們要實(shí)現(xiàn)的是在文本框中輸入內(nèi)容的時(shí)候臼氨,vue實(shí)例中的屬性值也跟著變化掺喻。思路是在文本框中輸入數(shù)據(jù)的時(shí)候,觸發(fā)文本框的input事件(也可以是keyup储矩,change)感耙,在相應(yīng)的事件處理程序中,獲取輸入內(nèi)容賦值給當(dāng)前vue實(shí)例vm的text屬性持隧。這里利用上面介紹的Object.defeinProperty()方法來(lái)給vue實(shí)例中data中的屬性重新定義為訪問(wèn)器屬性即硼,就是在定義這個(gè)屬性的時(shí)候添加get,set這兩個(gè)存取描述符屡拨,這樣給vm.text賦值的時(shí)候就會(huì)觸發(fā)set方法只酥。然后在set方法中更新vue實(shí)例屬性的值⊙嚼牵看下面的html裂允,js代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>響應(yīng)式數(shù)據(jù)綁定</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="text"/>
    {{ text }}
</div>
<script>
    /**
     * 使用defineProperty將data中的text設(shè)置為vm的訪問(wèn)器屬性
     * @param obj 對(duì)象
     * @param 屬性名
     * @param 屬性值
     * */
    function defineReactive (obj, key, val) {
        Object.defineProperty(obj, key, {
            get: function () {
                return val
            },
            set: function (newVal) {
                if (newVal === val) {
                    return
                }
                val = newVal
                // 輸出日志
                console.log(`set方法觸發(fā)屬性值變化${val}`)
            }
        })
    }
    /**
     * 給vue實(shí)例定義訪問(wèn)器屬性
     * @param obj vue實(shí)例中的數(shù)據(jù)
     * @param vm vue對(duì)象
     * */
    function observe (obj, vm) {
        Object.keys(obj).forEach(function (key) {
            defineReactive(vm, key, obj[key]);
        })
    }
    /**
     * 編譯過(guò)程,給子節(jié)點(diǎn)初始化綁定vue實(shí)例中的屬性值
     * @param node 子節(jié)點(diǎn)
     * @param vm vue實(shí)例
     * */
    function compile (node, vm) {
        let reg = /\{\{(.*)\}\}/
        // 節(jié)點(diǎn)類(lèi)型為元素
        if (node.nodeType === 1) {
            let attr = node.attributes
            // 解析屬性
            for (let i = 0; i < attr.length; i++) {
                if (attr[i].nodeName === 'v-model') {
                    // 獲取v-model綁定的屬性名
                    let name = attr[i].nodeValue
                    // 添加監(jiān)聽(tīng)事件
                    node.addEventListener('input', function (e) {
                        // 給相應(yīng)的data屬性賦值哥艇,進(jìn)而觸發(fā)該屬性的set方法
                        vm[name] = e.target.value;
                    });
                    // 將data的值賦給該node
                    node.value = vm.data[name];
                    node.removeAttribute('v-model')
                }
            }
        }
        // 節(jié)點(diǎn)類(lèi)型為text
        if (node.nodeType === 3) {
            if (reg.test(node.nodeValue)) {
                // 獲取匹配到的字符串
                let name = RegExp.$1
                name = name.trim()
                // 將data的值賦給該node
                node.nodeValue = vm.data[name]
            }
        }
    }
    /**
     * DocumentFragment文檔片段绝编,可以看作節(jié)點(diǎn)容器,它可以包含多個(gè)子節(jié)點(diǎn),當(dāng)將它插入到dom中時(shí)只有子節(jié)點(diǎn)插入到目標(biāo)節(jié)點(diǎn)中十饥。
     * 使用documentfragment處理節(jié)點(diǎn)速度和性能要高于直接操作dom窟勃。vue編譯的時(shí)候,就是將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到documentfragment
     * 中逗堵,經(jīng)過(guò)處理后再將documentfragment整體返回到掛載目標(biāo)中秉氧。
     * @param node 節(jié)點(diǎn)
     * @param vm vue實(shí)例
     * */
    function nodeToFragment (node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
            compile(child, vm);
            flag.appendChild(child);
        }
        return flag;
    }
    /*vue類(lèi)*/
    function Vue (options) {
        this.data = options.data
        let data = this.data
        // 給vue實(shí)例的data定義訪問(wèn)器屬性,覆蓋原來(lái)的同名屬性
        observe(data, this)
        let id = options.el
        let dom = nodeToFragment(document.getElementById(id), this)
        // 編譯蜒秤,劫持完成后將dom返回到app中
        document.getElementById(id).appendChild(dom)
    }

    /*定義一個(gè)vue實(shí)例*/
    let vm = new Vue({
        el: 'app',
        // 這里的data屬性不是訪問(wèn)器屬性
        data: {
            text: 'hello world!'
        }
    })
</script>
</body>
</html> 

效果3

下面不再逐句分析谬运,只說(shuō)重點(diǎn)的。

  1. 在defineReactive方法中垦藏,vue實(shí)例中的data的屬性重新定義為訪問(wèn)器屬性梆暖,并在set方法中將新的值更新到這個(gè)屬性上
  2. 在observe方法中,遍歷vue實(shí)例中data的屬性掂骏,逐一調(diào)用defineReactive方法轰驳,把他們定義為訪問(wèn)器屬性
  3. 在compile方法中,如果是input這樣的標(biāo)簽弟灼,給它添加事件(也可以是keyup级解,change),監(jiān)聽(tīng)input值變化田绑,并給vue實(shí)例中相應(yīng)的訪問(wèn)器屬性賦值
  4. 在Vue類(lèi)方法中勤哗,調(diào)用observer方法,傳入當(dāng)前實(shí)例對(duì)象和對(duì)象的data屬性掩驱,將data屬性中的子元素重新定義為當(dāng)前對(duì)象的訪問(wèn)器屬性

set方法被觸發(fā)之后芒划,vue實(shí)例的text屬性跟著變化,但是<span>的內(nèi)容并沒(méi)有變化欧穴,下面的內(nèi)容將會(huì)介紹“訂閱/發(fā)布模式”來(lái)解決這個(gè)問(wèn)題民逼。

4 雙向綁定的實(shí)現(xiàn)

在實(shí)現(xiàn)雙向綁定之前要先學(xué)習(xí)一下“訂閱/發(fā)布模式”。訂閱發(fā)布模式(又稱(chēng)為觀察者模式)定義一種一對(duì)多的關(guān)系涮帘,讓多個(gè)觀察者同時(shí)監(jiān)聽(tīng)一個(gè)主題對(duì)象拼苍,主題對(duì)象狀態(tài)發(fā)生改變的時(shí)候通知所有的觀察者。

發(fā)布者發(fā)出通知 => 主題對(duì)象收到通知并推送給訂閱者 => 訂閱者執(zhí)行相應(yīng)的操作

看下面的代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>訂閱/發(fā)布模式</title>
</head>
<body>
<script>
    /**
     * 定義一個(gè)發(fā)布者publisher
     * */
    var pub = {
        publish: function () {
            dep.notify();
        }
    }
    /**
     * 三個(gè)訂閱者
     * */
    var sub1 = {
        update: function () {
            console.log(1);
        }
    };
    var sub2 = {
        update: function () {
            console.log(2);
        }
    };
    var sub3 = {
        update: function () {
            console.log(3);
        }
    }
    /**
     * 一個(gè)主題對(duì)象
     * */
    function Dep () {
        this.subs = [sub1, sub2, sub3];
    }
    Dep.prototype.notify = function () {
        this.subs.forEach(function (sub) {
            sub.update();
        })
    }
    // 發(fā)布者發(fā)布消息调缨,主題對(duì)象執(zhí)行notifiy方法疮鲫,觸發(fā)所有訂閱者響應(yīng),執(zhí)行update
    var dep = new Dep();
    pub.publish();
</script>
</body>
</html>
效果4
  1. 定義發(fā)布者對(duì)象pub弦叶,對(duì)象中定義publish方法俊犯,方法調(diào)用主題對(duì)象實(shí)例dep的notify()方法
  2. 定義三個(gè)訂閱者對(duì)象,對(duì)象中定義update方法湾蔓,三個(gè)對(duì)象的update方法分別輸出1瘫析,2,3
  3. 定義一個(gè)主題方法類(lèi),主題對(duì)象中定義數(shù)組屬性subs贬循,包含三個(gè)訂閱者對(duì)象
  4. 在主題方法類(lèi)的原型對(duì)象上定義通知方法notify咸包,方法中循環(huán)調(diào)用三個(gè)訂閱者對(duì)象的update()方法
  5. 實(shí)例化主題方法類(lèi)得到實(shí)例dep
  6. 調(diào)用發(fā)布者對(duì)象的通知方法notifiy(),分別輸出1杖虾,2烂瘫,3

每當(dāng)創(chuàng)建一個(gè)Vue實(shí)例的時(shí)候,主要做了兩件事情奇适,第一是監(jiān)聽(tīng)數(shù)據(jù):observe(data)坟比,第二個(gè)是編譯HTML:nodeToFragment(id)。
在監(jiān)聽(tīng)數(shù)據(jù)過(guò)程中嚷往,為data的每一個(gè)屬性生成主題對(duì)象dep葛账。
在編譯HTML的過(guò)程中,為每個(gè)與數(shù)據(jù)綁定相關(guān)的節(jié)點(diǎn)生成一個(gè)訂閱者watcher皮仁,watcher會(huì)將自己添加到相應(yīng)屬性的dep中籍琳。
前面已經(jīng)實(shí)現(xiàn)了:修改輸入框內(nèi)容 => 在事件回調(diào)函數(shù)中修改屬性值 => 觸發(fā)屬性set方法。
接下來(lái)我們要實(shí)現(xiàn)的是:發(fā)出通知dep.notify() => 觸發(fā)訂閱者的updata方法 => 更新視圖贷祈,實(shí)現(xiàn)這個(gè)目標(biāo)的關(guān)鍵是如何將watcher添加到關(guān)聯(lián)屬性的dep中去趋急。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>雙向綁定的實(shí)現(xiàn)</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="text">
    {{ text }}
</div>
<script>
    /**
     * 使用defineProperty將data中的text設(shè)置為vm的訪問(wèn)器屬性
     * @param obj 對(duì)象
     * @param key 屬性名
     * @param val 屬性值
     */
    function defineReactive (obj, key, val) {
        var dep = new Dep();
        Object.defineProperty(obj, key, {
            get: function () {
                //  如果主題對(duì)象類(lèi)的靜態(tài)屬性target有值, 此時(shí)Watcher方法被調(diào)用势誊,給主題對(duì)象添加訂閱者
                if (Dep.target) dep.addSub(Dep.target);
                return val;
            },
            set: function (newVal) {
                if (newVal === val) return
                val = newVal;
                // 主題對(duì)象作為發(fā)布者收到通知推送給訂閱者
                dep.notify();
            }
        })
    }
    /**
     * 給vue實(shí)例定義訪問(wèn)器屬性
     * @param obj vue實(shí)例中的數(shù)據(jù)
     * @param vm vue對(duì)象
     */
    function observe (obj, vm) {
        Object.keys(obj).forEach(function (key) {
            defineReactive(vm, key, obj[key])
        })
    }
    /**
     * DocumentFragment文檔片段呜达,可以看作節(jié)點(diǎn)容器,它可以包含多個(gè)子節(jié)點(diǎn)粟耻,當(dāng)將它插入到dom中時(shí)只有子節(jié)點(diǎn)插入到目標(biāo)節(jié)點(diǎn)中查近。
     * 使用documentfragment處理節(jié)點(diǎn)速度和性能要高于直接操作dom。vue編譯的時(shí)候勋颖,就是將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到documentfragment
     * 中嗦嗡,經(jīng)過(guò)處理后再將documentfragment整體返回到掛載目標(biāo)中。
     * @param node 節(jié)點(diǎn)
     * @param vm vue實(shí)例
     * */
    function nodeToFragment (node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
            compile(child, vm);
            flag.appendChild(child);
        }
        return flag;
    }

    /**
     * 給子節(jié)點(diǎn)初始化綁定vue實(shí)例中的屬性值
     * @param node 子節(jié)點(diǎn)
     * @param vm vue實(shí)例
     */
    function compile (node, vm) {
        var reg = /\{\{(.*)\}\}/;
        // 節(jié)點(diǎn)類(lèi)型為元素
        if (node.nodeType === 1) {
            var attr = node.attributes;
            // 解析屬性
            for (var i = 0; i < attr.length; i++) {
                if (attr[i].nodeName === 'v-model') {
                    // 獲取v-model綁定的屬性名
                    var name = attr[i].nodeValue;
                    node.addEventListener('input', function (e) {
                        // 給相應(yīng)的data屬性賦值饭玲,觸發(fā)set方法
                        vm[name] = e.target.value
                    });
                    // 將data的值賦給該node
                    node.value = vm[name];
                    node.removeAttribute('v-model');
                }
            }
            new Watcher(vm, node, name, 'input')
        }
        if (node.nodeType === 3) {
            if (reg.test(node.nodeValue)) {
                var name = RegExp.$1; // 獲取匹配到的字符串
                name = name.trim();
                // 將data的值賦給該node
                new Watcher(vm, node, name, 'text');
            }
        }
    }

    /**
     * 編譯 HTML 過(guò)程中,為每個(gè)與 data 關(guān)聯(lián)的節(jié)點(diǎn)生成一個(gè) Watcher
     * @param vm
     * @param node
     * @param name
     * @param nodeType
     * @constructor
     */
    function Watcher (vm, node, name, nodeType) {
        // 將當(dāng)前對(duì)象賦值給全局變量Dep.target
        Dep.target = this;
        this.name = name;
        this.node = node;
        this.vm = vm;
        this.nodeType = nodeType;
        this.update();
        Dep.target = null;
    }
    Watcher.prototype = {
        update: function () {
            this.get();
            if (this.nodeType === 'text') {
                this.node.nodeValue = this.value;
            }
            if (this.nodeType === 'input') {
                this.node.value = this.value;
            }
        },
        get: function () {
            this.value = this.vm[this.name];
        }
    }

    /**
     * 定義一個(gè)主題對(duì)象
     * @constructor
     */
    function Dep () {
        this.subs = [];
    }

    /**
     * 定義主題對(duì)象的添加方法和通知變化方法
     * @type {{addSub: Dep.addSub, notify: Dep.notify}}
     */
    Dep.prototype = {
        addSub: function (sub) {
            this.subs.push(sub);
        },
        notify: function () {
            this.subs.forEach(function (sub) {
                sub.update();
            });
        }
    };

    /**
     * 定義Vue類(lèi)
     * @param options Vue參數(shù)選項(xiàng)
     * @constructor
     */
    function Vue (options) {
        this.data = options.data;
        var data = this.data;
        observe(data, this);
        var id = options.el;
        var dom = nodeToFragment(document.getElementById(id), this);
        // 編譯完成后叁执,將dom返回到app中
        document.getElementById(id).appendChild(dom);
    }
    // 定義Vue實(shí)例
    var vm = new Vue({
        el: 'app',
        data: {
            text: 'hello world'
        }
    })
</script>
</body>
</html> 
效果5

這里不再逐句分析茄厘,只把重點(diǎn)說(shuō)明一下

  1. 定義主題對(duì)象Dep,對(duì)象中有addSub和notify兩個(gè)方法谈宛,前者負(fù)責(zé)向當(dāng)前對(duì)象中添加訂閱者次哈,后者輪詢(xún)訂閱者,調(diào)用訂閱者的更新方法update()
  2. 定義觀察者對(duì)象方法Watcher吆录,在方法中先將自己賦給一個(gè)全局變量Dep.target窑滞,其實(shí)是給主題類(lèi)Dep定義了一個(gè)靜態(tài)屬性target,可以直接使用Dep.target訪問(wèn)這個(gè)靜態(tài)屬性。然后給類(lèi)定義共有屬性name(vue實(shí)例中的訪問(wèn)器屬性名“text”)哀卫,node(html標(biāo)簽巨坊,如<input>,{{text}})此改,vm(當(dāng)前vue實(shí)例)趾撵,nodeType(html標(biāo)簽類(lèi)型),其次執(zhí)行update方法共啃,進(jìn)而執(zhí)行了原型對(duì)象上的get方法占调,get方法中的this.vm[this.name]讀取了vm中的訪問(wèn)器屬性,從而觸發(fā)了訪問(wèn)器屬性的get方法移剪,get方法中將wathcer添加到對(duì)應(yīng)訪問(wèn)器屬性的dep中究珊,同時(shí)將屬性值賦給臨時(shí)變量value。再者纵苛,獲取屬性的值(保存在臨時(shí)變量value中)苦银,然后更新視圖。最后將Dep.target設(shè)為空赶站。因?yàn)樗侨肿兞酷B玻彩莣atcher與dep關(guān)聯(lián)的唯一橋梁,任何時(shí)刻都必須保證Dep.target只有一個(gè)值贝椿。
  3. 在編譯方法compile中想括,劫持子節(jié)點(diǎn)的時(shí)候,在節(jié)點(diǎn)上定義一個(gè)觀察者對(duì)象Watcher
  4. defineReactive方法中烙博,定義訪問(wèn)器屬性的時(shí)候瑟蜈,在存取描述符get中,如果主題對(duì)象類(lèi)的靜態(tài)屬性target有值渣窜, 此時(shí)Watcher方法被調(diào)用铺根,給主題對(duì)象添加訂閱者。

data中的數(shù)據(jù)重新定義為訪問(wèn)器屬性乔宿,get中將當(dāng)前數(shù)據(jù)對(duì)應(yīng)的節(jié)點(diǎn)添加到主題對(duì)象中位迂,set方法中通知數(shù)據(jù)對(duì)應(yīng)的節(jié)點(diǎn)更新。編譯過(guò)程將data數(shù)據(jù)生成數(shù)據(jù)節(jié)點(diǎn)详瑞,并生成一個(gè)觀察者來(lái)觀察節(jié)點(diǎn)變化掂林。

總結(jié)

本文介紹了vue.js的簡(jiǎn)單實(shí)現(xiàn)以及相關(guān)的知識(shí),包含MVC坝橡,MVP泻帮,MVVM的原理,對(duì)象的訪問(wèn)器屬性计寇,html的文檔片段(DocumentFragment)锣杂,觀察者模式脂倦。vue.js的實(shí)現(xiàn)主要介紹數(shù)據(jù)編譯(compile),通過(guò)文檔片段實(shí)現(xiàn)數(shù)據(jù)劫持掛載元莫,通過(guò)觀察者模式(訂閱發(fā)布模式)的實(shí)現(xiàn)數(shù)據(jù)雙向綁定等內(nèi)容赖阻。

參考

https://www.cnblogs.com/tylerdonet/p/9893065.html
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柒竞,隨后出現(xiàn)的幾起案子政供,更是在濱河造成了極大的恐慌,老刑警劉巖朽基,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件布隔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡稼虎,警方通過(guò)查閱死者的電腦和手機(jī)衅檀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)霎俩,“玉大人哀军,你說(shuō)我怎么就攤上這事〈蛉矗” “怎么了杉适?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)柳击。 經(jīng)常有香客問(wèn)我猿推,道長(zhǎng),這世上最難降的妖魔是什么捌肴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任蹬叭,我火速辦了婚禮,結(jié)果婚禮上状知,老公的妹妹穿的比我還像新娘秽五。我一直安慰自己,他們只是感情好饥悴,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布坦喘。 她就那樣靜靜地躺著,像睡著了一般铺坞。 火紅的嫁衣襯著肌膚如雪起宽。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,021評(píng)論 1 291
  • 那天济榨,我揣著相機(jī)與錄音,去河邊找鬼绿映。 笑死擒滑,一個(gè)胖子當(dāng)著我的面吹牛腐晾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丐一,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼藻糖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了库车?” 一聲冷哼從身側(cè)響起巨柒,我...
    開(kāi)封第一講書(shū)人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柠衍,沒(méi)想到半個(gè)月后洋满,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡珍坊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年牺勾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阵漏。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驻民,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出履怯,到底是詐尸還是另有隱情回还,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布叹洲,位于F島的核電站柠硕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疹味。R本人自食惡果不足惜仅叫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糙捺。 院中可真熱鬧诫咱,春花似錦蜕企、人聲如沸剩岳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)签钩。三九已至掏呼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铅檩,已是汗流浹背憎夷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昧旨,地道東北人拾给。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓祥得,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蒋得。 傳聞我的和親對(duì)象是個(gè)殘疾皇子级及,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350