vue雙向數(shù)據(jù)綁定原理

本文采用了比較特殊的input和v-model指令 實(shí)際上vue的指令解析模板很復(fù)雜,本文重點(diǎn)是理解數(shù)據(jù)更新的思想

幾種實(shí)現(xiàn)雙向綁定的做法

目前幾種主流的mvc(vm)框架都實(shí)現(xiàn)了單向數(shù)據(jù)綁定腹纳,而我所理解的雙向數(shù)據(jù)綁定無非就是在單向綁定的基礎(chǔ)上給可輸入元素(input、textare等)添加了change(input)事件锨并,來動(dòng)態(tài)修改model和 view聪蘸,并沒有多高深数焊。所以無需太過介懷是實(shí)現(xiàn)的單向或雙向綁定。
實(shí)現(xiàn)數(shù)據(jù)綁定的做法有大致如下幾種:

發(fā)布者-訂閱者模式(backbone.js)

臟值檢查(angular.js)

數(shù)據(jù)劫持(vue.js)

發(fā)布者-訂閱者模式:

一般通過sub, pub的方式實(shí)現(xiàn)數(shù)據(jù)和視圖的綁定監(jiān)聽膨处,更新數(shù)據(jù)方式通常做法是 vm.set('property', value)见秤,這里有篇文章講的比較詳細(xì),有興趣可點(diǎn)這里

這種方式現(xiàn)在畢竟太low了真椿,我們更希望通過 vm.property = value 這種方式更新數(shù)據(jù)鹃答,同時(shí)自動(dòng)更新視圖,于是有了下面兩種方式

臟值檢查:

angular.js 是通過臟值檢測的方式比對數(shù)據(jù)是否有變更突硝,來決定是否更新視圖挣跋,最簡單的方式就是通過 setInterval() 定時(shí)輪詢檢測數(shù)據(jù)變動(dòng),當(dāng)然Google不會(huì)這么low狞换,angular只有在指定的事件觸發(fā)時(shí)進(jìn)入臟值檢測避咆,大致如下:

DOM事件,譬如用戶輸入文本修噪,點(diǎn)擊按鈕等查库。( ng-click )

XHR響應(yīng)事件 ( $http )

瀏覽器Location變更事件 ( $location )

Timer事件( $timeout , $interval )

執(zhí)行 $digest() 或 $apply()

數(shù)據(jù)劫持:

vue.js 則是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個(gè)屬性的setter黄琼,getter樊销,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)脏款。

思路整理

  1. 實(shí)現(xiàn)一個(gè)數(shù)據(jù)監(jiān)聽器Observer围苫,能夠?qū)?shù)據(jù)對象的所有屬性進(jìn)行監(jiān)聽,如有變動(dòng)可拿到最新值并通知訂閱者
  2. 實(shí)現(xiàn)一個(gè)指令解析器Compile撤师,對每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描和解析剂府,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
  3. 實(shí)現(xiàn)一個(gè)Watcher剃盾,作為連接Observer和Compile的橋梁腺占,能夠訂閱并收到每個(gè)屬性變動(dòng)的通知淤袜,執(zhí)行指令綁定的相應(yīng)回調(diào)函數(shù),從而更新視圖
  4. 入口函數(shù)衰伯,整合以上三者

流程圖

mark

數(shù)據(jù)監(jiān)聽器

 function observe(obj, vm) {
        //  對傳入的對象 遍歷 并分別添加 object.defineProperty
        Object.keys(obj).forEach((key) => {
            defineReactive(vm, key, obj[key])
        })
    }

    function defineReactive(vm, key, val) {
        var dep = new Dep();
        Object.defineProperty(vm, key, {
            get: function () {
                // 通過這一步 添加訂閱者
                if (Dep.target) dep.addSub(Dep.target)
                return val;
            },
            set: function (newval) {
                if (newval === val) return
                val = newval;
                // 通知訂閱者
                dep.notify()
            }
        })
    }

    // 需要實(shí)現(xiàn)一個(gè)消息訂閱器
    function Dep() {
        // 消息訂閱的讓容器是一個(gè)數(shù)組 數(shù)組的每一項(xiàng) 都是指代一個(gè)view和mode的中間者
        this.subs = []
    }

    Dep.prototype = {
        addSub: function (sub) {
            this.subs.push(sub)
        },
        notify: function () {
            this.subs.forEach((sub) => {
                // 在這里 需要配合watcher進(jìn)行更新
                sub.update()
            })
        }
    }

