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

資訊專欄INFORMATION COLUMN

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

Yumenokanata / 617人閱讀

摘要:什么是雙向數(shù)據(jù)綁定是一個(gè)框架,數(shù)據(jù)綁定簡(jiǎn)單來(lái)說(shuō),就是當(dāng)數(shù)據(jù)發(fā)生變化時(shí),相應(yīng)的視圖會(huì)進(jìn)行更新,當(dāng)視圖更新時(shí),數(shù)據(jù)也會(huì)跟著變化。

什么是雙向數(shù)據(jù)綁定?Vue是一個(gè)MVVM框架,數(shù)據(jù)綁定簡(jiǎn)單來(lái)說(shuō),就是當(dāng)數(shù)據(jù)發(fā)生變化時(shí),相應(yīng)的視圖會(huì)進(jìn)行更新,當(dāng)視圖更新時(shí),數(shù)據(jù)也會(huì)跟著變化。

實(shí)現(xiàn)數(shù)據(jù)綁定的方式大致有以下幾種:

- 1、發(fā)布者-訂閱者模式(backbone.js)
- 2、臟值檢查(angular.js)
- 3、數(shù)據(jù)劫持(vue.js)
發(fā)布者-訂閱者模式

一般通過sub, pub的方式實(shí)現(xiàn)數(shù)據(jù)和視圖的綁定監(jiān)聽,更新數(shù)據(jù)方式通常做法是 vm.set("property", value),有興趣可參考這里

我們更希望可以通過 vm.property = value 這種方式進(jìn)行數(shù)據(jù)更新,同時(shí)自動(dòng)更新視圖。
臟值檢查

angular是通過臟值檢查方式來(lái)對(duì)比數(shù)據(jù)是否變化,來(lái)決定是否更新視圖,最常見的方式是通過setInterval()來(lái)監(jiān)測(cè)數(shù)據(jù)變化,當(dāng)然,只會(huì)在某些指定事件觸發(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ā)布者-訂閱者來(lái)實(shí)現(xiàn)的,數(shù)據(jù)劫持是利用ES5的Object.defineProperty(obj, key, val)來(lái)劫持各個(gè)屬性的的setter以及getter,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,從而觸發(fā)相應(yīng)的回調(diào)來(lái)更新視圖。

一、實(shí)現(xiàn)最基礎(chǔ)的數(shù)據(jù)綁定
    
    輸入的值為:

    
二、雙向數(shù)據(jù)綁定實(shí)現(xiàn)(此處用MVue替代)

上面的只是簡(jiǎn)單的使用了Object.defineProperty(),并不是我們最終想要的效果,最終想要的效果如下:

     
輸入的值為:{{text}}

實(shí)現(xiàn)思路:
1、輸入框以及文本節(jié)點(diǎn)和data中的數(shù)據(jù)進(jìn)行綁定
2、輸入框內(nèi)容變化時(shí),data中的對(duì)應(yīng)數(shù)據(jù)同步變化,即 view => model
3、data中數(shù)據(jù)變化時(shí),對(duì)應(yīng)的文本節(jié)點(diǎn)內(nèi)容同步變化 即 model => view

上述流程如圖所示:

1、實(shí)現(xiàn)一個(gè)數(shù)據(jù)監(jiān)聽器Obverser,對(duì)data中的數(shù)據(jù)進(jìn)行監(jiān)聽,若有變化,通知相應(yīng)的訂閱者。
2、實(shí)現(xiàn)一個(gè)指令解析器Compile,對(duì)于每個(gè)元素上的指令進(jìn)行解析,根據(jù)指令替換數(shù)據(jù),更新視圖。
3、實(shí)現(xiàn)一個(gè)Watcher,用來(lái)連接Obverser和Compile, 并為每個(gè)屬性綁定相應(yīng)的訂閱者,當(dāng)數(shù)據(jù)發(fā)生變化時(shí),執(zhí)行相應(yīng)的回調(diào)函數(shù),從而更新視圖。
4、構(gòu)造函數(shù) (new MVue({}))

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

在初始化MVue實(shí)例時(shí),對(duì)data中每個(gè)屬性劫持監(jiān)聽,同時(shí)進(jìn)行模板編譯,指令解析,最后掛載到相應(yīng)的DOM中。

    function MVue (options) {
        this.$el = options.el;
        this.$data = options.data;

        // 初始化操作,后面會(huì)說(shuō)
        // ...
    }

1、實(shí)現(xiàn) view => model

DocumentFragment(文檔片段)

