欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue雙向綁定的簡單實現(xiàn)

 更新時間:2016年12月22日 09:03:34   作者:yolo0927  
這篇文章主要為大家詳細介紹了vue雙向綁定的簡單實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下

研究了一下vue雙向綁定的原理,所以簡單記錄一下,以下例子只是簡單實現(xiàn),還請大家不要吐槽~

之前也了解過vue是通過數(shù)據(jù)劫持+訂閱發(fā)布模式來實現(xiàn)MVVM的雙向綁定的,但一直沒仔細研究,這次深入學習了一下,借此機會分享給大家。

首先先將流程圖給大家看一下

參考文章:Vue.js雙向綁定的實現(xiàn)原理

我雖然參考的是這篇文章,下面的代碼也是在閱讀幾遍后仿造的,自己只是簡單添加了個遞歸實現(xiàn)所有dom子節(jié)點的雙向綁定,以及添加了一些理解,但真正讓我了然于心,讓我可以獨立寫出2遍完整邏輯的其實是這張圖,所以個人認為這張流程圖才是最重要的,而我參考的這篇文章的作者也是參考這幅圖的原作者的。

原文章:剖析Vue原理&實現(xiàn)雙向綁定MVVM

站在閱讀和理解MVVM的完整邏輯的話,推薦大家看第一篇,但是第二篇原文章的圖文更能說明一些問題

如果大家看了我的解釋也能夠完全理解的話,那就更好啦啦啦啦啦~哈哈

好,下面我會從2個角度開始講解,先上單向綁定,再由單向綁定過渡到雙向綁定;

首先,先為大家解釋一下單向綁定model => view層的邏輯
1、劫持dom結構;
2、創(chuàng)建文檔碎片,利用文檔碎片重構dom結構;
3、在重構的過程中解析dom結構實現(xiàn)MVVM構造函數(shù)實例化后的數(shù)據(jù)初始化視圖數(shù)據(jù);
4、利用判斷dom一級子元素是否依然有子元素從而進行所有子元素的單向綁定;
5、將文檔碎片添加至根節(jié)點中.

這就是我總結的關于單向綁定的邏輯了,下面利用代碼跟大家解釋

//dom結構
<div id="app">
 <input type="text" v-model="msg">
 <p>{{msg}}</p>
 <ul>
 <li>1</li>
 <li>{{msg}}</li>
 <li>{{test}}</li>
 </ul>
</div>

//one-way-binding.js
 //判斷每個dom節(jié)點是否擁有子節(jié)點,若有則返回該節(jié)點
 function isChild(node){
 //這里使用childNodes可以讀取text文本節(jié)點,所以不用children
 if(node.childNodes.length ===0){
 return false;
 }
 else{
 return node;
 }
 }

 //利用文檔碎片劫持dom結構及數(shù)據(jù),進而進行dom的重構
 function nodeToFragment(node,vm){
 var frag = document.createDocumentFragment();
 var child;
 while(child = node.firstChild){
 //一級dom節(jié)點數(shù)據(jù)綁定
 compile(child,vm);
 //判斷每個一級dom節(jié)點是否有二級節(jié)點,若有則遞歸處理文檔碎片
 if(isChild(child)){
 //遞歸實現(xiàn)二級dom節(jié)點的重構
 nodeToFragment(isChild(child),vm);
 }
 frag.appendChild(child);
 }
 //將文檔碎片添加至對應node中,最后為id為app的元素下
 node.appendChild(frag);
 }

 //初始化綁定數(shù)據(jù)
 function compile(node,vm){
 //node節(jié)點為元素節(jié)點時
 if(node.nodeType === 1){
 var attr = node.attributes;
 //遍歷當前節(jié)點的所有屬性
 for(var i=0;i<attr.length;i++){
 if(attr[i].nodeName === 'v-model'){
 //屬性名
 var name = attr[i].nodeValue;
 //將data下對應屬性名的值賦值給當前節(jié)點值
 //這里因為node是input標簽所以值為node.value
 node.value = vm.data[name];
 //最后標簽中的v-model屬性也可以功成身退了,刪除它
 node.removeAttribute(attr[i].nodeName);
 }
 }
 }

 //node節(jié)點為text文本節(jié)點#text時
 if(node.nodeType === 3){
 var reg = /\{\{(.*)\}\}/;
 if(reg.test(node.nodeValue.trim())){
 //將正則匹配到的{{}}中的字符串賦值給name
 var name = RegExp.$1;
 //利用name對應賦值相應的節(jié)點值
 node.nodeValue = vm.data[name];
 }
 }
 }

 //MVVM構造函數(shù),這里我就寫成Vue了
 function Vue(options){
 this.id = options.el;
 this.data = options.data;
 //將根節(jié)點與實例化后的對象作為參數(shù)傳入
 nodeToFragment(document.getElementById(this.id),this);
 }
 //實例化
 var vm = new Vue({
 el:'app',
 data:{
 msg:'hello,two-ways-binding',
 test:'test key'
 }
 })

