理解VUE2雙向數(shù)據(jù)綁定原理和實(shí)現(xiàn)

一、原理:

1.vue 雙向數(shù)據(jù)綁定是通過 數(shù)據(jù)劫持 結(jié)合 發(fā)布訂閱模式的方式來實(shí)現(xiàn)的残家, 也就是說數(shù)據(jù)和視圖同步榆俺,數(shù)據(jù)發(fā)生變化,視圖跟著變化坞淮,視圖變化茴晋,數(shù)據(jù)也隨之發(fā)生改變;

2.核心:關(guān)于VUE雙向數(shù)據(jù)綁定回窘,其核心是 Object.defineProperty()方法晃跺;

3.介紹一下Object.defineProperty()方法
(1)Object.defineProperty(obj, prop, descriptor) ,這個語法內(nèi)有三個參數(shù)毫玖,分別為 obj (要定義其上屬性的對象) prop (要定義或修改的屬性) descriptor (具體的改變方法)
(2)簡單地說,就是用這個方法來定義一個值凌盯。當(dāng)調(diào)用時我們使用了它里面的get方法付枫,當(dāng)我們給這個屬性賦值時,又用到了它里面的set方法驰怎;

set阐滩,get方法初步了解

二、先簡單的實(shí)現(xiàn)一個js的雙向數(shù)據(jù)綁定來熟悉一下這個方法:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <input type="text" id="a">
        <span id="b"></span>
    </div>
</body>
<script>
    var obj = {};  //定義一個空對象

    var val = 'zhao';  //賦予初始值

    Object.defineProperty(obj, 'val', {//定義要修改對象的屬性

        get: function () {

            return val;
        },

        set: function (newVal) { 

             val = newVal;//定義val等于修改后的內(nèi)容

            document.getElementById('a').value = val;//讓文本框的內(nèi)容等于val
            
            document.getElementById('b').innerHTML = val;//讓span的內(nèi)容等于val

        }

    });
    document.addEventListener('keyup', function (e) {//當(dāng)在文本框輸入內(nèi)容時讓對象里你定義的val等于文本框的值

        obj.val = e.target.value;

    })

</script>

</html>

這樣我們就能實(shí)現(xiàn)js的雙向數(shù)據(jù)綁定县忌,也對這個方法有初步的了解掂榔;
這個例子實(shí)現(xiàn)的效果是:隨著文本框輸入文字的變化继效,span中會同步顯示相同的文字內(nèi)容;這樣就實(shí)現(xiàn)了 model => view 以及 view => model 的雙向綁定装获。
通過添加事件監(jiān)聽keyup來觸發(fā)set方法瑞信,而set再修改了訪問器屬性的同時,也修改了dom樣式穴豫,改變了span標(biāo)簽內(nèi)的文本凡简。

三、實(shí)現(xiàn)一個真正的雙向綁定的原理

1.實(shí)現(xiàn)效果
先來看一下vue雙向數(shù)據(jù)綁定是如何進(jìn)行的精肃,以便我們確定好思考方向

image

image

2.任務(wù)拆分

拆分任務(wù)可以讓我們的思路更加清晰:
(1)將vue中的data中的內(nèi)容綁定到輸入文本框和文本節(jié)點(diǎn)中
(2)當(dāng)文本框的內(nèi)容改變時秤涩,vue實(shí)例中的data也同時發(fā)生改變
(3)當(dāng)data中的內(nèi)容發(fā)生改變時,輸入框及文本節(jié)點(diǎn)的內(nèi)容也發(fā)生變化

3.開始任務(wù)1——綁定內(nèi)容
我們先了解一下 DocuemntFragment(碎片化文檔)這個概念司抱,你可以把他認(rèn)為一個dom節(jié)點(diǎn)收容器筐眷,當(dāng)你創(chuàng)造了10個節(jié)點(diǎn),當(dāng)每個節(jié)點(diǎn)都插入到文檔當(dāng)中都會引發(fā)一次瀏覽器的回流习柠,也就是說瀏覽器要回流10次匀谣,十分消耗資源。
而使用碎片化文檔津畸,也就是說我把10個節(jié)點(diǎn)都先放入到一個容器當(dāng)中振定,最后我再把容器直接插入到文檔就可以了!瀏覽器只回流了1次肉拓。
注意:還有一個很重要的特性是后频,如果使用appendChid方法將原dom樹中的節(jié)點(diǎn)添加到DocumentFragment中時,會刪除原來的節(jié)點(diǎn)暖途。

舉個例子:
可以看到卑惜,我的app中有兩個子節(jié)點(diǎn),一個元素節(jié)點(diǎn)驻售,一個文本節(jié)點(diǎn)
但是露久,當(dāng)我通過DocumentFragment 劫持?jǐn)?shù)據(jù)一下后