vue進(jìn)行編譯時(shí),將掛載目標(biāo)的所有子節(jié)點(diǎn)劫持到DocumentFragment中,經(jīng)過一份解析等處理后,再將DocumentFragment整體掛載到目標(biāo)節(jié)點(diǎn)上。

    function nodeToFragment (node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
            compile(child, vm);
             if (child.firstChild) {
                var dom = nodeToFragment(child, vm);
                child.appendChild(dom);
            }
            flag.appendChild(child);
        }
        return flag;
    }
模板編譯(指令解析,事件綁定、初始化數(shù)據(jù)綁定)

編譯過程圖

代碼如下:

    function compile (node, vm) {
        let reg = /{{(.*)}}/;
        // 元素節(jié)點(diǎn)
        if (node.nodeType === 1) {
            var attrs = node.attributes;
            for (let attr of attrs) {
                if (attr.nodeName === "v-model") {
                    // 獲取v-model指令綁定的data屬性
                    var name = attr.nodeValue;
                    // 綁定事件
                    node.addEventListener("input", function(e) {
                        vm.$data[name] = e.target.value;
                    })
                    // 初始化數(shù)據(jù)綁定
                    node.value = vm.$data[name];
                    // 移除v-model 屬性
                    node.removeAttribute("v-model")
                }
            }
        }
        
        // 文本節(jié)點(diǎn)
        if (node.nodeType === 3) {
            if (reg.test(node.nodeValue)) {
                var name = RegExp.$1 && (RegExp.$1.trim());
                // 綁定數(shù)據(jù)到文本節(jié)點(diǎn)中
                 node.nodeValue = node.nodeValue.replace(new RegExp("{{s*(" + name + ")s*}}"), vm.$data[name]);
            }
        }
    }

現(xiàn)在,我們修改下MVue構(gòu)造函數(shù),增加模板編譯,如下:

    function MVue (options) {
        this.$el = options.el;
        this.$data = options.data;

        // 模板編譯
        let elem = document.querySelector(this.$el);
        elem.appendChild(nodeToFragment(elem, this))
    }

那么,我們的view => model 已經(jīng)實(shí)現(xiàn)了,包括初始化綁定默認(rèn)值,只要修改了input中的值,data中對(duì)應(yīng)的值相應(yīng)變化,并觸發(fā)了setter, 更新屬性值等(可以自行在set方法中打印看效果,或者在控制臺(tái)手動(dòng)輸入vm.$data.text也會(huì)看到效果)。
2、實(shí)現(xiàn) model => view
上面可以看出,雖然我們實(shí)現(xiàn)了初始化數(shù)據(jù)綁定,以及輸入框變化時(shí),data中text也會(huì)變化,但是文本節(jié)點(diǎn)仍然沒有任何變化,那么如果做到文本節(jié)點(diǎn)也同步變化呢,這里用的是發(fā)布者-訂閱者模式。

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

發(fā)布者-訂閱者模式又稱為觀察者模式,讓多個(gè)觀察者同時(shí)監(jiān)聽某個(gè)主題對(duì)象,當(dāng)主題對(duì)象發(fā)生變化時(shí),會(huì)通知所有的觀察者對(duì)象,即:發(fā)布者發(fā)出通知給主題對(duì)象 => 主題對(duì)象接收到通知后推送給所有訂閱者 => 訂閱者執(zhí)行相應(yīng)的操作。

1)首先,定義一個(gè)主題對(duì)象,用來(lái)收集所有的訂閱者,并提供notify方法,用來(lái)調(diào)用訂閱者的update方法,從而執(zhí)行相應(yīng)的操作。

    function Dep () {
        this.subs = [];
    }
    Dep.prototype = {
        addSub (sub) {
            this.subs.push(sub);
        },
        notify () {
            this.subs.forEach(sub => {
                // 執(zhí)行訂閱者的update方法
                sub.update();
            })
        }
    }

不難看出,當(dāng)text屬性變化時(shí),會(huì)觸發(fā)set方法,作為發(fā)布者,將數(shù)據(jù)更新消息通過主題對(duì)象發(fā)送給訂閱者, 那么該如何通知呢?
我們知道,在new一個(gè)vue時(shí),會(huì)執(zhí)行兩個(gè)操作,一個(gè)事編譯模板,一個(gè)監(jiān)聽data數(shù)據(jù),在監(jiān)聽data時(shí),vue為data的每個(gè)屬性都生成一個(gè)主題對(duì)象Dep,而在編譯模板時(shí),會(huì)為每個(gè)與數(shù)據(jù)綁定的節(jié)點(diǎn)生成一個(gè)Watcher,那么只要關(guān)聯(lián)了Dep與Watcher,是不是就實(shí)現(xiàn)了消息通知呢,關(guān)鍵邏輯是實(shí)現(xiàn)二者關(guān)聯(lián)。