上述就是簡單的單向綁定了,整個邏輯實際上非常簡單,我再來跟大家說明一下

1、為了令model層的數(shù)據(jù)可以綁定到view層的dom上,所以我們想了一個辦法來替換dom中的一些元素值,而明顯一個個替換時不可取的,因為大量的dom操作會降低程序的運行效率,你想想,每次dom操作可都是一次對dom整體的遍歷過程~,所以我們覺得采用文檔碎片的形式,將dom一次全部劫持,在內(nèi)存中執(zhí)行全部數(shù)據(jù)綁定操作,最后只進行一次dom操作,即添加子節(jié)點來解決這個頻繁操作dom的問題,你也可以理解為中間的一層存在于內(nèi)存中的虛擬dom;

2、那么既然如此,我們就要首先劫持所有dom節(jié)點,這里我們利用nodeToFragment函數(shù)來劫持;

3、在每次劫持對應dom節(jié)點的過程中,我們也會相對應的實現(xiàn)對該dom元素的數(shù)據(jù)綁定,以求在最后直接添加到為根節(jié)點的子元素即可,這個過程我們就在nodeToFragment函數(shù)中插入了compile函數(shù)來初始化綁定,并且添加遞歸函數(shù)實現(xiàn)所有子元素的初始綁定;

4、在compile函數(shù)中我們添加的數(shù)據(jù)又從何而來呢?對,正是因為這點,所以我們建立MVVM的構造函數(shù)Vue來實現(xiàn)數(shù)據(jù)支持,并實現(xiàn)在實例化時就執(zhí)行nodeToFragment同時重構dom和實現(xiàn)初始化綁定compile;

5、好了,單向綁定就是這么簡單,4個函數(shù)即可Vue => nodeToFragment => compile => isChild。

完成圖如下

好了,再回過來看看整體的流程圖,我們已經(jīng)實現(xiàn)了這一塊了

接下來,休息下,大家準備開始流程圖后面的雙向綁定,ok,還是按照單向綁定的順序,先跟大家講明實現(xiàn)邏輯;

1、創(chuàng)建數(shù)據(jù)監(jiān)聽者observer去監(jiān)聽view層數(shù)據(jù)的變化;(利用Object.defineProperty劫持所有要用到的數(shù)據(jù))

2、當view層數(shù)據(jù)變化后,通過通知者Dep通知訂閱者去實現(xiàn)數(shù)據(jù)的更新;(通知后,遍歷所有用到數(shù)據(jù)的訂閱者更新數(shù)據(jù))

3、訂閱者watcher接收到view層數(shù)據(jù)變更后,重新對變化的數(shù)據(jù)進行賦值,改變model層,從而改變所有view層用到過該數(shù)據(jù)的地方。(更新數(shù)據(jù),并改變view層所有用到該數(shù)據(jù)的節(jié)點值)

