今天小編給大家分享一下Vue2.0如何實(shí)現(xiàn)雙向綁定的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
公司主營(yíng)業(yè)務(wù):網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。創(chuàng)新互聯(lián)公司推出甘南免費(fèi)做網(wǎng)站回饋大家。
一、實(shí)現(xiàn)雙向綁定的做法
前端MVVM最令人激動(dòng)的就是雙向綁定機(jī)制了,實(shí)現(xiàn)雙向數(shù)據(jù)綁定的做法大致有如下三種:
1.發(fā)布者-訂閱者模式(backbone.js)
思路:使用自定義的data屬性在HTML代碼中指明綁定。所有綁定起來(lái)的JavaScript對(duì)象以及DOM元素都將“訂閱”一個(gè)發(fā)布者對(duì)象。任何時(shí)候如果JavaScript對(duì)象或者一個(gè)HTML輸入字段被偵測(cè)到發(fā)生了變化,我們將代理事件到發(fā)布者-訂閱者模式,這會(huì)反過(guò)來(lái)將變化廣播并傳播到所有綁定的對(duì)象和元素。
2.臟值檢查(angular.js)
思路:angular.js 是通過(guò)臟值檢測(cè)的方式比對(duì)數(shù)據(jù)是否有變更,來(lái)決定是否更新視圖,最簡(jiǎn)單的方式就是通過(guò) setInterval() 定時(shí)輪詢檢測(cè)數(shù)據(jù)變動(dòng),angular只有在指定的事件觸發(fā)時(shí)進(jìn)入臟值檢測(cè),大致如下:
DOM事件,譬如用戶輸入文本,點(diǎn)擊按鈕等。( ng-click )
XHR響應(yīng)事件 ( $http )
瀏覽器Location變更事件 ( $location )
Timer事件( $timeout , $interval )
執(zhí)行 $digest() 或 $apply()
3.數(shù)據(jù)劫持(Vue.js)
思路: vue.js 則是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過(guò)Object.defineProperty()來(lái)劫持各個(gè)屬性的setter,getter,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽(tīng)回調(diào)。
由此可見(jiàn),Object.defineProperty() 這個(gè)API是Vue實(shí)現(xiàn)雙向數(shù)據(jù)綁定的關(guān)鍵,我們先簡(jiǎn)單了解下這個(gè)API,了解更多戳這里
二、Object.defineProperty()
簡(jiǎn)單例子:
var obj = {}; Object.defineProperty(obj, 'hello', { get: function() { console.log('get val:'+ val); return val; }, set: function(newVal) { val = newVal; console.log('set val:'+ val); } }); obj.hello; obj.hello='111';
如果去掉 obj.hello=‘111' 這行代碼,則get的返回值val會(huì)報(bào)錯(cuò)val is not defined??梢?jiàn)Object.defineProperty() 監(jiān)控對(duì)數(shù)據(jù)的操作,可以自動(dòng)觸發(fā)數(shù)據(jù)同步。下面我們先用Object.defineProperty()來(lái)實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的雙向綁定。
三、實(shí)現(xiàn)簡(jiǎn)單的雙向綁定
最簡(jiǎn)單例子:
<!DOCTYPE html> <head></head> <body> <div id="app"> <input type="text" id="a"> <span id="b"></span> </div> <script type="text/javascript"> var obj = {}; Object.defineProperty(obj, 'hello', { get: function() { console.log('get val:'+ val); return val; }, set: function(newVal) { val = newVal; console.log('set val:'+ val); document.getElementById('a').value = val; document.getElementById('b').innerHTML = val; } }); document.addEventListener('keyup', function(e) { obj.hello = e.target.value; }); </script> </body> </html>
實(shí)現(xiàn)效果如下:
上面例子直接用了dom操作改變了文本節(jié)點(diǎn)的值,而且是在我們知道是哪個(gè)id的情況下,通過(guò)document.getElementById 獲取到相應(yīng)的文本節(jié)點(diǎn),然后直接修改文本節(jié)點(diǎn)的值,這種做法是最簡(jiǎn)單粗暴的。
封裝成一個(gè)框架,肯定不能是這種做法,所以我們需要一個(gè)解析dom,并能修改dom中相應(yīng)的變量的模塊。
四、實(shí)現(xiàn)簡(jiǎn)單Compile
首先我們需要獲取文本中真實(shí)的dom節(jié)點(diǎn),然后再分析節(jié)點(diǎn)的類(lèi)型,根據(jù)節(jié)點(diǎn)類(lèi)型做相應(yīng)的處理。
在上面例子我們多次操作了dom節(jié)點(diǎn),為提高性能和效率,會(huì)先將所有的節(jié)點(diǎn)轉(zhuǎn)換城文檔碎片fragment進(jìn)行編譯操作,解析操作完成后,再將fragment添加到原來(lái)的真實(shí)dom節(jié)點(diǎn)中。
<!DOCTYPE html> <head></head> <body> <div id="app"> <input type="text" id="a" v-model="text"> {{text}} </div> <script type="text/javascript"> function Compile(node, vm) { if(node) {this.$frag = this.nodeToFragment(node, vm); return this.$frag; } } Compile.prototype = { nodeToFragment: function(node, vm) { var self = this; var frag = document.createDocumentFragment(); var child; while(child = node.firstChild) { self.compileElement(child, vm); frag.append(child); // 將所有子節(jié)點(diǎn)添加到fragment中,child是指向元素首個(gè)子節(jié)點(diǎn)的引用。將child引用指向的對(duì)象append到父對(duì)象的末尾,原來(lái)child引用的對(duì)象就跳到了frag對(duì)象的末尾,而child就指向了本來(lái)是排在第二個(gè)的元素對(duì)象。如此循環(huán)下去,鏈接就逐個(gè)往后跳了 } return frag; }, compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; //節(jié)點(diǎn)類(lèi)型為元素 if(node.nodeType === 1) { var attr = node.attributes; // 解析屬性 for(var i = 0; i < attr.length; i++ ) { if(attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue; // 獲取v-model綁定的屬性名 node.addEventListener('input', function(e) { // 給相應(yīng)的data屬性賦值,進(jìn)而觸發(fā)該屬性的set方法 vm.data[name]= e.target.value; }); node.value = vm.data[name]; // 將data的值賦給該node node.removeAttribute('v-model'); } }; } //節(jié)點(diǎn)類(lèi)型為textif(node.nodeType === 3) { if(reg.test(node.nodeValue)) { var name = RegExp.$1; // 獲取匹配到的字符串 name = name.trim(); node.nodeValue = vm.data[name]; // 將data的值賦給該node } } }, } function Vue(options) { this.data = options.data; var data = this.data; var id = options.el; var dom =new Compile(document.getElementById(id),this); // 編譯完成后,將dom返回到app中 document.getElementById(id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { text: 'hello world' } }); </script> </body> </html>
到這,我們做到了獲取文本中真實(shí)的dom節(jié)點(diǎn),然后分析節(jié)點(diǎn)的類(lèi)型,并能處理節(jié)點(diǎn)中相應(yīng)的變量如上面代碼中的{{text}},最后渲染到頁(yè)面中。接著我們需要和雙向綁定聯(lián)系起來(lái),實(shí)現(xiàn){{text}}響應(yīng)式的數(shù)據(jù)綁定。
五、實(shí)現(xiàn)簡(jiǎn)單observe
簡(jiǎn)單的observe定義如下:
需要監(jiān)控data的屬性值,這個(gè)對(duì)象的某個(gè)值賦值,就會(huì)觸發(fā)setter,這樣就能監(jiān)聽(tīng)到數(shù)據(jù)變化。然后注意vm.data[name]屬性將改為vm[name]
完整代碼如下:
<!DOCTYPE html> <head></head> <body> <div id="app"> <input type="text" id="a" v-model="text"> {{text}} </div> <script type="text/javascript"> function Compile(node, vm) { if(node) { this.$frag = this.nodeToFragment(node, vm); return this.$frag; } } Compile.prototype = { nodeToFragment: function(node, vm) { var self = this; var frag = document.createDocumentFragment(); var child; while(child = node.firstChild) { self.compileElement(child, vm); frag.append(child); // 將所有子節(jié)點(diǎn)添加到fragment中 } return frag; }, compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; //節(jié)點(diǎn)類(lèi)型為元素 if(node.nodeType === 1) { var attr = node.attributes; // 解析屬性 for(var i = 0; i < attr.length; i++ ) { if(attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue; // 獲取v-model綁定的屬性名 node.addEventListener('input', function(e) { // 給相應(yīng)的data屬性賦值,進(jìn)而觸發(fā)該屬性的set方法 vm[name]= e.target.value; }); node.value = vm[name]; // 將data的值賦給該node node.removeAttribute('v-model'); } }; } //節(jié)點(diǎn)類(lèi)型為text 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); } } }, } function defineReactive (obj, key, val) { Object.defineProperty(obj, key, { get: function() { return val; }, set: function (newVal) { if(newVal === val) return; val = newVal; console.log(val); } }) } function observe(obj, vm) { Object.keys(obj).forEach(function(key) { defineReactive(vm, key, obj[key]); }) } function Vue(options) { this.data = options.data; var data = this.data; observe(data, this); var id = options.el; var dom =new Compile(document.getElementById(id),this); // 編譯完成后,將dom返回到app中 document.getElementById(id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { text: 'hello world' } }); </script> </body> </html>
結(jié)果:
到這,雖然set方法觸發(fā)了,但是文本節(jié)點(diǎn){{text}}的內(nèi)容沒(méi)有變化,要讓綁定的文本節(jié)點(diǎn)同步變化,我們需要引入訂閱發(fā)布模式。
六、訂閱發(fā)布模式
訂閱發(fā)布模式(又稱觀察者模式)定義了一種一對(duì)多的關(guān)系,讓多個(gè)觀察者同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象,這個(gè)主題對(duì)象的狀態(tài)發(fā)生改變時(shí)就會(huì)通知所有觀察者對(duì)象。
發(fā)布者發(fā)出通知 => 主題對(duì)象收到通知并推送給訂閱者 => 訂閱者執(zhí)行相應(yīng)操作
首先我們要一個(gè)收集訂閱者的容器,定義一個(gè)Dep作為主題對(duì)象
然后定義訂閱者Watcher
添加訂閱者Watcher到主題對(duì)象Dep,發(fā)布者發(fā)出通知放到屬性監(jiān)聽(tīng)里面
最后需要訂閱的地方
至此,比較簡(jiǎn)單地實(shí)現(xiàn)了我們第三步用dom操作實(shí)現(xiàn)的雙向綁定效果,代碼:
<!DOCTYPE html> <head></head> <body> <div id="app"> <input type="text" id="a" v-model="text"> {{text}} </div> <script type="text/javascript"> function Compile(node, vm) { if(node) { this.$frag = this.nodeToFragment(node, vm); return this.$frag; } } Compile.prototype = { nodeToFragment: function(node, vm) { var self = this; var frag = document.createDocumentFragment(); var child; while(child = node.firstChild) { self.compileElement(child, vm); frag.append(child); // 將所有子節(jié)點(diǎn)添加到fragment中 } return frag; }, compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; //節(jié)點(diǎn)類(lèi)型為元素 if(node.nodeType === 1) { var attr = node.attributes; // 解析屬性 for(var i = 0; i < attr.length; i++ ) { if(attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue; // 獲取v-model綁定的屬性名 node.addEventListener('input', function(e) { // 給相應(yīng)的data屬性賦值,進(jìn)而觸發(fā)該屬性的set方法 vm[name]= e.target.value; }); // node.value = vm[name]; // 將data的值賦給該node new Watcher(vm, node, name, 'value'); } }; } //節(jié)點(diǎn)類(lèi)型為text 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, 'nodeValue'); } } }, } function Dep() { this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }) } } function Watcher(vm, node, name, type) { Dep.target = this; this.name = name; this.node = node; this.vm = vm; this.type = type; this.update(); Dep.target = null; } Watcher.prototype = { update: function() { this.get(); this.node[this.type] = this.value; // 訂閱者執(zhí)行相應(yīng)操作 }, // 獲取data的屬性值 get: function() { this.value = this.vm[this.name]; //觸發(fā)相應(yīng)屬性的get } } function defineReactive (obj, key, val) { var dep = new Dep(); Object.defineProperty(obj, key, { get: function() { //添加訂閱者watcher到主題對(duì)象Dep if(Dep.target) { // JS的瀏覽器單線程特性,保證這個(gè)全局變量在同一時(shí)間內(nèi),只會(huì)有同一個(gè)監(jiān)聽(tīng)器使用 dep.addSub(Dep.target); } return val; }, set: function (newVal) { if(newVal === val) return; val = newVal; console.log(val); // 作為發(fā)布者發(fā)出通知 dep.notify(); } }) } function observe(obj, vm) { Object.keys(obj).forEach(function(key) { defineReactive(vm, key, obj[key]); }) } function Vue(options) { this.data = options.data; var data = this.data; observe(data, this); var id = options.el; var dom =new Compile(document.getElementById(id),this); // 編譯完成后,將dom返回到app中 document.getElementById(id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { text: 'hello world' } }); </script> </body> </html>
七、總結(jié)
關(guān)于雙向綁定的實(shí)現(xiàn),看了網(wǎng)上很多資料,開(kāi)始看到是對(duì)Vue源碼的解析,看的過(guò)程似懂非懂。后來(lái)找到參考資料1,然后自己跟著實(shí)現(xiàn)一遍,才理解許多。感謝這篇文章的作者,寫(xiě)的由淺入深,比較好理解。為了加深自己的理解,于是自己順著這個(gè)思路寫(xiě)下這個(gè)筆記。本文主要了解了幾種雙向綁定的做法,然后先用原生JS,dom操作實(shí)現(xiàn)一個(gè)最簡(jiǎn)單雙向綁定,在這個(gè)基礎(chǔ)上進(jìn)行改裝,為減少dom操作,實(shí)現(xiàn)簡(jiǎn)單的Compile(編譯HTML);接著為了實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽(tīng),實(shí)現(xiàn)observe;最后為了實(shí)現(xiàn)數(shù)據(jù)的雙向綁定實(shí)現(xiàn)訂閱發(fā)布模式。
雖然實(shí)現(xiàn)的比較簡(jiǎn)單,有很多功能沒(méi)有考慮,不過(guò)這個(gè)過(guò)程還是可以理解到Vue實(shí)現(xiàn)雙向綁定的原理。過(guò)程中,有思考:
1. Vue的源代碼中,用了文檔碎片fragment作為真實(shí)節(jié)點(diǎn)的存儲(chǔ)嗎?
之前有聽(tīng)說(shuō)用VDOM,在Vue源代碼中,也找過(guò)是否有創(chuàng)建文檔碎片,結(jié)果沒(méi)找到。看了參考資料4中,VDOM的介紹,好像是把節(jié)點(diǎn)用JS對(duì)象模擬。類(lèi)似:
模板
<ul id='list'> <li class='item'>Item 1</li> <li class='item'>Item 2</li> <li class='item'>Item 3</li> </ul>
js對(duì)象
var element = { tagName: 'ul', // 節(jié)點(diǎn)標(biāo)簽名 props: { // DOM的屬性,用一個(gè)對(duì)象存儲(chǔ)鍵值對(duì) id: 'list' }, children: [ // 該節(jié)點(diǎn)的子節(jié)點(diǎn) {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]}, {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]}, {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]}, ] }
以上就是“Vue2.0如何實(shí)現(xiàn)雙向綁定”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
標(biāo)題名稱:Vue2.0如何實(shí)現(xiàn)雙向綁定
標(biāo)題路徑:http://bm7419.com/article2/pcisic.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈、App設(shè)計(jì)、企業(yè)網(wǎng)站制作、電子商務(wù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)