亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

解析Vue2.0雙向綁定實現原理

2019-11-19 17:26:52
字體:
來源:轉載
供稿:網友

一、實現雙向綁定的做法

前端MVVM最令人激動的就是雙向綁定機制了,實現雙向數據綁定的做法大致有如下三種:

1.發布者-訂閱者模式(backbone.js)

思路:使用自定義的data屬性在HTML代碼中指明綁定。所有綁定起來的JavaScript對象以及DOM元素都將“訂閱”一個發布者對象。任何時候如果JavaScript對象或者一個HTML輸入字段被偵測到發生了變化,我們將代理事件到發布者-訂閱者模式,這會反過來將變化廣播并傳播到所有綁定的對象和元素。

2.臟值檢查(angular.js)

思路:angular.js 是通過臟值檢測的方式比對數據是否有變更,來決定是否更新視圖,最簡單的方式就是通過 setInterval() 定時輪詢檢測數據變動,angular只有在指定的事件觸發時進入臟值檢測,大致如下:

  • DOM事件,譬如用戶輸入文本,點擊按鈕等。( ng-click )
  • XHR響應事件 ( $http )
  • 瀏覽器Location變更事件 ( $location )
  • Timer事件( $timeout , $interval )
  • 執行 $digest() 或 $apply()

3.數據劫持(Vue.js)

思路: vue.js 則是采用數據劫持結合發布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。

由此可見,Object.defineProperty() 這個API是Vue實現雙向數據綁定的關鍵,我們先簡單了解下這個API,了解更多戳這里

二、Object.defineProperty()

簡單例子:

  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會報錯val is not defined??梢奜bject.defineProperty() 監控對數據的操作,可以自動觸發數據同步。下面我們先用Object.defineProperty()來實現一個非常簡單的雙向綁定。

三、實現簡單的雙向綁定

 最簡單例子:

<!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>

實現效果如下:

上面例子直接用了dom操作改變了文本節點的值,而且是在我們知道是哪個id的情況下,通過document.getElementById 獲取到相應的文本節點,然后直接修改文本節點的值,這種做法是最簡單粗暴的。

封裝成一個框架,肯定不能是這種做法,所以我們需要一個解析dom,并能修改dom中相應的變量的模塊。

四、實現簡單Compile

首先我們需要獲取文本中真實的dom節點,然后再分析節點的類型,根據節點類型做相應的處理。

在上面例子我們多次操作了dom節點,為提高性能和效率,會先將所有的節點轉換城文檔碎片fragment進行編譯操作,解析操作完成后,再將fragment添加到原來的真實dom節點中。

<!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); // 將所有子節點添加到fragment中,child是指向元素首個子節點的引用。將child引用指向的對象append到父對象的末尾,原來child引用的對象就跳到了frag對象的末尾,而child就指向了本來是排在第二個的元素對象。如此循環下去,鏈接就逐個往后跳了    }    return frag;   },   compileElement: function(node, vm) {    var reg = //{/{(.*)/}/}/;    //節點類型為元素    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) {        // 給相應的data屬性賦值,進而觸發該屬性的set方法         vm.data[name]= e.target.value;       });       node.value = vm.data[name]; // 將data的值賦給該node       node.removeAttribute('v-model');      }     };    }    //節點類型為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>

結果:

到這,我們做到了獲取文本中真實的dom節點,然后分析節點的類型,并能處理節點中相應的變量如上面代碼中的{{text}},最后渲染到頁面中。接著我們需要和雙向綁定聯系起來,實現{{text}}響應式的數據綁定。

五、實現簡單observe

簡單的observe定義如下:

需要監控data的屬性值,這個對象的某個值賦值,就會觸發setter,這樣就能監聽到數據變化。然后注意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); // 將所有子節點添加到fragment中    }    return frag;   },   compileElement: function(node, vm) {    var reg = //{/{(.*)/}/}/;    //節點類型為元素    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) {        // 給相應的data屬性賦值,進而觸發該屬性的set方法         vm[name]= e.target.value;       });       node.value = vm[name]; // 將data的值賦給該node       node.removeAttribute('v-model');      }     };    }    //節點類型為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>

結果:

到這,雖然set方法觸發了,但是文本節點{{text}}的內容沒有變化,要讓綁定的文本節點同步變化,我們需要引入訂閱發布模式。

六、訂閱發布模式

訂閱發布模式(又稱觀察者模式)定義了一種一對多的關系,讓多個觀察者同時監聽某一個主題對象,這個主題對象的狀態發生改變時就會通知所有觀察者對象。

