日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Vue2.x的雙向綁定原理及實(shí)現(xiàn)

 Vue 數(shù)據(jù)雙向綁定原理

Vue 是利用的 Object.defineProperty() 方法進(jìn)行的數(shù)據(jù)劫持,利用 set、get 來檢測數(shù)據(jù)的讀寫。

成都創(chuàng)新互聯(lián)是一家專業(yè)提供原陽企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站制作、成都做網(wǎng)站、H5場景定制、小程序制作等業(yè)務(wù)。10年已為原陽眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)公司優(yōu)惠進(jìn)行中。

https://jsrun.net/RMIKp/embed...

MVVM 框架主要包含兩個方面,數(shù)據(jù)變化更新視圖,視圖變化更新數(shù)據(jù)。

視圖變化更新數(shù)據(jù),如果是像 input 這種標(biāo)簽,可以使用 oninput 事件..

數(shù)據(jù)變化更新視圖可以使用 Object.definProperty() 的 set 方法可以檢測數(shù)據(jù)變化,當(dāng)數(shù)據(jù)改變就會觸發(fā)這個函數(shù),然后更新視圖。

實(shí)現(xiàn)過程

我們知道了如何實(shí)現(xiàn)雙向綁定了,首先要對數(shù)據(jù)進(jìn)行劫持監(jiān)聽,所以我們需要設(shè)置一個 Observer 函數(shù),用來監(jiān)聽所有屬性的變化。

如果屬性發(fā)生了變化,那就要告訴訂閱者 watcher 看是否需要更新數(shù)據(jù),如果訂閱者有多個,則需要一個 Dep 來收集這些訂閱者,然后在監(jiān)聽器 observer 和 watcher 之間進(jìn)行統(tǒng)一管理。

還需要一個指令解析器 compile,對需要監(jiān)聽的節(jié)點(diǎn)和屬性進(jìn)行掃描和解析。

因此,流程大概是這樣的:

  1.  實(shí)現(xiàn)一個監(jiān)聽器 Observer,用來劫持并監(jiān)聽所有屬性,如果發(fā)生變動,則通知訂閱者。
  2.  實(shí)現(xiàn)一個訂閱者 Watcher,當(dāng)接到屬性變化的通知時,執(zhí)行對應(yīng)的函數(shù),然后更新視圖,使用 Dep 來收集這些 Watcher。
  3.  實(shí)現(xiàn)一個解析器 Compile,用于掃描和解析的節(jié)點(diǎn)的相關(guān)指令,并根據(jù)初始化模板以及初始化相應(yīng)的訂閱器。

顯示一個 Observer

Observer 是一個數(shù)據(jù)監(jiān)聽器,核心方法是利用 Object.defineProperty() 通過遞歸的方式對所有屬性都添加 setter、getter 方法進(jìn)行監(jiān)聽。

 
 
 
 
  1. var library = {  
  2.   book1: {  
  3.     name: "",  
  4.   },  
  5.   book2: "",  
  6. };  
  7. observe(library);  
  8. library.book1.name = "vue權(quán)威指南"; // 屬性name已經(jīng)被監(jiān)聽了,現(xiàn)在值為:“vue權(quán)威指南”  
  9. library.book2 = "沒有此書籍"; // 屬性book2已經(jīng)被監(jiān)聽了,現(xiàn)在值為:“沒有此書籍”  
  10. // 為數(shù)據(jù)添加檢測  
  11. function defineReactive(data, key, val) {  
  12.   observe(val); // 遞歸遍歷所有子屬性  
  13.   let dep = new Dep(); // 新建一個dep  
  14.   Object.defineProperty(data, key, {  
  15.     enumerable: true,  
  16.     configurable: true,  
  17.     get: function() {  
  18.       if (Dep.target) {  
  19.         // 判斷是否需要添加訂閱者,僅第一次需要添加,之后就不用了,詳細(xì)看Watcher函數(shù)  
  20.         dep.addSub(Dep.target); // 添加一個訂閱者  
  21.       } 
  22.       return val;  
  23.     },  
  24.     set: function(newVal) {  
  25.       if (val == newVal) return; // 如果值未發(fā)生改變就return  
  26.       val = newVal;  
  27.       console.log(  
  28.         "屬性" + key + "已經(jīng)被監(jiān)聽了,現(xiàn)在值為:“" + newVal.toString() + "”"  
  29.       );  
  30.       dep.notify(); // 如果數(shù)據(jù)發(fā)生變化,就通知所有的訂閱者。  
  31.     },  
  32.   }); 
  33. }  
  34. // 監(jiān)聽對象的所有屬性  
  35. function observe(data) {  
  36.   if (!data || typeof data !== "object") {  
  37.     return; // 如果不是對象就return 
  38.   }  
  39.   Object.keys(data).forEach(function(key) {  
  40.     defineReactive(data, key, data[key]);  
  41.   });  
  42. }  
  43. // Dep 負(fù)責(zé)收集訂閱者,當(dāng)屬性發(fā)生變化時,觸發(fā)更新函數(shù)。  
  44. function Dep() {  
  45.   this.subs = {};  
  46. }  
  47. Dep.prototype = {  
  48.   addSub: function(sub) {  
  49.     this.subs.push(sub);  
  50.   },  
  51.   notify: function() {  
  52.     this.subs.forEach((sub) => sub.update());  
  53.   },  
  54. }; 