上面是實現(xiàn)邏輯,下面將通過具體代碼告訴大家每一步的做法,由于雙向綁定中訂閱者會涉及初始化綁定的過程,所以代碼量較多,我會在大更改處用——為大家框出來

 //判斷每個dom節(jié)點是否擁有子節(jié)點,若有則返回該節(jié)點
 function isChild(node){
 if(node.childNodes.length ===0){
 return false;
 }
 else{
 return node;
 }
 }

 //利用文檔碎片劫持dom結構及數(shù)據(jù),進而進行dom的重構
 function nodeToFragment(node,vm){
 var frag = document.createDocumentFragment();
 var child;
 while(child = node.firstChild){
 //一級dom節(jié)點數(shù)據(jù)綁定
 compile(child,vm);
 //判斷每個一級dom節(jié)點是否有二級節(jié)點,若有則遞歸處理文檔碎片
 if(isChild(child)){
 nodeToFragment(isChild(child),vm);
 }
 frag.appendChild(child);
 }
 node.appendChild(frag);
 }

 //初始化綁定數(shù)據(jù)
 function compile(node,vm){
 //node節(jié)點為元素節(jié)點時
 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;
 //特殊處理input標簽
 //------------------------
 if(node.nodeName === 'INPUT'){
 node.addEventListener('keyup',function(e){
 vm[name] = e.target.value;
 })
 }
 //由于數(shù)據(jù)已經(jīng)由data劫持至vm下,所以直接賦值vm[name]即可觸發(fā)getter訪問器
 node.value = vm[name];
 //-------------------------
 node.removeAttribute(attr[i].nodeName);
 }
 }
 }

 //node節(jié)點為text文本節(jié)點時
 if(node.nodeType === 3){
 var reg = /\{\{(.*)\}\}/;
 if(reg.test(node.nodeValue.trim())){
 var name = RegExp.$1;
 //node.nodeValue = vm[name];
 //----------------------
 //為每個節(jié)點建立訂閱者,通過訂閱者watcher初始化及更新視圖數(shù)據(jù)
 new watcher(vm,node,name);
 //-----------------------
 }
 }
 }
 //----------------------------------------------------------------
 //訂閱者(為每個節(jié)點的數(shù)據(jù)建立watcher隊列,每次接受更改數(shù)據(jù)需求后,利用劫持數(shù)據(jù)執(zhí)行對應節(jié)點的數(shù)據(jù)更新)
 function watcher(vm,node,name){
 //將每個掛載了數(shù)據(jù)的dom節(jié)點添加到通知者列表,要保證每次創(chuàng)建watcher時只有一個添加目標,否則后續(xù)會因為watcher是全局而被覆蓋,所以每次要清空目標

 Dep.target = this;
 this.vm = vm;
 this.node = node;
 this.name = name;
 //執(zhí)行update的時候會調(diào)用監(jiān)聽者劫持的getter事件,從而添加到watcher隊列,因為update中有訪問this.vm[this.name]
 this.update();
 //為保證只有一個全局watcher,添加到隊列后,清空全局watcher
 Dep.target = null;
 }

 watcher.prototype = {
 update(){
 this.get();
 //input標簽特殊處理化
 if(this.node.nodeName === 'INPUT'){
 this.node.value = this.value;
 }
 else{
 this.node.nodeValue = this.value;
 }
 },
 get(){
 //這里調(diào)用了數(shù)據(jù)劫持的getter
 this.value = this.vm[this.name];
 }
 };

 //通知者(將監(jiān)聽者的更改信息需求發(fā)送給訂閱者,告訴訂閱者哪些數(shù)據(jù)需要更改)
 function Dep(){
 this.subs = [];
 }

 Dep.prototype = {
 addSub(watcher){
 //添加用到數(shù)據(jù)的節(jié)點進入watcher隊列
 this.subs.push(watcher);
 },
 notify(){
 //遍歷watcher隊列,令相應數(shù)據(jù)節(jié)點重新更新view層數(shù)據(jù),model => view
 this.subs.forEach(function(watcher){
 watcher.update();
 })
 }
 };

 //監(jiān)聽者(利用setter監(jiān)聽view => model的數(shù)據(jù)變化,發(fā)出通知更改model數(shù)據(jù)后再從model => view更新視圖所有用到該數(shù)據(jù)的地方)
 function observer(data,vm){
 //遍歷劫持data下所有屬性
 Object.keys(data).forEach(function(key){
 defineReactive(vm,key,data[key]);
 })
 }

 function defineReactive(vm,key,val){
 //新建通知者
 var dep = new Dep();
 //靈活利用setter與getter訪問器
 Object.defineProperty(vm,key,{
 get(){
 //初始化數(shù)據(jù)更新時將每個數(shù)據(jù)的watcher添加至隊列棧中
 if(Dep.target) dep.addSub(Dep.target);
 return val;
 },
 set(newVal){
 if(val === newVal) return ;
 //初始化后,文檔碎片中的虛擬dom已與model層數(shù)據(jù)綁定起來了
 val = newVal;
 //同步更新model中data屬性下的數(shù)據(jù)
 vm.data[key] = val;
 //數(shù)據(jù)有改動時向通知者發(fā)送通知
 dep.notify();
 }
 })
 }
 //---------------------------------------------------------------
 function Vue(options){
 this.id = options.el;
 this.data = options.data;
 observer(this.data,this);
 nodeToFragment(document.getElementById(this.id),this);
 }

 var vm = new Vue({
 el:'app',
 data:{
 msg:'hello,two-ways-binding',
 test:'test key'
 }
 })