image
image
image

注意:我的碎片化文檔是將子節(jié)點(diǎn)都劫持了過來锥忿,而我的id為app的div內(nèi)已經(jīng)沒有內(nèi)容了丛版。
同時要主要我while的判斷條件扇售。判斷是否有子節(jié)點(diǎn)慧耍,因?yàn)槲颐看蝍ppendChild都把node中的第一個子節(jié)點(diǎn)劫持走了仑性,node中就會少一個诗良,直到?jīng)]有的時候杂腰,child也就變成了undefined森渐,也就終止了循環(huán)类腮。

來實(shí)現(xiàn)內(nèi)容綁定
我們要考慮兩個問題臊泰,一個是如何綁定要input上,另一個是如何綁定要文本節(jié)點(diǎn)中蚜枢。
這樣思路就來了缸逃,我們已經(jīng)獲取到了div的所以子節(jié)點(diǎn)了针饥,就在DocumentFragment里面,然后對每一個節(jié)點(diǎn)進(jìn)行處理需频,看是不是有跟vm實(shí)例中有關(guān)聯(lián)的內(nèi)容丁眼,如果有,修改這個節(jié)點(diǎn)的內(nèi)容贺辰。然后重新添加入DocumentFragment中户盯。

首先,我們寫一個處理每一個節(jié)點(diǎn)的函數(shù)饲化,如果有input綁定v-model屬性或者有{{ xxx }}的文本節(jié)點(diǎn)出現(xiàn)莽鸭,就進(jìn)行內(nèi)容替換,替換為vm實(shí)例中的data中的內(nèi)容

image

然后吃靠,在向碎片化文檔中添加節(jié)點(diǎn)時硫眨,每個節(jié)點(diǎn)都處理一下。

image

創(chuàng)建Vue的實(shí)例化函數(shù)

image

效果圖如下:

image

我們成功將內(nèi)容都綁定到了輸入框與文本節(jié)點(diǎn)上巢块!

4礁阁、實(shí)現(xiàn)任務(wù)2——【view => model
對于此任務(wù),我們從輸入框考慮族奢,輸入框的問題姥闭,輸入框如何改變data。我們通過事件監(jiān)聽器keyup越走,input等棚品,來獲取到最新的value,然后通過Object.defineProperty將獲取的最新的value廊敌,賦值給實(shí)例vm的text铜跑,我們把vm實(shí)例中的data下的text通過Object.defineProperty設(shè)置為訪問器屬性,這樣給vm.text賦值骡澈,就觸發(fā)了set锅纺。set函數(shù)的作用一個是更新data中的text,另一個等到任務(wù)三再說肋殴。

首先實(shí)現(xiàn)一個響應(yīng)式監(jiān)聽屬性的函數(shù)囤锉。一旦有賦新值就發(fā)生變化

image

然后,實(shí)現(xiàn)一個觀察者护锤,對于一個實(shí)例 每一個屬性值都進(jìn)行觀察嚼锄。

image

改寫編譯函數(shù),注意由于改成了訪問器屬性蔽豺,訪問的方法也產(chǎn)生變化,同時添加了事件監(jiān)聽器拧粪,把實(shí)例的text值隨時更新

image

實(shí)例函數(shù)中修陡,觀察data中的所有屬性值沧侥,注意增添了observe

image

最終我們改變input中的內(nèi)容能改變data中的數(shù)據(jù),單頁面卻沒有刷新

image
image

4魄鸦、實(shí)現(xiàn)任務(wù)3——【model => view】
通過修改vm實(shí)例的屬性 該改變輸入框的內(nèi)容 與 文本節(jié)點(diǎn)的內(nèi)容宴杀。
這里涉及到一個問題 需要我們注意,當(dāng)我們修改輸入框拾因,改變了vm實(shí)例的屬性旺罢,這是1對1的。
但是绢记,我們可能在頁面中多處用到 data中的屬性扁达,這是1對多的。也就是說蠢熄,改變1個model的值可以改變多個view中的值跪解。
這就需要我們引入一個新的知識點(diǎn):

訂閱/發(fā)布者模式
訂閱發(fā)布模式(又稱觀察者模式)定義了一種一對多的關(guān)系,讓多個觀察者同時監(jiān)聽某一個主題對象签孔,這個主題對象的狀態(tài)發(fā)生改變時就會通知所有觀察者對象。

發(fā)布者發(fā)出通知 => 主題對象收到通知并推送給訂閱者 => 訂閱者執(zhí)行相應(yīng)操作
1
舉個例子:

image

