Vue - 如何實現一個雙向綁定

 

JS - 如何實現一個相似 vue 的雙向綁定 Github JS 實現代碼vue

 

先來看一張圖:node

 

這張圖我作個簡要的描述:git

首先建立一個實例對象,分別觸發了 compile  解析指令 和 observer 監聽器,github

compile 解析指令則循環遞歸 解析 相似 v-model 這樣的指令,初始化 data 綁定數據,同時每一個節點建立一個訂閱者 watcher ,數組

observer 監聽器 則利用了 Object.defineProperty()  方法的描述屬性裏邊的 set,get方法,來監聽數據變化,app

get 方法是在建立實例對象,生成dom節點的時候都會觸發,固:在compile 解析編譯的時候,依次給每個節點添加了一個訂閱者到主題對象 Depdom

set 方法則是數據發生改變了,通知Dep訂閱器裏的全部wachter,而後找到對應訂閱者 wachter 觸發對應 update 更新視圖函數

簡單的說明就是這樣了。this

 

雙向綁定原理spa

vue數據雙向綁定是經過數據劫持結合發佈者-訂閱者模式的方式來實現的。

 

具體點兒

Vue雙向數據綁定的原理就是利用了 Object.defineProperty() 這個方法從新定義了對象獲取屬性值(get)和設置屬性值(set)的操做來實現的
 
再具體點兒
 
好吧,總結下來,分爲如下四個步驟
 
1.實現一個解析器Compile,能夠掃描和解析每一個節點的相關指令(v-model,v-on等指令),若是節點存在v-model,v-on等指令,則解析器Compile初始化這類節點的模板數據,使之能夠顯示在視圖上,同時初始化相應的訂閱者(Watcher)。

2.實現一個監聽器Observer,用來劫持並監聽全部屬性,若是有變更的,就通知訂閱者。

3.實現一個訂閱者Watcher,每個Watcher都綁定一個 update,watcher 能夠收到屬性的變化通知並執行相應的 update ,從而更新視圖。

4.實現MVVM,雙向綁定

 

如下實踐裏邊的幾個方法我就不作介紹了,感興趣可查詢

Object.defineProperty() 

createDocumentFragment()

Object.keys()

 

話很少說:直接上代碼:實現一個解析器Compile

 /*
  第一步
  1,建立文檔碎片,劫持全部dom節點,重繪dom節點
  2,重繪dom節點,初始化文檔碎片綁定數據 實現文檔編譯 compile
  */
  function getDocumentFragment(node, vm) {
    var flag = document.createDocumentFragment();
    var child;
    while (child = node.firstChild) {
      /*
       while (child = node.firstChild)
       至關於 
       child = node.firstChild
       while (child)
       */
      compile(child, vm);
      flag.appendChild(child);
    }
    node.appendChild(flag);
  }
  function compile(node, vm) {
    /*
   nodeType 返回數字,表示當前節點類型    
   1 Element    表明元素    Element, Text, 
   2    Attr    表明屬性    Text, EntityReference
   3    Text    表明元素或屬性中的文本內容。
   . . . 更多請查看文檔
   */
    if (node.nodeType === 1) {
      // 獲取當前元素的attr屬性
      var attr = node.attributes;
      for (let i = 0; i < attr.length; i++) {
        // nodeName 是attr屬性 key 即名稱 , 匹配自定義 v-m
        if (attr[i].nodeName === 'v-m') {
          // 獲取當前值 即 v-m = "test" 裏邊的 test 
          let name = attr[i].nodeValue;
          // 當前節點輸入事件
          node.addEventListener('keyup', function (e) {
            vm[name] = e.target.value;
          });
          // 頁面元素寫值  vm.data[name] 即 vm.data['test'] 即 MVVM
          node.value = vm.data[name];
          //最後移除標籤中的 v-m 屬性
          node.removeAttribute('v-m');
          // 爲每個節點建立一個 watcher  
          new Watcher(vm, node, name, "input");
        }
      }
      /*
      繼續遞歸調用 文檔編譯 實現 視圖更新 ;
      */
      if (child = node.firstChild) {
        /*
        if (child = node.firstChild)
        至關於 
        child = node.firstChild
        id(child)
        */
        compile(child, vm);
      }
    }
    if (node.nodeType === 3) {
      let reg = /\{\{(.*)\}\}/;
      if (reg.test(node.nodeValue)) {
        let name = RegExp.$1.trim();
        node.nodeValue = vm.data[name];
        // 爲每個節點建立一個 watcher  
        new Watcher(vm, node, name, "text");
      }
    }
  }

