簡陋的雙向綁定

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model="text">
    {{text}}
  </div>
</body>
<script type="text/javascript">

//將結點劫持
  function node2Fragment(node,vm){
    var flag=document.createDocumentFragment();
    var child;
    while(child=node.firstChild){
      //編譯每一個節點
      compile(child,vm)
      /*appendChild 會把節點從原來結構轉移到目標父節點,因此至關於原來的節點被刪除*/
      flag.appendChild(child)
    }
    return flag
  }

  /*
  DocumentFragments 是DOM節點。它們不是主DOM樹的一部分。一般的用例是建立文檔片斷,將元素附加到文檔片斷,而後將文檔片斷附加到DOM樹。在DOM樹中,文檔片斷被其全部的子元素所代替。
  由於文檔片斷存在於內存中,並不在DOM樹中,因此將子元素插入到文檔片斷時不會引發頁面迴流(reflow)(對元素位置和幾何上的計算)。所以,使用文檔片斷document fragments 一般會起到優化性能的做用
   */
  
  //編譯虛擬dom的輔助函數
  function compile(node,vm){
    /*匹配{{裏的內容}}*/
    var reg=/\{\{(.*)\}\}/;

    /*nodeType -1元素節點 3-文本節點*/
    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;
          node.addEventListener('input',function(e){
            vm[name]=e.target.value;
          })
          node.value=vm[name];
        }
      }
    };
    if(node.nodeType===3){
      if(reg.test(node.nodeValue)){
        var name=RegExp.$1;
        name=name.trim();
        //初始化數據而且觸發get函數
        new Watcher(vm,node,name);
      }
    }
  }
  //定義訂閱者類
  function Watcher(vm,node,name){
    //Dep.target存儲了當前的觀察者,使get函數可以存儲觀察者
    Dep.target=this;
    this.name=name;
    this.node=node;
    this.vm=vm;
    //訂閱者執行一次更新視圖並把訂閱者添加進去
    this.update();
    Dep.target=null;
  }
  Watcher.prototype={
    update:function(){
      //觸發set函數
      this.get();
      //更新視圖
      this.node.nodeValue=this.value;
    },
    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)dep.addSub(Dep.target)
        return val
      },
      set:function(newVal){
        if(newVal===val)return ;
        val=newVal;
        //實例發出通知(更新全部訂閱了這個屬性消息的view)
        dep.notify();
      }
    })
  }
  function Dep(){
    //主題的訂閱者們
    this.subs=[];
  }
  Dep.prototype={
    //添加訂閱者的方法
    addSub:function(sub){
      this.subs.push(sub);
    },
    //發佈信息的方法(讓訂閱者watcher們所有更新view)
    notify:function(){
      this.subs.forEach(function(sub){
        sub.update();
      })
    }
  }
  //監聽vm這個對象的obj有的屬性
  function observe(obj,vm){
    Object.keys(obj).forEach(function(key){
      console.log(vm)
      defineReactive(vm,key,obj[key])
    })
  }
  //構建Vue對象
  function Vue(options){
    this.data=options.data;
    var id=options.el;
    var data=this.data
    //監聽this(即vm)這個對象的data屬性
    observe(data,this)
    var dom=node2Fragment(document.getElementById(id),this);
    document.getElementById(id).appendChild(dom);
  }
  var vm=new Vue({
    el:'app',
    data:{
      text:'hello world',
      fuck:'fuckingboy'
    }
  })
  console.log(vm);

/*
  使用發佈-訂閱模式與閉包,很巧妙的實現了雙向綁定,
  首先observe調用,遍歷數據對象,並給每一個屬性利用閉包建立私有空間,
  每一個私有空間都new出一個Dep對象,試每一個空間都有本身的訂閱發佈對象,
  並在get訪問器裏添加訂閱者,那麼每一個Watcher對象裏設置當前訂閱對象利用
  觸發get訪問器方法能夠向當前屬性的私有空間添加訂閱者,而後只要每次
  設置這個屬性的值時就會觸發設置器就會直接觸發向全部訂閱了這個屬性訂閱者
  接收到發佈的消息,那麼就能夠更新對應視圖內容。
 */

</script>
</html>

 

扒一張圖,表明一下。javascript

 

 

一直據說vue的雙向綁定很巧妙,很小巧,因此就大概學習一下。html

一個雙向綁定系統是要很嚴謹的代碼實現的,因此代碼量必定很多,vue

要考慮的因素不少,如data深遞歸,html各類節點的深遞歸,還有各類java

數據對應視圖的那些節點位置對應,還有正則的使用,這些種種考慮node

就不是通常幾天能夠搞定的,因此學習一下就能夠,沒有必要造輪子,閉包

以上的例子沒法符合數據的多層嵌套和就實現了一個v-model,其餘都沒有,app

只能加深雙向綁定原理,好之後對這方面的使用更順手。dom

相關文章
相關標籤/搜索