原生JS模擬Vue雙向數(shù)據(jù)綁定

童鞋們都應(yīng)該知道Vue2.x很重要的特性(數(shù)據(jù)雙向綁定、虛擬DOM),所以在面試的時候就經(jīng)常有面試官問小白同學(xué)說數(shù)據(jù)雙向綁定原理是什么,虛擬DOM是什么,給我實現(xiàn)一個唄。所以給大家展示一個最簡的雙向綁定的案例,也參考了網(wǎng)上一些資料。至于虛擬DOM的實現(xiàn)后面再說吧,網(wǎng)上資料也很多很全因為畢竟react都出來好久了。(都知道網(wǎng)上資料一大堆,大都不著調(diào)。)直接上代碼?。?!

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="msg">
        <span>{{msg}}</span>
    </div>
    <script src="mvvm/watcher.js"></script>
    <script src="mvvm/observer.js"></script>
    <script src="mvvm/compile.js"></script>
    <script src="mvvm/index.js"></script>
    <script>
        let vm = new Vue({
            el: "app",
            data: {
                msg: 'hello world'
            }
        })
    </script>
</body>
</html>
  • index.js
// 這個文件為入口文件,也就是Vue的構(gòu)造函數(shù)
function Vue(options) {
    // 傳遞過來的對象
    this.data = options.data;
    this.id = options.el;
    // 1.第一步先將data中所有的數(shù)據(jù)進行監(jiān)聽
    //   這個函數(shù)一般使用遞歸的方式完成所有屬性的監(jiān)聽
    observer(this.data, this);

    // 2.第二步將所有DOM節(jié)點的翻譯出來,也就是說將v-model,
    //  {{}}等翻譯成你想要的數(shù)據(jù)。其次還有將v-model的數(shù)據(jù)進行監(jiān)聽,使用觀察者模式,完成雙向綁定
    getAllNode(document.getElementById(this.id), this);
}
  • observer.js
// 定義一個pubsub,這個作用是將所有的觀察者加入其中,
// 并且出發(fā)事件
function pubsub() {
    this.subs = [];
}

pubsub.prototype = {
    // 將需要觀察的數(shù)據(jù)加入subs中
    addSub: function(sub){
        this.subs.push(sub);
    },
    // 執(zhí)行觀察的數(shù)據(jù)上綁定的事件update事件。
    pub: function(){
        console.log(this.subs);
        this.subs.forEach(function(sub){
            sub.update();
        })
    }
}

// 將數(shù)據(jù)都進行監(jiān)聽
function active(obj, key, val) {
    var pubsub1 = new pubsub();
    Object.defineProperty(obj.data, key, {
        // getter,如果獲取數(shù)據(jù)時,會判斷是有需要觀察的數(shù)據(jù),如果有就添加到subs中,沒有不添加
        // Pubsub是一個全局的變量,這個變量必須是全局才能判斷是有需要觀察的數(shù)據(jù)
        get() {
            if(Pubsub.target) {
                // 添加訂閱
                pubsub1.addSub(Pubsub.target);
            }
            return val;
        },
        set(newVal) {
            // 如果數(shù)據(jù)被setter,那么就涉及到及時的更新數(shù)據(jù),
            // 這時只需要進行發(fā)布事件,觀察的數(shù)據(jù)就會執(zhí)行update函數(shù)來執(zhí)行更新操作
            if(val == newVal) {
                return;
            }
            val = newVal;

            // 發(fā)布
            pubsub1.pub();
        }
    })
}

// 監(jiān)聽data中所有的數(shù)據(jù)
function observer(obj, vm) {
    // obj = data
    // vm  = 實例對象
    for(var key in obj) {
        active(vm, key, obj[key]);
    }
}
  • comiple.js
// 獲取到所有節(jié)點,并且進行翻譯
function getAllNode(node, vm) {
    console.log(vm);
    var length = node.childNodes.length;
    for(var i = 0; i < length; i++) {
        compile(node.childNodes[i], vm)
    }
}

// 翻譯
function compile(node, vm) {
    // 匹配到{{}},將其中的值進行觀察。
    var reg = /\{\{(.*)\}\}/;
    // 如果節(jié)點存在并且節(jié)點類型為1時(查看一下為1時一般都是什么節(jié)點)
    if(node!=undefined && node.nodeType == 1) {
        // 獲取到節(jié)點的屬性
        var attr = node.attributes;
        if(attr.length) {
            // 對節(jié)點的屬性循環(huán)處理
            for(var i = 0; i < attr.length; i++) {
                // 如果為v-model時,進行處理
                if(attr[i].nodeName == "v-model") {
                    // 獲取到v-model里面的寫的變量名
                    var name = attr[i].nodeValue;
                    // 給該input增加事件處理,如果內(nèi)容改變,并及時更新data中的數(shù)據(jù)
                    node.addEventListener('input', function(e) {
                        vm.data[name] = e.target.value;
                    })

                    // 修改dom上的數(shù)據(jù),并移除指令
                    console.log(vm.data[name]);
                    node.value = vm.data[name];
                    node.removeAttribute('v-model')
                }
            }
        }else {
            // 匹配到{{}}的dom節(jié)點
            if(reg.test(node.outerText)) {
                var name = RegExp.$1;
                // 拿到變量的名稱
                name = name.trim();

                // 將變量加入到watcher中
                new Watcher(vm, node, name)
            }
        }
    }
}
  • watcher.js
// 全局的變量,這個變量來控制是否當(dāng)前有觀察的數(shù)據(jù)
var Pubsub = {
    target: null
}
// 觀察者定義
function Watcher(vm, node, name) {
    Pubsub.target = this;
    this.name = name;
    this.node = node;
    this.vm = vm;
    // 將數(shù)據(jù)觀察時就要進行更新操作一次
    this.update();
    Pubsub.target = null;
}

Watcher.prototype = {
    // 將更新的數(shù)據(jù)渲染到頁面中去
    update() {
        this.node.innerHTML = this.vm.data[this.name];
    }
}

大家想測試,就將五個文件的代碼復(fù)制下來,運行一下。
運行的同時給大家配一張圖,讓大家容易理解。(這張是盜圖,不知哪位大神用visio畫的,這里我就直接引用了)


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

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

  • 有人說,生活是什么?每個人看法不一樣,而我認為生活來源于每個人的心里。 你有沒有一個想要生活好的心,這...
    林霜閱讀 112評論 0 0
  • 文|紀不了 -1- 你走過這么多的路,看過那么多的云。那你愛過一個人嗎? 起初怕他知道又怕他不知道,用盡一切方法吸...
    紀不了閱讀 1,000評論 6 5
  • 很多朋友問我,寫新體詩究竟要不要加標點符號?根據(jù)我自己的體會,詩歌的標點符號其實也是一種寫作工具,有時要用...
    暗香夜話閱讀 986評論 0 1
  • 孔夫子本則感嘆說明人性自私的一面,容易嚴以待人寬以待己,古來今往人性不變。 既然從古到今都一樣,也不必動不動就發(fā)些...
    海水藍閱讀 207評論 0 0

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