vue双向绑定
1.vue雙向綁定原理
vue.js 則是采用數據劫持結合發布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。我們先來看Object.defineProperty()這個方法:
var obj = {}; Object.defineProperty(obj, 'name', {get: function() {console.log('我被獲取了')return val;},set: function (newVal) {console.log('我被設置了')} }) obj.name = 'fei';//在給obj設置name屬性的時候,觸發了set這個方法 var val = obj.name;//在得到obj的name屬性,會觸發get方法已經了解到vue是通過數據劫持的方式來做數據綁定的,其中最核心的方法便是通過Object.defineProperty()來實現對屬性的劫持,那么在設置或者獲取的時候我們就可以在get或者set方法里假如其他的觸發函數,達到監聽數據變動的目的,無疑這個方法是本文中最重要、最基礎的內容之一。
2.實現最簡單的雙向綁定
我們知道通過Object.defineProperty()可以實現數據劫持,是的屬性在賦值的時候觸發set方法,
<!doctype html> <html lang="en"> <head><meta charset="UTF-8"><title>Document</title> </head> <body><div id="demo"></div><input type="text" id="inp"><script>var obj = {};var demo = document.querySelector('#demo')var inp = document.querySelector('#inp')Object.defineProperty(obj, 'name', {get: function() {return val;},set: function (newVal) {//當該屬性被賦值的時候觸發inp.value = newVal;demo.innerHTML = newVal;}})inp.addEventListener('input', function(e) {// 給obj的name屬性賦值,進而觸發該屬性的set方法obj.name = e.target.value;});obj.name = 'fei';//在給obj設置name屬性的時候,觸發了set這個方法</script> </body> </html>當然要是這么粗暴,肯定不行,性能會出很多的問題。
3.講解vue如何實現
先看原理圖
3.1 observer用來實現對每個vue中的data中定義的屬性循環用Object.defineProperty()實現數據劫持,以便利用其中的setter和getter,然后通知訂閱者,訂閱者會觸發它的update方法,對視圖進行更新。
3.2 我們介紹為什么要訂閱者,在vue中v-model,v-name,{{}}等都可以對數據進行顯示,也就是說假如一個屬性都通過這三個指令了,那么每當這個屬性改變的時候,相應的這個三個指令的html視圖也必須改變,于是vue中就是每當有這樣的可能用到雙向綁定的指令,就在一個Dep中增加一個訂閱者,其訂閱者只是更新自己的指令對應的數據,也就是v-model='name'和{{name}}有兩個對應的訂閱者,各自管理自己的地方。每當屬性的set方法觸發,就循環更新Dep中的訂閱者。
4.vue代碼實現
4.1 observer實現,主要是給每個vue的屬性用Object.defineProperty(),代碼如下:
function defineReactive (obj, key, val) {var dep = new Dep();Object.defineProperty(obj, key, {get: function() {//添加訂閱者watcher到主題對象Depif(Dep.target) {// JS的瀏覽器單線程特性,保證這個全局變量在同一時間內,只會有同一個監聽器使用dep.addSub(Dep.target);}return val;},set: function (newVal) {if(newVal === val) return;val = newVal;console.log(val);// 作為發布者發出通知dep.notify();//通知后dep會循環調用各自的update方法更新視圖}}) }function observe(obj, vm) {Object.keys(obj).forEach(function(key) {defineReactive(vm, key, obj[key]);})}4.2實現compile:
compile的目的就是解析各種指令稱真正的html。
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) {console.log([child])self.compileElement(child, vm);frag.append(child); // 將所有子節點添加到fragment中}return frag;},compileElement: function(node, vm) {var reg = /\{\{(.*)\}\}/;//節點類型為元素(input元素這里)if(node.nodeType === 1) {var attr = node.attributes;// 解析屬性for(var i = 0; i < attr.length; i++ ) {if(attr[i].nodeName == 'v-model') {//遍歷屬性節點找到v-model的屬性var name = attr[i].nodeValue; // 獲取v-model綁定的屬性名node.addEventListener('input', function(e) {// 給相應的data屬性賦值,進而觸發該屬性的set方法vm[name]= e.target.value;});new Watcher(vm, node, name, 'value');//創建新的watcher,會觸發函數向對應屬性的dep數組中添加訂閱者,}};}//節點類型為textif(node.nodeType === 3) {if(reg.test(node.nodeValue)) {var name = RegExp.$1; // 獲取匹配到的字符串name = name.trim();new Watcher(vm, node, name, 'nodeValue');}}} }4.3 watcher實現
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; // 訂閱者執行相應操作},// 獲取data的屬性值get: function() {console.log(1)this.value = this.vm[this.name]; //觸發相應屬性的get} }4.4 實現Dep來為每個屬性添加訂閱者
function Dep() {this.subs = []; } Dep.prototype = {addSub: function(sub) {this.subs.push(sub);},notify: function() {this.subs.forEach(function(sub) {sub.update();})} }這樣一來整個數據的雙向綁定就完成了。
5.梳理
首先我們為每個vue屬性用Object.defineProperty()實現數據劫持,為每個屬性分配一個訂閱者集合的管理數組dep;然后在編譯的時候在該屬性的數組dep中添加訂閱者,v-model會添加一個訂閱者,{{}}也會,v-bind也會,只要用到該屬性的指令理論上都會,接著為input會添加監聽事件,修改值就會為該屬性賦值,觸發該屬性的set方法,在set方法內通知訂閱者數組dep,訂閱者數組循環調用各訂閱者的update方法更新視圖。
總結
- 上一篇: python从入门到实践电子版-Pyth
- 下一篇: 第一行代码android网课,使用Mon