發布者發出通知 => 主題對象收到通知并推送給訂閱者 => 訂閱者執行相應操作

首先我們要一個收集訂閱者的容器,定義一個Dep作為主題對象

然后定義訂閱者Watcher

添加訂閱者Watcher到主題對象Dep,發布者發出通知放到屬性監聽里面

最后需要訂閱的地方

至此,比較簡單地實現了我們第三步用dom操作實現的雙向綁定效果,代碼:

<!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); // 將所有子節點添加到fragment中    }    return frag;   },   compileElement: function(node, vm) {    var reg = //{/{(.*)/}/}/;    //節點類型為元素    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) {        // 給相應的data屬性賦值,進而觸發該屬性的set方法         vm[name]= e.target.value;       });       // node.value = vm[name]; // 將data的值賦給該node       new Watcher(vm, node, name, 'value');      }     };    }    //節點類型為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; // 訂閱者執行相應操作   },   // 獲取data的屬性值   get: function() {    this.value = this.vm[this.name]; //觸發相應屬性的get   }  }  function defineReactive (obj, key, val) {   var dep = new Dep();   Object.defineProperty(obj, key, {    get: function() {      //添加訂閱者watcher到主題對象Dep     if(Dep.target) {      // JS的瀏覽器單線程特性,保證這個全局變量在同一時間內,只會有同一個監聽器使用      dep.addSub(Dep.target);     }     return val;    },    set: function (newVal) {     if(newVal === val) return;     val = newVal;     console.log(val);     // 作為發布者發出通知     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>

七、總結

關于雙向綁定的實現,看了網上很多資料,開始看到是對Vue源碼的解析,看的過程似懂非懂。后來找到參考資料1,然后自己跟著實現一遍,才理解許多。感謝這篇文章的作者,寫的由淺入深,比較好理解。為了加深自己的理解,于是自己順著這個思路寫下這個筆記。本文主要了解了幾種雙向綁定的做法,然后先用原生JS,dom操作實現一個最簡單雙向綁定,在這個基礎上進行改裝,為減少dom操作,實現簡單的Compile(編譯HTML);接著為了實現數據監聽,實現observe;最后為了實現數據的雙向綁定實現訂閱發布模式。

雖然實現的比較簡單,有很多功能沒有考慮,不過這個過程還是可以理解到Vue實現雙向綁定的原理。過程中,有思考:

1. Vue的源代碼中,用了文檔碎片fragment作為真實節點的存儲嗎?

之前有聽說用VDOM,在Vue源代碼中,也找過是否有創建文檔碎片,結果沒找到??戳藚⒖假Y料4中,VDOM的介紹,好像是把節點用JS對象模擬。類似:

模板

<ul id='list'> <li class='item'>Item 1</li> <li class='item'>Item 2</li> <li class='item'>Item 3</li></ul>

js對象