實(shí)現(xiàn)Compile

 //  在這里增加dom編譯模板
    function nodeToFragment(node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
            compile(child, vm);
            // 將子節(jié)點(diǎn)劫持到文本節(jié)點(diǎn)中
            flag.appendChild(child)
        }
        return flag
    }

    function compile(node, vm) {
        var reg = /\{\{(.*)\}\}/;
        // 跟據(jù)節(jié)點(diǎn)類型去判斷
        if (node.nodeType === 1) {
            var attr = node.attributes;
            for (var i = 0; i < attr.length; i++) {
                if (attr[i].nodeName === 'v-model') {
                    // 此時(shí) name為text
                    var name = attr[i].nodeValue;
                    // 增加數(shù)據(jù)的變化監(jiān)聽
                    node.addEventListener('input', (e) => {
                        vm[name] = e.target.value;
                })
                    ;
                    // 在這里 因?yàn)?我們的數(shù)據(jù)監(jiān)聽器 已經(jīng)封裝了vm[name]  觸發(fā)了 getter方法 完成了數(shù)據(jù)的初始化
                    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();
                new Watcher(vm, node, name, 'text')
            }
        }
    }

增加watcher 觀察函數(shù)

//訂閱者 搭建數(shù)據(jù)監(jiān)聽變化和變異模板的橋梁
    function Watcher(vm, node, name, nodeType) {
        Dep.target = this;
        this.vm = vm;
        this.node = node;
        this.name = name;
        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];
        }
    }

入口函數(shù)

 function Vue(options) {
        // 將options里面的data屬性 放入數(shù)據(jù)監(jiān)聽器
        this.data = options.data;
        var data = this.data;
        observe(data, this); // this指代vm
        // 對指定id的dom 進(jìn)行頁面的渲染
        this.$el = options.el;
        var id = this.$el;
        var Dom = nodeToFragment(document.getElementById(id), this);
        // 編譯完成之后 將dom 添加到節(jié)點(diǎn)中
        document.getElementById(id).appendChild(Dom)
    }

    var vm = new Vue({
        el: 'app',
        data: {
            text: 'hello world',
            name: '你好自脯,全世界'
        }
    });
    vm.data.text = 'majunchang'


    document.getElementsByClassName('btn')[0].onclick = function () {
        vm.text = 'majunchang'
        vm.name = '又疑瑤臺(tái)鏡贬蛙,飛在青云端'
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市萍启,隨后出現(xiàn)的幾起案子刻帚,更是在濱河造成了極大的恐慌谣旁,老刑警劉巖毡咏,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剃允,死亡現(xiàn)場離奇詭異,居然都是意外死亡杆勇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門饱亿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚜退,“玉大人,你說我怎么就攤上這事彪笼∽曜ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵配猫,是天一觀的道長幅恋。 經(jīng)常有香客問我,道長泵肄,這世上最難降的妖魔是什么捆交? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮腐巢,結(jié)果婚禮上品追,老公的妹妹穿的比我還像新娘。我一直安慰自己冯丙,他們只是感情好肉瓦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胃惜,像睡著了一般泞莉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上船殉,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天鲫趁,我揣著相機(jī)與錄音,去河邊找鬼利虫。 笑死饮寞,一個(gè)胖子當(dāng)著我的面吹牛孝扛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幽崩,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼苦始,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慌申?” 一聲冷哼從身側(cè)響起陌选,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹄溉,沒想到半個(gè)月后咨油,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柒爵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年役电,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棉胀。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡法瑟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唁奢,到底是詐尸還是另有隱情霎挟,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布麻掸,位于F島的核電站酥夭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脊奋。R本人自食惡果不足惜熬北,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诚隙。 院中可真熱鬧蒜埋,春花似錦、人聲如沸最楷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽籽孙。三九已至烈评,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間犯建,已是汗流浹背讲冠。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留适瓦,地道東北人竿开。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓谱仪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親否彩。 傳聞我的和親對象是個(gè)殘疾皇子疯攒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容