摘要:什么是雙向數(shù)據(jù)綁定是一個框架,數(shù)據(jù)綁定簡單來說,就是當數(shù)據(jù)發(fā)生變化時,相應的視圖會進行更新,當視圖更新時,數(shù)據(jù)也會跟著變化。
什么是雙向數(shù)據(jù)綁定?Vue是一個MVVM框架,數(shù)據(jù)綁定簡單來說,就是當數(shù)據(jù)發(fā)生變化時,相應的視圖會進行更新,當視圖更新時,數(shù)據(jù)也會跟著變化。
實現(xiàn)數(shù)據(jù)綁定的方式大致有以下幾種:
- 1、發(fā)布者-訂閱者模式(backbone.js) - 2、臟值檢查(angular.js) - 3、數(shù)據(jù)劫持(vue.js)發(fā)布者-訂閱者模式
一般通過sub, pub的方式實現(xiàn)數(shù)據(jù)和視圖的綁定監(jiān)聽,更新數(shù)據(jù)方式通常做法是 vm.set("property", value),有興趣可參考這里
我們更希望可以通過 vm.property = value 這種方式進行數(shù)據(jù)更新,同時自動更新視圖。臟值檢查
angular是通過臟值檢查方式來對比數(shù)據(jù)是否變化,來決定是否更新視圖,最常見的方式是通過setInterval()來監(jiān)測數(shù)據(jù)變化,當然,只會在某些指定事件觸發(fā)時下才進行臟值檢查。大致如下:
- DOM事件,譬如用戶輸入文本,點擊按鈕等。( ng-click ) - XHR響應事件 ( $http ) - 瀏覽器Location變更事件 ( $location ) - Timer事件( $timeout , $interval ) - 執(zhí)行 $digest() 或 $apply()數(shù)據(jù)劫持
Vue.js則是通過數(shù)據(jù)劫持以及結合發(fā)布者-訂閱者來實現(xiàn)的,數(shù)據(jù)劫持是利用ES5的Object.defineProperty(obj, key, val)來劫持各個屬性的的setter以及getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,從而觸發(fā)相應的回調(diào)來更新視圖。
一、實現(xiàn)最基礎的數(shù)據(jù)綁定輸入的值為:二、雙向數(shù)據(jù)綁定實現(xiàn)(此處用MVue替代)
上面的只是簡單的使用了Object.defineProperty(),并不是我們最終想要的效果,最終想要的效果如下:
輸入的值為:{{text}}
實現(xiàn)思路:
1、輸入框以及文本節(jié)點和data中的數(shù)據(jù)進行綁定
2、輸入框內(nèi)容變化時,data中的對應數(shù)據(jù)同步變化,即 view => model
3、data中數(shù)據(jù)變化時,對應的文本節(jié)點內(nèi)容同步變化 即 model => view
上述流程如圖所示:
1、實現(xiàn)一個數(shù)據(jù)監(jiān)聽器Obverser,對data中的數(shù)據(jù)進行監(jiān)聽,若有變化,通知相應的訂閱者。
2、實現(xiàn)一個指令解析器Compile,對于每個元素上的指令進行解析,根據(jù)指令替換數(shù)據(jù),更新視圖。
3、實現(xiàn)一個Watcher,用來連接Obverser和Compile, 并為每個屬性綁定相應的訂閱者,當數(shù)據(jù)發(fā)生變化時,執(zhí)行相應的回調(diào)函數(shù),從而更新視圖。
4、構造函數(shù) (new MVue({}))
在初始化MVue實例時,對data中每個屬性劫持監(jiān)聽,同時進行模板編譯,指令解析,最后掛載到相應的DOM中。
function MVue (options) { this.$el = options.el; this.$data = options.data; // 初始化操作,后面會說 // ... }
1、實現(xiàn) view => model
DocumentFragment(文檔片段)vue進行編譯時,將掛載目標的所有子節(jié)點劫持到DocumentFragment中,經(jīng)過一份解析等處理后,再將DocumentFragment整體掛載到目標節(jié)點上。
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é)點 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é)點 if (node.nodeType === 3) { if (reg.test(node.nodeValue)) { var name = RegExp.$1 && (RegExp.$1.trim()); // 綁定數(shù)據(jù)到文本節(jié)點中 node.nodeValue = node.nodeValue.replace(new RegExp("{{s*(" + name + ")s*}}"), vm.$data[name]); } } }
現(xiàn)在,我們修改下MVue構造函數(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)實現(xiàn)了,包括初始化綁定默認值,只要修改了input中的值,data中對應的值相應變化,并觸發(fā)了setter, 更新屬性值等(可以自行在set方法中打印看效果,或者在控制臺手動輸入vm.$data.text也會看到效果)。
2、實現(xiàn) model => view
上面可以看出,雖然我們實現(xiàn)了初始化數(shù)據(jù)綁定,以及輸入框變化時,data中text也會變化,但是文本節(jié)點仍然沒有任何變化,那么如果做到文本節(jié)點也同步變化呢,這里用的是發(fā)布者-訂閱者模式。
發(fā)布者-訂閱者模式又稱為觀察者模式,讓多個觀察者同時監(jiān)聽某個主題對象,當主題對象發(fā)生變化時,會通知所有的觀察者對象,即:發(fā)布者發(fā)出通知給主題對象 => 主題對象接收到通知后推送給所有訂閱者 => 訂閱者執(zhí)行相應的操作。
1)首先,定義一個主題對象,用來收集所有的訂閱者,并提供notify方法,用來調(diào)用訂閱者的update方法,從而執(zhí)行相應的操作。
function Dep () { this.subs = []; } Dep.prototype = { addSub (sub) { this.subs.push(sub); }, notify () { this.subs.forEach(sub => { // 執(zhí)行訂閱者的update方法 sub.update(); }) } }
不難看出,當text屬性變化時,會觸發(fā)set方法,作為發(fā)布者,將數(shù)據(jù)更新消息通過主題對象發(fā)送給訂閱者, 那么該如何通知呢?
我們知道,在new一個vue時,會執(zhí)行兩個操作,一個事編譯模板,一個監(jiān)聽data數(shù)據(jù),在監(jiān)聽data時,vue為data的每個屬性都生成一個主題對象Dep,而在編譯模板時,會為每個與數(shù)據(jù)綁定的節(jié)點生成一個Watcher,那么只要關聯(lián)了Dep與Watcher,是不是就實現(xiàn)了消息通知呢,關鍵邏輯是實現(xiàn)二者關聯(lián)。
已實現(xiàn):輸入框變化 => 觸發(fā)相應的事件,修改值 => 觸發(fā)set方法
需要實現(xiàn):發(fā)出通知dep.notify() => 觸發(fā)訂閱者update方法 => 更新視圖
我們修改下compile中文本節(jié)點內(nèi)容(只修改部分)
// 文本節(jié)點 if (node.nodeType === 3) { if (reg.test(node.nodeValue)) { var name = RegExp.$1 && (RegExp.$1.trim()); // 綁定數(shù)據(jù)到文本節(jié)點中 // node.nodeValue = node.nodeValue.replace(new RegExp("{{s*(" + name + ")s*}}"), vm.$data[name]); new Watcher(vm, node, name); } }
2)其次、實現(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] } }
首先,定義了一個全局的Dep.target,然后執(zhí)行了update方法,進而執(zhí)行了get方法,都去了this.vm的訪問器屬性, 從而將訂閱的消息保存在該屬性的主題對象中,并最終將Dep.target設置為空,全局變量,是watcher和dep之間的唯一橋梁,必須保證Dep.target只有一個值。
3)接著、實現(xiàn)一個obverser給data中每個屬性添加一個主題對象
遍歷data中的所有屬性,包括子屬性對象的屬性
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()來監(jiān)聽屬性變動,給屬性添加setter和getter
function defineReactive (obj, key) { var _value= obj[key]; // new一個主題對象 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ā)出通知給主題對象 dep.notify(); }, get () { // 如果訂閱者存在,添加到主題對象中 if (Dep.target) { dep.addSub(Dep.target); } return _value } }) }
最后,我們需要再次修改構造函數(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)實現(xiàn)了model => view的變化
當輸入框值變化時 => text也會變化 => 文本節(jié)點值變化
但如果細心的話,會發(fā)現(xiàn)還有一個問題,當我們手動改變text的值時(如在控制臺上輸入vm.$data.text = "xxx"),會發(fā)現(xiàn),文本節(jié)點值已經(jīng)變化了,但是輸入框的值沒有變化。
如果給輸入框也添加一個Watcher,是不是也就和文本節(jié)點一樣實現(xiàn)了呢,但需要注意的是,輸入框、文本框、下拉框等,是通過value改變值的,而不是nodeValuefa,因為可以做如下修改:
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,基本上完工。
獲取完整代碼,猛戳這里
個人博客也可以獲取完整代碼(https://jefferye.github.io)
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94618.html
摘要:關于雙向數(shù)據(jù)綁定當我們在前端開發(fā)中采用的模式時,,指的是模型,也就是數(shù)據(jù),,指的是視圖,也就是頁面展現(xiàn)的部分。參考沉思錄一數(shù)據(jù)綁定雙向數(shù)據(jù)綁定實現(xiàn)數(shù)據(jù)與視圖的綁定與同步,最終體現(xiàn)在對數(shù)據(jù)的讀寫處理過程中,也就是定義的數(shù)據(jù)函數(shù)中。 關于雙向數(shù)據(jù)綁定 當我們在前端開發(fā)中采用MV*的模式時,M - model,指的是模型,也就是數(shù)據(jù),V - view,指的是視圖,也就是頁面展現(xiàn)的部分。通常,...
摘要:雙向數(shù)據(jù)綁定指的是,將對象屬性變化與視圖的變化相互綁定。數(shù)據(jù)雙向綁定已經(jīng)了解到是通過數(shù)據(jù)劫持的方式來做數(shù)據(jù)綁定的,其中最核心的方法便是通過來實現(xiàn)對屬性的劫持,達到監(jiān)聽數(shù)據(jù)變動的目的。和允許觀察數(shù)據(jù)的更改并觸發(fā)更新。 1 MVVM 雙向數(shù)據(jù)綁定指的是,將對象屬性變化與視圖的變化相互綁定。換句話說,如果有一個擁有name屬性的user對象,與元素的內(nèi)容綁定,當給user.name賦予一個新...
摘要:執(zhí)行的時候,會綁定上下文對象為組件實例于是中的就能取到組件實例本身,的代碼塊頂層作用域就綁定為了組件實例于是內(nèi)部變量的訪問,就會首先訪問到組件實例上。其中的獲取,就會先從組件實例上獲取,相當于。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得...
摘要:儲存訂閱器因為屬性被監(jiān)聽,這一步會執(zhí)行監(jiān)聽器里的方法這一步我們把也給弄了出來,到這一步我們已經(jīng)實現(xiàn)了一個簡單的雙向綁定了,我們可以嘗試把兩者結合起來看下效果。總結本文主要是對雙向綁定原理的學習與實現(xiàn)。 當今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足于前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。 所以我們要時刻保持好奇心,擁抱變化,...
1:vue 雙向數(shù)據(jù)綁定的原理: Object.defineProperty是ES5新增的一個API,其作用是給對象的屬性增加更多的控制Object.defineProperty(obj, prop, descriptor)參數(shù) obj: 需要定義屬性的對象(目標對象)prop: 需被定義或修改的屬性名(對象上的屬性或者方法)對于setter和getter,我的理解是它們是一對勾子(hook...
閱讀 1407·2021-11-24 10:20
閱讀 3649·2021-11-24 09:38
閱讀 2294·2021-09-27 13:37
閱讀 2196·2021-09-22 15:25
閱讀 2270·2021-09-01 18:33
閱讀 3488·2019-08-30 15:55
閱讀 1783·2019-08-30 15:54
閱讀 2081·2019-08-30 12:50