var element = { tagName: 'ul', // 節點標簽名 props: { // DOM的屬性,用一個對象存儲鍵值對  id: 'list' }, children: [ // 該節點的子節點  {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},  {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},  {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]}, ]}

恩,這就又牽扯出模板了。先收住,我先盡量把簡單的搞懂。

2.Compile模塊對v-model節點的解析,事件的綁定,我只實現簡單的,特定的v-model,還有其它事件綁定如v-on等沒有分析,看了別人的代碼,情況一多起來,看得就有些吃力,希望后面自己會再來完善,給自己定一個這樣的框架在這.

代碼:戳這里

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人国产va精品日本一级| 欧美另类暴力丝袜| 4438全国亚洲精品在线观看视频| 欧美猛男性生活免费| 欧洲成人午夜免费大片| 亚洲人成在线观看| 成人网在线视频| 亚洲午夜色婷婷在线| 55夜色66夜色国产精品视频| 国产狼人综合免费视频| 最近的2019中文字幕免费一页| 欧美一级电影久久| 日韩不卡在线观看| 91精品国产综合久久久久久蜜臀| 国产精品网站视频| 最近2019免费中文字幕视频三| 欧美激情精品久久久久久免费印度| 最近2019年中文视频免费在线观看| 亚洲精品免费一区二区三区| 日韩美女福利视频| 日韩av在线免播放器| 日本久久久久久久久久久| 日韩av影片在线观看| 欧洲精品久久久| 日韩欧美在线一区| 在线观看亚洲视频| 日韩av大片免费看| 日韩动漫免费观看电视剧高清| 成人免费淫片aa视频免费| 九九综合九九综合| 久久久久久有精品国产| 一区二区欧美激情| 亚洲精品一二区| 国内精品免费午夜毛片| 97高清免费视频| 日韩小视频在线观看| www.亚洲人.com| 91精品国产免费久久久久久| 久久久成人精品视频| 久久好看免费视频| 国产日韩欧美视频在线| 不卡在线观看电视剧完整版| 欧美高清视频在线观看| 91久久国产综合久久91精品网站| 久久综合网hezyo| 97超碰国产精品女人人人爽| www高清在线视频日韩欧美| 亚洲精品小视频| 亚洲xxx视频| 国产mv久久久| 亚洲自拍偷拍色图| 国产精品第一区| 在线观看欧美视频| 91精品国产综合久久香蕉922| 亚洲精品www| 91午夜理伦私人影院| 亚洲欧美资源在线| 亚洲最新av在线网站| 色悠悠久久久久| 亚洲男女性事视频| 性夜试看影院91社区| 欧美日韩中国免费专区在线看| 成人免费在线网址| 精品视频在线播放色网色视频| 亚洲精品自拍偷拍| 国产成人鲁鲁免费视频a| 亚洲精品久久久久久久久久久久久| 欧美激情综合亚洲一二区| 亚洲精品中文字幕女同| 亚洲区一区二区| 国产在线精品自拍| 成人国产精品久久久久久亚洲| 国内精品久久久久久中文字幕| 一本色道久久88亚洲综合88| 久久久久九九九九| 国产乱人伦真实精品视频| 欧美大肥婆大肥bbbbb| 中文字幕亚洲色图| 成人美女av在线直播| 日韩av色综合| 亚洲视频在线播放| 久热99视频在线观看| 7m第一福利500精品视频| 色一区av在线| 国产区精品在线观看| 中文字幕在线视频日韩| 一本色道久久综合狠狠躁篇怎么玩| 欧美性xxxx极品hd欧美风情| 麻豆成人在线看| 国产精品永久在线| 亚洲黄色免费三级| 久久综合免费视频影院| 欧美尺度大的性做爰视频| 日本午夜在线亚洲.国产| 2024亚洲男人天堂| 欧美猛少妇色xxxxx| 九色成人免费视频| 国产精品一区二区三区在线播放| 北条麻妃在线一区二区| 国产这里只有精品| 78色国产精品| 欧美野外猛男的大粗鳮| 97人人模人人爽人人喊中文字| 成人动漫网站在线观看| 亚洲 日韩 国产第一| 久久精品国产亚洲精品2020| 91在线视频成人| 国产精品日韩专区| 1769国产精品| 成人在线观看视频网站| 国语自产精品视频在免费| 国产亚洲欧美日韩一区二区| 91精品国产99| 久久久久久久一区二区| 国产91网红主播在线观看| 国产日韩欧美另类| 久久久久久久久久久人体| 日韩中文字幕在线| 一本一道久久a久久精品逆3p| 色婷婷**av毛片一区| 亚洲成人网在线| 久久99国产精品久久久久久久久| 国产69久久精品成人看| 国产精品一区二区三区免费视频| 色爱精品视频一区| 欧美伦理91i| 秋霞成人午夜鲁丝一区二区三区| 亚洲人成毛片在线播放| 91精品国产亚洲| 久久成人综合视频| 亚洲久久久久久久久久久| 国产精品午夜国产小视频| 国产一区香蕉久久| 夜色77av精品影院| 欧美韩日一区二区| 日韩av手机在线看| 色中色综合影院手机版在线观看| 一本一本久久a久久精品牛牛影视| 国产一区二区三区网站| 国产日韩在线免费| 国产日本欧美在线观看| 国内伊人久久久久久网站视频| 精品无人国产偷自产在线| 国产欧美日韩视频| 欧美成人精品一区二区三区| 亚洲最大福利网站| 亚洲精品视频在线播放| 欧美精品亚州精品| 91精品国产91久久久久福利| 欧美国产在线电影| 一区二区三区国产视频| 中文字幕av一区中文字幕天堂| 中文字幕亚洲欧美在线| 久久久国产精品一区| 欧美大片免费观看在线观看网站推荐| 亚洲国产精品资源| 亚洲欧美日韩天堂一区二区| 国产精品露脸自拍| 日韩在线激情视频| 日韩av影片在线观看| 国产精品av在线| 色综合久久久久久中文网| 亚洲精品视频中文字幕| 久久香蕉国产线看观看网|