思路分析中,需要有一個可以容納訂閱者消息訂閱器 Dep,用于收集訂閱者,在屬性發(fā)生變化時執(zhí)行對應(yīng)的更新函數(shù)。

從代碼上看,將訂閱器 Dep 添加在 getter 里,是為了讓 Watcher 初始化時觸發(fā),,因此,需要判斷是否需要訂閱者。

在 setter 中,如果有數(shù)據(jù)發(fā)生變化,則通知所有的訂閱者,然后訂閱者就會更新對應(yīng)的函數(shù)。

到此為止,一個比較完整的 Observer 就完成了,接下來開始設(shè)計 Watcher.

實(shí)現(xiàn) Watcher

訂閱者 Watcher 需要在初始化的時候?qū)⒆约禾砑拥接嗛喥?Dep 中,我們已經(jīng)知道監(jiān)聽器 Observer 是在 get 時執(zhí)行的 Watcher 操作,所以只需要在 Watcher 初始化的時候觸發(fā)對應(yīng)的 get 函數(shù)去添加對應(yīng)的訂閱者操作即可。

那給如何觸發(fā) get 呢?因?yàn)槲覀円呀?jīng)設(shè)置了 Object.defineProperty(),所以只需要獲取對應(yīng)的屬性值就可以觸發(fā)了。

我們只需要在訂閱者 Watcher 初始化的時候,在 Dep.target 上緩存下訂閱者,添加成功之后在將其去掉就可以了。

 
 
 
 
  1. function Watcher(vm, exp, cb) {  
  2.   this.cb = cb;  
  3.   this.vm = vm;  
  4.   this.exp = exp;  
  5.   thisthis.value = this.get(); // 將自己添加到訂閱器的操作  
  6. }  
  7. Watcher.prototype = {  
  8.   update: function() {  
  9.     this.run();  
  10.   },  
  11.   run: function() {  
  12.     var value = this.vm.data[this.exp];  
  13.     var oldVal = this.value; 
  14.     if (value !== oldVal) {  
  15.       this.value = value;  
  16.       this.cb.call(this.vm, value, oldVal);  
  17.     }  
  18.   },  
  19.   get: function() {  
  20.     Dep.target = this; // 緩存自己,用于判斷是否添加watcher。  
  21.     var value = this.vm.data[this.exp]; // 強(qiáng)制執(zhí)行監(jiān)聽器里的get函數(shù)  
  22.     Dep.target = null; // 釋放自己  
  23.     return value;  
  24.   },  
  25. }; 

到此為止, 簡單的額 Watcher 設(shè)計完畢,然后將 Observer 和 Watcher 關(guān)聯(lián)起來,就可以實(shí)現(xiàn)一個簡單的的雙向綁定了。

因?yàn)檫€沒有設(shè)計解析器 Compile,所以可以先將模板數(shù)據(jù)寫死。