已實(shí)現(xiàn):輸入框變化 => 觸發(fā)相應(yīng)的事件,修改值 => 觸發(fā)set方法
需要實(shí)現(xiàn):發(fā)出通知dep.notify() => 觸發(fā)訂閱者update方法 => 更新視圖

我們修改下compile中文本節(jié)點(diǎn)內(nèi)容(只修改部分)

    // 文本節(jié)點(diǎn)
    if (node.nodeType === 3) {
        if (reg.test(node.nodeValue)) {
            var name = RegExp.$1 && (RegExp.$1.trim());
            // 綁定數(shù)據(jù)到文本節(jié)點(diǎn)中
           //  node.nodeValue = node.nodeValue.replace(new RegExp("{{s*(" + name + ")s*}}"), vm.$data[name]);
           new Watcher(vm, node, name);
        }
    }

2)其次、實(shí)現(xiàn)訂閱者Watcher

    function Watcher (vm, node, name) {
        // 全局的、唯一
        Dep.target = this;
        this.node = node;
        this.name = name;
        this.vm = vm;
        this.index = index;
        this.update();
        Dep.target = null;
    }

    Watcher.prototype = {
        update () {
            this.get();
            this.node.nodeValue = this.value;
        },
        get () {
            this.value = this.vm.$data[this.name]
        }
    }

首先,定義了一個(gè)全局的Dep.target,然后執(zhí)行了update方法,進(jìn)而執(zhí)行了get方法,都去了this.vm的訪問器屬性, 從而將訂閱的消息保存在該屬性的主題對(duì)象中,并最終將Dep.target設(shè)置為空,全局變量,是watcher和dep之間的唯一橋梁,必須保證Dep.target只有一個(gè)值。

3)接著、實(shí)現(xiàn)一個(gè)obverser給data中每個(gè)屬性添加一個(gè)主題對(duì)象
遍歷data中的所有屬性,包括子屬性對(duì)象的屬性

     function obverser (obj) {
        Object.keys(obj).forEach(key => {
            if (obj.hasOwnProperty(key)) {
                if (obj[key].constructor === "Object") {
                    obverser(obj[key])
                }
                defineReactive(obj, key);
            }
        })
    }

使用Object.definePeoperty()來(lái)監(jiān)聽屬性變動(dòng),給屬性添加setter和getter

    function defineReactive (obj, key) {
        var _value= obj[key];
        // new一個(gè)主題對(duì)象
        var dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            set (newVal) {
                if (_value= newVal) {
                    return;
                }
                _value= newVal;
                console.log(value)
                // 作為發(fā)布者發(fā)出通知給主題對(duì)象
                dep.notify();
            },
            get () {
                // 如果訂閱者存在,添加到主題對(duì)象中
                if (Dep.target) {
                    dep.addSub(Dep.target);
                }
                return _value
            }
        })
    }

最后,我們需要再次修改構(gòu)造函數(shù)MVue

    function MVue (options) {
        this.$el = options.el;
        this.$data = options.data;

        // 數(shù)據(jù)監(jiān)聽
        obverser(this.$data);

        // 模板編譯
        let elem = document.querySelector(this.$el);
        elem.appendChild(nodeToFragment(elem, this))
    }

現(xiàn)在,已經(jīng)實(shí)現(xiàn)了model => view的變化
當(dāng)輸入框值變化時(shí) => text也會(huì)變化 => 文本節(jié)點(diǎn)值變化

但如果細(xì)心的話,會(huì)發(fā)現(xiàn)還有一個(gè)問題,當(dāng)我們手動(dòng)改變text的值時(shí)(如在控制臺(tái)上輸入vm.$data.text = "xxx"),會(huì)發(fā)現(xiàn),文本節(jié)點(diǎn)值已經(jīng)變化了,但是輸入框的值沒有變化。
如果給輸入框也添加一個(gè)Watcher,是不是也就和文本節(jié)點(diǎn)一樣實(shí)現(xiàn)了呢,但需要注意的是,輸入框、文本框、下拉框等,是通過value改變值的,而不是nodeValuefa,因?yàn)榭梢宰鋈缦滦薷模?br>compile中:

    // 初始化數(shù)據(jù)綁定
    // node.value = vm.$data[name];
    new Watcher(vm, node, name);
    // 移除v-model 屬性
    node.removeAttribute("v-model")

wather中:

    Watcher.prototype = {
        update () {
            this.get();
            let _name;
            if (this.index === 1) {
                _name = this.name;
            } else {
                _name = this.value;
            }
            if (this.node.nodeName === "INPUT") {
                // 可以添加TEXTAREA、SELECT等
                this.node.value = this.value;
            } else {
               // this.node.nodeValue = this.value;
               this.node.nodeValue = this.node.nodeValue.replace(new RegExp("{?{?s*(" + _name + ")s*}?}?"), this.value);
            } 
            ++this.index;
        },
        get () {
            this.value = this.vm.$data[this.name]
        }
    }