實現一個監聽器Observer

 /* 
  第二步
  實現一個數據監聽
  1,獲取當前實例對象的  data 屬性 key  
     observer(當前實例對象 data ,當前實例對象)
  2,使用 Object.defineProperty 方法 實現監聽 
  */
  function observe(data, vm) {
    Object.keys(data).forEach(function (key) {
      defineReactive(vm, key, data[key]);
    });
  }
  function defineReactive(vm, key, val) {
    /*
    Object.defineProperty
    obj
    要在其上定義屬性的對象。
    prop
    要定義或修改的屬性的名稱。
    descriptor
    將被定義或修改的屬性描述符。 描述符有不少,就包括咱們要市用 set , get 方法
    */
    var dep = new Dep();
    Object.defineProperty(vm, key, {
      get: function () {
        /* 
        if (Dep.target) dep.addSub(Dep.target);
        看到這段代碼不要差別,生成每個 dom節點,都會走 get 方法
        這裏爲每個節點 添加一個訂閱者 到主題對象 Dep
        */
        if (Dep.target) dep.addSub(Dep.target);
        console.log(val)
        return val;
      },
      set: function (newValue) {
        if (newValue === val) return;
        val = newValue;
        console.log(val + "=>" + newValue)
        // 通知全部訂閱者
        dep.notify();
      }
    });
  }

實現一個訂閱者Watcher

/*
  第三步
  
  1,實現一個 watcher 觀察者/訂閱者
    訂閱者原型上掛在兩個方法 分別是
    update 渲染視圖

  2,定義一個消息訂閱器
    很簡單,維護一個數組,用來收集訂閱者
    消息訂閱器原型掛載兩個方法 分別是  
    addSub 添加一個訂閱者   
    notify 數據變更 通知 這個訂閱者的 update 方法
  */
  function Watcher(vm, node, name, nodeType) {
    Dep.target = this;
    this.vm = vm;
    this.node = node;
    this.name = name;
    this.nodeType = nodeType;
    this.update();
    console.log(Dep.target)
    Dep.target = null;
  }
  Watcher.prototype = {
    update: function () {
      /*
      this.node 指向當前修改的 dom 元素
      this.vm 指向當前 dom 的實例對象
      根據 nodeType 類型 賦值渲染頁面
      */
      if (this.nodeType === 'text') {
        this.node.nodeValue = this.vm[this.name]
      }
      if (this.nodeType === 'input') {
        this.node.value = this.vm[this.name]
      }
    }
  }
  function Dep() {
    this.subs = [];
  }
  Dep.prototype = {
    addSub: function (sub) {
      this.subs.push(sub);
    },
    notify: function () {
      this.subs.forEach(function (sub) {
        sub.update();
      });
    }
  }

實現相似Vue的MVVM

/*
  建立一個構造函數,並生成實例化對象 vm
  */
  function Vue(o) {
    this.id = o.el;
    this.data = o.data;
    observe(this.data, this);
    getDocumentFragment(document.getElementById(this.id), this);
  }
  var vm = new Vue({
    el: 'app',
    data: {
      msg: 'HiSen',
      test: 'Hello,MVVM'
    }
  });

  也許看到最後你們也沒有看出個因此然,曾幾什麼時候的我跟大家同樣,看來看去,就是這麼幾段代碼;建議:拿下個人源碼,本身跑一跑,看一看,是騾子是馬拉出來溜溜。

相關文章
相關標籤/搜索