將代碼轉(zhuǎn)化為 ES6 構(gòu)造函數(shù)的寫法,預(yù)覽試試。

https://jsrun.net/8SIKp/embed...

這段代碼因?yàn)闆]有實(shí)現(xiàn)編譯器而是直接傳入了所綁定的變量,我們只在一個節(jié)點(diǎn)上設(shè)置一個數(shù)據(jù)(name)進(jìn)行綁定,然后在頁面上進(jìn)行 new MyVue,就可以實(shí)現(xiàn)雙向綁定了。

并兩秒后進(jìn)行值得改變,可以看到,頁面也發(fā)生了變化。

 
 
 
 
  1. // MyVue  
  2. proxyKeys(key) {  
  3.     var self = this;  
  4.     Object.defineProperty(this, key, {  
  5.         enumerable: false,  
  6.         configurable: true,  
  7.         get: function proxyGetter() {  
  8.             return self.data[key];  
  9.         },  
  10.         set: function proxySetter(newVal) {  
  11.             self.data[key] = newVal;  
  12.         }  
  13.     });  

上面這段代碼的作用是將 this.data 的 key 代理到 this 上,使得我可以方便的使用 this.xx 就可以取到 this.data.xx。

實(shí)現(xiàn) Compile

雖然上面實(shí)現(xiàn)了雙向數(shù)據(jù)綁定,但是整個過程都沒有解析 DOM 節(jié)店,而是固定替換的,所以接下來要實(shí)現(xiàn)一個解析器來做數(shù)據(jù)的解析和綁定工作。

解析器 compile 的實(shí)現(xiàn)步驟:

  1.  解析模板指令,并替換模板數(shù)據(jù),初始化視圖。
  2.  將模板指定對應(yīng)的節(jié)點(diǎn)綁定對應(yīng)的更新函數(shù),初始化相應(yīng)的訂閱器。

為了解析模板,首先需要解析 DOM 數(shù)據(jù),然后對含有 DOM 元素上的對應(yīng)指令進(jìn)行處理,因此整個 DOM 操作較為頻繁,可以新建一個 fragment 片段,將需要的解析的 DOM 存入 fragment 片段中在進(jìn)行處理。

 
 
 
 
  1. function nodeToFragment(el) {  
  2.   var fragment = document.createDocumentFragment();  
  3.   var child = el.firstChild;  
  4.   while (child) {  
  5.     // 將Dom元素移入fragment中  
  6.     fragment.appendChild(child);  
  7.     child = el.firstChild;  
  8.   }  
  9.   return fragment;  

接下來需要遍歷各個節(jié)點(diǎn),對含有相關(guān)指令和模板語法的節(jié)點(diǎn)進(jìn)行特殊處理,先進(jìn)行最簡單模板語法處理,使用正則解析“{{變量}}”這種形式的語法。

 
 
 
 
  1. function compileElement (el) {  
  2.     var childNodes = el.childNodes;  
  3.     var self = this;  
  4.     [].slice.call(childNodes).forEach(function(node) {  
  5.         var reg = /\{\{(.*)\}\}/; // 匹配{{xx}}  
  6.         var text = node.textContent;  
  7.         if (self.isTextNode(node) && reg.test(text)) {  // 判斷是否是符合這種形式{{}}的指令  
  8.             self.compileText(node, reg.exec(text)[1]);  
  9.         }  
  10.         if (node.childNodes && node.childNodes.length) {  
  11.             self.compileElement(node);  // 繼續(xù)遞歸遍歷子節(jié)點(diǎn)  
  12.         }  
  13.     });  
  14. },  
  15. function compileText (node, exp) {  
  16.     var self = this;  
  17.     var initText = this.vm[exp];  
  18.     updateText(node, initText);  // 將初始化的數(shù)據(jù)初始化到視圖中  
  19.     new Watcher(this.vm, exp, function (value) {  // 生成訂閱器并綁定更新函數(shù)  
  20.         self.updateText(node, value);  
  21.     });  
  22. },  
  23. function updateText (node, value) {  
  24.     node.textContent = typeof value == 'undefined' ? '' : value;  

獲取到最外層的節(jié)點(diǎn)后,調(diào)用 compileElement 函數(shù),對所有的子節(jié)點(diǎn)進(jìn)行判斷,如果節(jié)點(diǎn)是文本節(jié)點(diǎn)切匹配{{}}這種形式的指令,則進(jìn)行編譯處理,初始化對應(yīng)的參數(shù)。

然后需要對當(dāng)前參數(shù)生成一個對應(yīng)的更新函數(shù)訂閱器,在數(shù)據(jù)發(fā)生變化時更新對應(yīng)的 DOM。

這樣就完成了解析、初始化、編譯三個過程了。

接下來改造一個 myVue 就可以使用模板變量進(jìn)行雙向數(shù)據(jù)綁定了。

https://jsrun.net/K4IKp/embed...

添加解析事件

添加完 compile 之后,一個數(shù)據(jù)雙向綁定就基本完成了,接下來就是在 Compile 中添加更多指令的解析編譯,比如 v-model、v-on、v-bind 等。

添加一個 v-model 和 v-on 解析:

 
 
 
 
  1. function compile(node) {  
  2.   var nodenodeAttrs = node.attributes; 
  3.   var self = this;  
  4.   Array.prototype.forEach.call(nodeAttrs, function(attr) {  
  5.     var attrattrName = attr.name; 
  6.     if (isDirective(attrName)) {  
  7.       var exp = attr.value;  
  8.       var dir = attrName.substring(2);  
  9.       if (isEventDirective(dir)) {  
  10.         // 事件指令  
  11.         self.compileEvent(node, self.vm, exp, dir);  
  12.       } else { 
  13.         // v-model 指令  
  14.         self.compileModel(node, self.vm, exp, dir);  
  15.       }  
  16.       node.removeAttribute(attrName); // 解析完畢,移除屬性  
  17.     }  
  18.   });  
  19. }  
  20. // v-指令解析  
  21. function isDirective(attr) {  
  22.   return attr.indexOf("v-") == 0;  
  23. }  
  24. // on: 指令解析  
  25. function isEventDirective(dir) {  
  26.   return dir.indexOf("on:") === 0;  

上面的 compile 函數(shù)是用于遍歷當(dāng)前 dom 的所有節(jié)點(diǎn)屬性,然后判斷屬性是否是指令屬性,如果是在做對應(yīng)的處理(事件就去監(jiān)聽事件、數(shù)據(jù)就去監(jiān)聽數(shù)據(jù)..)

完整版 myVue

在 MyVue 中添加 mounted 方法,在所有操作都做完時執(zhí)行。

 
 
 
 
  1. class MyVue {  
  2.   constructor(options) {  
  3.     var self = this;  
  4.     this.data = options.data;  
  5.     this.methods = options.methods;
  6.      Object.keys(this.data).forEach(function(key) {  
  7.       self.proxyKeys(key);  
  8.     });  
  9.     observe(this.data);  
  10.     new Compile(options.el, this);  
  11.     options.mounted.call(this); // 所有事情處理好后執(zhí)行mounted函數(shù)  
  12.   }  
  13.   proxyKeys(key) {  
  14.     // 將this.data屬性代理到this上  
  15.     var self = this;  
  16.     Object.defineProperty(this, key, {  
  17.       enumerable: false,  
  18.       configurable: true,  
  19.       get: function getter() {  
  20.         return self.data[key];  
  21.       },  
  22.       set: function setter(newVal) {  
  23.         self.data[key] = newVal;  
  24.       },  
  25.     });  
  26.   }  

然后就可以測試使用了。

https://jsrun.net/Y4IKp/embed...

總結(jié)一下流程,回頭在哪看一遍這個圖,是不是清楚很多了。

可以查看的代碼地址:Vue2.x 的雙向綁定原理及實(shí)現(xiàn)


網(wǎng)站標(biāo)題:Vue2.x的雙向綁定原理及實(shí)現(xiàn)
URL標(biāo)題:http://www.dlmjj.cn/article/dhiiigh.html