之前提到的set函數(shù)的第二個作用 就是來提醒訂閱者 進(jìn)行noticy操作图仓,告訴他們:“我的text變了但绕!” 文本節(jié)點(diǎn)變成了訂閱者救崔,接到消息后帚豪,立馬進(jìn)行update操作

回顧一下,每當(dāng) new 一個 Vue草丧,主要做了兩件事:第一個是監(jiān)聽數(shù)據(jù):observe(data)狸臣,第二個是編譯 HTML:nodeToFragement(id)昌执。
在監(jiān)聽數(shù)據(jù)的過程中,我們會為 data 中的每一個屬性生成一個主題對象 dep煤禽。

在編譯 HTML 的過程中岖赋,會為每個與數(shù)據(jù)綁定相關(guān)的節(jié)點(diǎn)生成一個訂閱者 watcher,watcher 會將自己添加到相應(yīng)屬性的 dep 容器中选脊。

我們已經(jīng)實(shí)現(xiàn):修改輸入框內(nèi)容 => 在事件回調(diào)函數(shù)中修改屬性值 => 觸發(fā)屬性的 set 方法恳啥。

接下來我們要實(shí)現(xiàn)的是:發(fā)出通知 dep.notify() => 觸發(fā)訂閱者的 update 方法 => 更新視圖。
這里的關(guān)鍵邏輯是:如何將 watcher 添加到關(guān)聯(lián)屬性的 dep 中翁垂。

注意: 我把直接賦值的操作改為了 添加一個 Watcher 訂閱者

image

那么沿猜,Watcher又該做些什么呢邢疙?

image

首先望薄,將自己賦給了一個全局變量 Dep.target痕支;

其次,執(zhí)行了 update 方法另绩,進(jìn)而執(zhí)行了 get 方法花嘶,get 的方法讀取了 vm 的訪問器屬性椭员,從而觸發(fā)了訪問器屬性的 get 方法,get 方法中將該 watcher 添加到了對應(yīng)訪問器屬性的 dep 中侍芝;

再次州叠,獲取屬性的值凶赁,然后更新視圖逆甜。

最后忆绰,將 Dep.target 設(shè)為空可岂。因?yàn)樗侨肿兞柯拼猓彩?watcher 與 dep 關(guān)聯(lián)的唯一橋梁平斩,任何時刻都必須保證 Dep.target 只有一個值咽块。

image
image

最終我們就實(shí)現(xiàn)了這個雙向數(shù)據(jù)綁定功能侈沪,雖然很繁瑣亭罪,但我相信,你多打幾遍情组,一定會對你有所幫助院崇,加油吧!!

最后小編給大家附上源碼:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>

    <body>

        <div id="app">

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

        </div>

    </body>

    <script type="text/javascript">
        //          編譯函數(shù)
        function compile(node, vm) {

            var reg = /\{\{(.*)\}\}/; // 來匹配{{xxx}}中的xxx

            //如果是元素節(jié)點(diǎn)
            if(node.nodeType === 1) {

                var attr = node.attributes;

                //解析元素節(jié)點(diǎn)的所有屬性

                for(let i = 0; i < attr.length; i++) {

                    if(attr[i].nodeName == 'v-model') {

                        var name = attr[i].nodeValue //看看是與哪一個數(shù)據(jù)相關(guān)

                        node.addEventListener('input', function(e) { //將與其相關(guān)的數(shù)據(jù)改為最新值
                            vm[name] = e.target.value
                        })

                        node.value = vm.data[name]; //將data中的值賦予給該node

                        node.removeAttribute('v-model')

                    }

                }

            }

            //如果是文本節(jié)點(diǎn)

            if(node.nodeType === 3) {

                if(reg.test(node.nodeValue)) {

                    var name = RegExp.$1; //獲取到匹配的字符串

                    name = name.trim();

                    //                  node.nodeValue = vm[name]; //將data中的值賦予給該node

                    new Watcher(vm, node, name) //綁定一個訂閱者
                }

            }

        }

        //          在向碎片化文檔中添加節(jié)點(diǎn)時底瓣,每個節(jié)點(diǎn)都處理一下

        function nodeToFragment(node, vm) {

            var fragment = document.createDocumentFragment();

            var child;

            while(child = node.firstChild) {

                compile(child, vm);

                fragment.appendChild(child);

            }

            return fragment

        }

        //          Vue構(gòu)造函數(shù)     
        //      觀察data中的所有屬性值濒持,注意增添了observe

        function Vue(options) {

            this.data = options.data;

            observe(this.data, this)

            var id = options.el;

            var dom = nodeToFragment(document.getElementById(id), this)

            //處理完所有節(jié)點(diǎn)后柑营,重新把內(nèi)容添加回去
            document.getElementById(id).appendChild(dom)

        }

        //      實(shí)現(xiàn)一個響應(yīng)式監(jiān)聽屬性的函數(shù)村视。一旦有賦新值就發(fā)生變化 

        function defineReactive(obj, key, val) {

            var dep = new Dep();

            Object.defineProperty(obj, key, {

                get: function() {

                    if(Dep.target) {

                        dep.addSub(Dep.target)

                    }

                    return val

                },
                set: function(newVal) {

                    if(newVal === val) {

                        return

                    }

                    val = newVal;

                    console.log('新值' + val);

                    //一旦更新立馬通知

                    dep.notify();

                }

            })

        }

        //      實(shí)現(xiàn)一個觀察者,對于一個實(shí)例 每一個屬性值都進(jìn)行觀察惋嚎。

        function observe(obj, vm) {

            for(let key of Object.keys(obj)) {

                defineReactive(vm, key, obj[key]);

            }

        }

        //      Watcher監(jiān)聽者

        function Watcher(vm, node, name) {

            Dep.target = this;

            this.vm = vm;
            this.node = node;
            this.name = name;

            this.update();

            Dep.target = null;

        }

        Watcher.prototype = {

            update() {
                this.get();
                this.node.nodeValue = this.value //更改節(jié)點(diǎn)內(nèi)容的關(guān)鍵
            },
            get() {
                this.value = this.vm[this.name] //觸發(fā)相應(yīng)的get
            }

        }

        //      dep構(gòu)造函數(shù)

        function Dep() {
            this.subs = []
        }
        Dep.prototype = {
            addSub(sub) {
                this.subs.push(sub)
            },
            notify() {
                this.subs.forEach(function(sub) {
                    sub.update();
                })
            }
        }

        var vm = new Vue({

            el: 'app',

            data: {
                text: '趙剛'
            }

        })
    </script>

