Vue雙向綁定實現(xiàn)原理與方法詳解
本文實例講述了Vue雙向綁定實現(xiàn)原理與方法。分享給大家供大家參考,具體如下:
昨天接到一個電話面試,上來第一個問題就是Vue雙向綁定的原理。當時我并不知道如何監(jiān)聽數(shù)據(jù)層到視圖層的變化,于是沒答上來,掛電話后,我趕忙查了下資料,主要思路有如下三種。
1.發(fā)布者-訂閱者模式(backbone.js)
思路:使用自定義的data屬性在HTML代碼中指明綁定。所有綁定起來的JavaScript對象以及DOM元素都將“訂閱”一個發(fā)布者對象。任何時候如果JavaScript對象或者一個HTML輸入字段被偵測到發(fā)生了變化,我們將代理事件到發(fā)布者-訂閱者模式,這會反過來將變化廣播并傳播到所有綁定的對象和元素。
2.臟值檢查(angular.js)
思路:angular.js 是通過臟值檢測的方式比對數(shù)據(jù)是否有變更,來決定是否更新視圖,最簡單的方式就是通過 setInterval() 定時輪詢檢測數(shù)據(jù)變動,angular只有在指定的事件觸發(fā)時進入臟值檢測,大致如下:
- DOM事件,譬如用戶輸入文本,點擊按鈕等。( 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ā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。
Object.defineProperty():方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xià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='111';//控制臺打印set val:111 obj.hello; //控制臺打印get val:111
當獲取hello屬性時,觸發(fā)get;設(shè)置hello值時,觸發(fā)set;這就是vue實現(xiàn)雙向綁定的核心
完整代碼如下
<!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>v-model</title> </head> <body> <div id='app'> <h2>{{title}}</h2> <input id='i' v-model='text' type="text"> <h1>{{text}}</h1> <button v-on:click='clickMe'>click me</button> </div> <script> //Dom類 解析模板指令,將模板中的變量替換成數(shù)據(jù),然后初始化渲染頁面視圖 //并將每個指令對應(yīng)的節(jié)點綁定更新函數(shù),添加監(jiān)聽數(shù)據(jù)的訂閱者,一旦數(shù)據(jù)有變動,收到通知,更新視圖 class Doms { constructor(node, vm) { if (node) { this.$frag = this.nodeToFragment(node, vm) return this.$frag } } nodeToFragment(node, vm) {//將dom轉(zhuǎn)換成fragment var frag = document.createDocumentFragment() var child; while (child = node.firstChild) { this.compileElement(child, vm) frag.appendChild(child) } return frag } compileElement(node, vm) {//獲取v-model屬性,給dom賦值 var reg = /\{\{(.*)\}\}/ //匹配雙括號里面的任何字符 if (node.nodeType === 1) {//element元素 var attr = node.attributes; for (var i = 0; i < attr.length; i++) { if (attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue//獲取綁定的key node.addEventListener('input', function (e) { vm[name] = e.target.value//觸發(fā)set方法 }) new Watcher(vm, node, name, 'value') } else if (attr[i].nodeName.includes(':')) { var eventType = attr[i].nodeName.split(':')[1]//事件名 var cb = vm.methods && vm.methods[attr[i].nodeValue] if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false) } } } if (node.childNodes && node.childNodes.length) {//如果還有子節(jié)點 遞歸 [...node.childNodes].forEach(n => this.compileElement(n, vm)) } } if (node.nodeType === 3) {//text if (reg.test(node.nodeValue)) { var name = RegExp.$1 name = name.trim() new Watcher(vm, node, name, 'nodeValue') } } } } class Vue {//Vue類 constructor(params) { this.data = params.data //獲取屬性 this.methods = params.methods //獲取方法 this.observe(params.data, this)//監(jiān)聽屬性 var id = params.el; var dom = new Doms(document.getElementById(id), this) document.getElementById(id).appendChild(dom) params.mounted.call(this) } observe(obj, vm) {//讀取data內(nèi)屬性,并監(jiān)聽 if (!obj || typeof obj !== 'object') return Object.keys(obj).forEach(key => this.defineReactive(vm, key, obj[key])) } defineReactive(obj, key, val) {//利用Object.defineProperty監(jiān)聽屬性改變 var dep = new Dep() Object.defineProperty(obj, key, { get: function () { if (Dep.target) {//添加訂閱者watcher到主題對象Dep dep.addSub(Dep.target) } return val }, set: function (newVal) { if (newVal === val) return val = newVal console.log(val) //作為發(fā)布者發(fā)布通知 dep.notify() } }) } } class Dep {//收集訂閱者的容器類 constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify() { this.subs.forEach(sub => sub.update()) } } class Watcher { constructor(vm, node, name, type) { Dep.target = this this.name = name this.node = node this.vm = vm this.type = type this.update() Dep.target = null } update() { this.get() this.node[this.type] = this.value//訂閱者執(zhí)行響應(yīng)操作 } get() { this.value = this.vm[this.name]//觸發(fā)響應(yīng)屬性的get } } var vm = new Vue({ el: 'app', data: { text: 'lyl', title: 'hello world' }, methods: { clickMe() { this.title = 'hello world' } }, mounted() { setTimeout(() => { this.title = '你好' }, 1000); } }) </script> </body> </html>
GitHub地址:https://github.com/ChrisLuckComes/Vue2WayBind
感興趣的朋友可以使用在線HTML/CSS/JavaScript代碼運行工具:http://tools.jb51.net/code/HtmlJsRun測試上述代碼運行效果。
更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript錯誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學運算用法總結(jié)》
希望本文所述對大家JavaScript程序設(shè)計有所幫助。
相關(guān)文章
在vs code 中如何創(chuàng)建一個自己的 Vue 模板代碼
這篇文章主要介紹了在vs code 中如何創(chuàng)建一個自己的 Vue 模板代碼,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11ant-design-vue前端UI庫,如何解決Table中時間格式化問題
這篇文章主要介紹了ant-design-vue前端UI庫,如何解決Table中時間格式化問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03Vue使用Multiavatarjs生成自定義隨機頭像的案例
這篇文章給大家介紹了Vue項目中使用Multiavatarjs生成自定義隨機頭像的案例,文中通過代碼示例介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下2023-10-10Vue項目如何根據(jù)圖片url獲取file對象并用axios上傳
這篇文章主要介紹了Vue項目如何根據(jù)圖片url獲取file對象并用axios上傳問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09