OK,基本上完工。

獲取完整代碼,猛戳這里
個(gè)人博客也可以獲取完整代碼(https://jefferye.github.io)

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

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

相關(guān)文章

  • Vue 雙向數(shù)據(jù)綁定原理分析

    摘要:關(guān)于雙向數(shù)據(jù)綁定當(dāng)我們?cè)谇岸碎_發(fā)中采用的模式時(shí),,指的是模型,也就是數(shù)據(jù),,指的是視圖,也就是頁(yè)面展現(xiàn)的部分。參考沉思錄一數(shù)據(jù)綁定雙向數(shù)據(jù)綁定實(shí)現(xiàn)數(shù)據(jù)與視圖的綁定與同步,最終體現(xiàn)在對(duì)數(shù)據(jù)的讀寫處理過程中,也就是定義的數(shù)據(jù)函數(shù)中。 關(guān)于雙向數(shù)據(jù)綁定 當(dāng)我們?cè)谇岸碎_發(fā)中采用MV*的模式時(shí),M - model,指的是模型,也就是數(shù)據(jù),V - view,指的是視圖,也就是頁(yè)面展現(xiàn)的部分。通常,...

    nanfeiyan 評(píng)論0 收藏0
  • Vue面試題精選:Vue原理以及雙向數(shù)據(jù)綁定的實(shí)戰(zhàn)過程

    摘要:雙向數(shù)據(jù)綁定指的是,將對(duì)象屬性變化與視圖的變化相互綁定。數(shù)據(jù)雙向綁定已經(jīng)了解到是通過數(shù)據(jù)劫持的方式來(lái)做數(shù)據(jù)綁定的,其中最核心的方法便是通過來(lái)實(shí)現(xiàn)對(duì)屬性的劫持,達(dá)到監(jiān)聽數(shù)據(jù)變動(dòng)的目的。和允許觀察數(shù)據(jù)的更改并觸發(fā)更新。 1 MVVM 雙向數(shù)據(jù)綁定指的是,將對(duì)象屬性變化與視圖的變化相互綁定。換句話說(shuō),如果有一個(gè)擁有name屬性的user對(duì)象,與元素的內(nèi)容綁定,當(dāng)給user.name賦予一個(gè)新...

    malakashi 評(píng)論0 收藏0
  • Vue原理】VModel - 白話版

    摘要:執(zhí)行的時(shí)候,會(huì)綁定上下文對(duì)象為組件實(shí)例于是中的就能取到組件實(shí)例本身,的代碼塊頂層作用域就綁定為了組件實(shí)例于是內(nèi)部變量的訪問,就會(huì)首先訪問到組件實(shí)例上。其中的獲取,就會(huì)先從組件實(shí)例上獲取,相當(dāng)于。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得...

    keke 評(píng)論0 收藏0
  • Vue雙向綁定原理,教你一步一步實(shí)現(xiàn)雙向綁定

    摘要:儲(chǔ)存訂閱器因?yàn)閷傩员槐O(jiān)聽,這一步會(huì)執(zhí)行監(jiān)聽器里的方法這一步我們把也給弄了出來(lái),到這一步我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的雙向綁定了,我們可以嘗試把兩者結(jié)合起來(lái)看下效果??偨Y(jié)本文主要是對(duì)雙向綁定原理的學(xué)習(xí)與實(shí)現(xiàn)。 當(dāng)今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個(gè)陣營(yíng)基本上無(wú)法立足于前端,甚至是兩個(gè)或者三個(gè)陣營(yíng)都要選擇,大勢(shì)所趨。 所以我們要時(shí)刻保持好奇心,擁抱變化,...

    Labradors 評(píng)論0 收藏0
  • 詳解 vue 雙向數(shù)據(jù)綁定原理,并實(shí)現(xiàn)一組雙向數(shù)據(jù)綁定

    1:vue 雙向數(shù)據(jù)綁定的原理: Object.defineProperty是ES5新增的一個(gè)API,其作用是給對(duì)象的屬性增加更多的控制Object.defineProperty(obj, prop, descriptor)參數(shù) obj: 需要定義屬性的對(duì)象(目標(biāo)對(duì)象)prop: 需被定義或修改的屬性名(對(duì)象上的屬性或者方法)對(duì)于setter和getter,我的理解是它們是一對(duì)勾子(hook...

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

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

0條評(píng)論

閱讀需要支付1元查看
<