</html>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绞旅,隨后出現(xiàn)的幾起案子因悲,更是在濱河造成了極大的恐慌,老刑警劉巖讯检,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件人灼,死亡現(xiàn)場離奇詭異誊涯,居然都是意外死亡暴构,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砾隅,“玉大人晴埂,你說我怎么就攤上這事【停” “怎么了琅锻?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長僵芹。 經(jīng)常有香客問我拇派,道長凿跳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任躬审,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拯腮。我一直安慰自己萝喘,他們只是感情好启妹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布咙崎。 她就那樣靜靜地躺著网杆,像睡著了一般笑旺。 火紅的嫁衣襯著肌膚如雪乌妙。 梳的紋絲不亂的頭發(fā)上虐沥,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音脚翘,去河邊找鬼。 笑死海诲,一個胖子當(dāng)著我的面吹牛闸昨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼阿浓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚣抗,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锭魔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抵屿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朝群,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡右莱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年耘柱,在試婚紗的時候發(fā)現(xiàn)自己被綠了士袄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窖剑。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡西土,死狀恐怖鞍盗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肋乍,我是刑警寧澤墓造,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布觅闽,位于F島的核電站,受9級特大地震影響尸闸,放射性物質(zhì)發(fā)生泄漏吮廉。R本人自食惡果不足惜畸肆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一轴脐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦徽级、人聲如沸聊浅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绞呈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間艺智,已是汗流浹背十拣。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工夭问, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弄跌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像直撤,于是被迫代替她去往敵國和親蜕着。 傳聞我的和親對象是個殘疾皇子承匣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

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

  • 本文是lhyt本人原創(chuàng)韧骗,希望用通俗易懂的方法來理解一些細(xì)節(jié)和難點(diǎn)袍暴。轉(zhuǎn)載時請注明出處。文章最早出現(xiàn)于本人github...
    lhyt閱讀 2,215評論 0 4
  • 前言 在之前面試中岗宣,有被問到這個問題耗式,雖然了解過是劫持Object.defineProperty方法,但是其細(xì)節(jié)并...
    Aleph_Zheng閱讀 1,074評論 0 5
  • 1.目前雙向數(shù)據(jù)綁定的方法 發(fā)布者-訂閱者模式(backbone.js) 因?yàn)楸疚难芯康牟皇沁@個,所以不做...
    錢羅羅_閱讀 7,753評論 1 10
  • 從此企巢,成為獨(dú)立個體吧让蕾,既然沒有一點(diǎn)溫暖是專屬于你的(^_^)
    yuku233閱讀 250評論 0 0
  • 01晚餐哪里吃 我想要健康探孝,我要身心愉悅顿颅,晚上,我從父母家里離開庇配,帶女兒去了健身房绍些,準(zhǔn)時上安老師的哈他瑜伽柬批,上她的...
    夕夕夕木木木閱讀 315評論 0 3