好的,到這里雙向綁定的講解也就結束了,代碼量確實有點多,但是我們看到其實邏輯在你熟悉后并不復雜,特別是參照了上文的流程圖后,其實就是:

1、通過observer劫持所有model層數(shù)據(jù)到vue下,并在劫持時靈活運用getter與setter訪問器屬性來在虛擬dom初始化數(shù)據(jù)綁定時,利用此時的get方法綁定初始化數(shù)據(jù)進入通知者隊列,后續(xù)初始化完成后,在view層數(shù)據(jù)發(fā)生變化時,利用set方法及時利用通知者發(fā)出通知;

2、在dep通知者接收到有一處dom節(jié)點數(shù)據(jù)更改的通知時,遍歷watcher隊列及告訴watcher訂閱者,view層數(shù)據(jù)有所變動model層已經(jīng)相應改變,你要重新執(zhí)行update將model層的數(shù)據(jù)更新到view層所有用到該數(shù)據(jù)的地方(比如我們利用input實現(xiàn)的雙向綁定就不止一個dom節(jié)點內(nèi)使用了,而是多個,所以必須整體遍歷修改)。

3、這是一個model => view => model =>view的過程,真正的邏輯順序為model => view,view => model,model => view,反復的過程~

貼上結果圖

初始未改動view層數(shù)據(jù)圖

修改view層數(shù)據(jù)后圖

最后大家再看一次流程圖,看看整個邏輯是不是跟流程圖一模一樣,通過流程圖就可以回憶起這個邏輯過程,寫多2遍就可以記??!

以上只是通過簡單實現(xiàn)來告訴大家vue的數(shù)據(jù)劫持+訂閱發(fā)布模式這個雙向綁定的原理,其中有很多細節(jié)上的不足可能未作處理,還請見諒~

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • 使用ElementUI修改el-tabs標簽頁組件樣式

    使用ElementUI修改el-tabs標簽頁組件樣式

    這篇文章主要介紹了使用ElementUI修改el-tabs標簽頁組件樣式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 原生JS實現(xiàn)Vue transition fade過渡動畫效果示例

    原生JS實現(xiàn)Vue transition fade過渡動畫效果示例

    這篇文章主要為大家介紹了原生JS實現(xiàn)Vue transition fade過渡動畫效果示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • axios解決高并發(fā)的方法:axios.all()與axios.spread()的操作

    axios解決高并發(fā)的方法:axios.all()與axios.spread()的操作

    這篇文章主要介紹了axios解決高并發(fā)的方法:axios.all()與axios.spread()的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • Vue自定義全局彈窗組件操作

    Vue自定義全局彈窗組件操作

    這篇文章主要介紹了Vue自定義全局彈窗組件操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • vue實現(xiàn)右鍵菜單欄

    vue實現(xiàn)右鍵菜單欄

    這篇文章主要為大家詳細介紹了vue實現(xiàn)右鍵菜單欄,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Vue Element前端應用開發(fā)之echarts圖表

    Vue Element前端應用開發(fā)之echarts圖表

    在我們做應用系統(tǒng)的時候,往往都會涉及圖表的展示,綜合的圖表展示能夠給客戶帶來視覺的享受和數(shù)據(jù)直觀體驗,同時也是增強客戶認同感的舉措之一
    2021-05-05
  • 關于vue中hash和history的區(qū)別與使用圖文詳解

    關于vue中hash和history的區(qū)別與使用圖文詳解

    vue-router中有hash模式和history模式,下面這篇文章主要給大家介紹了關于vue中hash和history的區(qū)別與使用的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-03-03
  • vue中如何禁止input框和textarea編輯

    vue中如何禁止input框和textarea編輯

    這篇文章主要介紹了vue中如何禁止input框和textarea編輯,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • 詳解用vue-cli來搭建vue項目和webpack

    詳解用vue-cli來搭建vue項目和webpack

    本篇文章主要介紹了詳解用vue-cli來搭建vue項目和webpack,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • 使用elementuiadmin去掉默認mock權限控制的設置

    使用elementuiadmin去掉默認mock權限控制的設置

    這篇文章主要介紹了使用elementuiadmin去掉默認mock權限控制的設置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04

最新評論