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

基本原理

只為您設(shè)計更接底氣、較有營銷力的好網(wǎng)站,將營銷策劃與網(wǎng)頁設(shè)計互相結(jié)合的專業(yè)機構(gòu),營銷型網(wǎng)站建設(shè)公司中較早掌握H5網(wǎng)站設(shè)計技術(shù)的機構(gòu)。一個好的成都品牌網(wǎng)站建設(shè),不能只是一張名片,茫茫網(wǎng)海,想要快速吸引到您客戶的眼球,必須全方位的展現(xiàn)出企業(yè)突出的優(yōu)勢,以求達到主動營銷的效果,最終促成成交!

Vue.采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter和getter,數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)函數(shù)的回調(diào)。

思路整理

要實現(xiàn)mvvm的雙向綁定,需要實現(xiàn)如下幾點:

1.實現(xiàn)一個數(shù)據(jù)監(jiān)聽器Observer,能夠?qū)ο蟮乃袑傩赃M行監(jiān)聽,發(fā)生變化時拿到最新值通知訂閱者
2.實現(xiàn)一個解析器Compile,對每個子元素節(jié)點的指令進行掃描和解析,根據(jù)模板指令替換數(shù)據(jù),初始化視圖以及綁定相應(yīng)的回調(diào)函數(shù);
3.實現(xiàn)一個Watcher,作為Observer和Compile的橋梁,能夠訂閱屬性變動的通知,執(zhí)行指令綁定的回調(diào)函數(shù),更新視圖
4.mvvm的入口,整合以上三者

流程圖如下:

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

分布實現(xiàn)

1. MVVM.js

function MVVM(options) {
  this.$options = options || {};
  var data = this._data = this.$options.data;
  var me = this;

  // 數(shù)據(jù)代理
  // 實現(xiàn) vm.xxx -> vm._data.xxx
  Object.keys(data).forEach(function(key) {
    me._proxyData(key);
  });
  // 代理計算屬性
  // 同樣通過Object.defineProperty進行劫持
  this._initComputed();

  observe(data, this);

  this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
  $watch: function(key, cb, options) {
    new Watcher(this, key, cb);
  }
}

MVVM入口文件,整合Observer/Compile/Watcher三者,達到數(shù)據(jù)變化->更新視圖;視圖變化->數(shù)據(jù)變更的雙向綁定效果。(結(jié)合鉤子函數(shù),理解Vue生命周期中各個階段的作用)

2. Observer.js

function Observer(data) {
  Object.keys(data).forEach(function() {
    defineReactive(data, key, data[key]);
  });
}
function defineReactive (data, key, val) {
  var dep = new Dep();
  var childObj = observe(val);

  Object.defineProperty(data, key, {
    enumerable: true, // 可枚舉
    configurable: false, // 不能再define
    get: function() {
      if (Dep.target) {
        dep.depend();
      }
      return val;
    },
    set: function(newVal) {
      if (newVal === val) {
        return;
      }
      val = newVal;
      // 新的值是object的話,進行監(jiān)聽
      childObj = observe(newVal);
      // 通知訂閱者
      dep.notify();
    }
  });
}

對需要監(jiān)測的對象的每個屬性進行遞歸遍歷,通過Object.defineProperty設(shè)置setter和getter。當設(shè)置新的屬性值時,觸發(fā)相應(yīng)的setter,通知訂閱者。

function Dep() {
  this.id = uid++;
  this.subs = [];
}
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  depend: function() {
    Dep.target.addDep(this);
  },
  notify: function() {
    this.subs.forEach(function(sub) {
      sub.update();
    });
  }
};

訂閱者模式,每個屬性維護一個Dep,記錄自己的訂閱者(即watcher),notify通知每個訂閱者執(zhí)行相應(yīng)的update方法,更新視圖。

3. Compile.js

Compile做了兩件事情:

1.解析模板指令,替換變量,初始化渲染視圖;
2.生成一個watcher,注冊回調(diào)函數(shù),添加監(jiān)聽數(shù)據(jù)的訂閱者,數(shù)據(jù)變動時,更新視圖

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

解析流程如下:

1.將DOM轉(zhuǎn)成文檔碎片fragment,提升查詢效率
2.遍歷所有元素節(jié)點及其子節(jié)點,調(diào)用對應(yīng)的指令渲染函數(shù)渲染,并調(diào)用對應(yīng)的指令更新函數(shù)進行綁定
3.將fragment添加回真實的DOM中

遍歷元素

function compileElement (el) {
  var childNodes = el.childNodes,
    me = this;
  [].slice.call(childNodes).forEach(function(node) {
    var text = node.textContent;
    var reg = /\{\{(.*)\}\}/;
    // 解析元素節(jié)點
    if (me.isElementNode(node)) {
      me.compile(node);
    // {{}}替換變量
    } else if (me.isTextNode(node) && reg.test(text)) {
      me.compileText(node, RegExp.$1);
    }
    // 遞歸遍歷子節(jié)點
    if (node.childNodes && node.childNodes.length) {
      me.compileElement(node);
    }
  });
}

編譯元素節(jié)點

compile: function(node) {
  var nodeAttrs = node.attributes,
    me = this;
  [].slice.call(nodeAttrs).forEach(function(attr) {
    // 指令以v-xxx命名
    // <span v-html="content"></span>
    var attrName = attr.name; // v-html
    if (me.isDirective(attrName)) {
      var exp = attr.value; // content
      var dir = attrName.substring(2);
      // 事件指令
      if (me.isEventDirective(dir)) {
        compileUtil.eventHandler(node, me.$vm, exp, dir);
        // 普通指令
      } else {
        compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
      }
      node.removeAttribute(attrName);
    }
  });
}

指令處理與更新函數(shù)

var compileUtil = {
  html: function(node, vm, exp) {
    this.bind(node, vm, exp, 'html');
  },
  
  bind: function(node, vm, exp, dir) {
    var updaterFn = updater[dir + 'Updater'];
    // 第一次初始化視圖
    updaterFn && updaterFn(node, this._getVMVal(vm, exp));
    // 實例化Watcher,添加訂閱者
    new Watcher(vm, exp, function(value, oldValue) {
      // 屬性變化的視圖更新函數(shù)
      updaterFn && updaterFn(node, value, oldValue);
    });
  },
}

var Updater = {
  htmlUpdater: function(node, value) {
    node.innerHTML = typeof value == 'undefined' ? '' : value;
  }
}

4. Watcher.js

Watcher作為Observer與Compile之間通信的橋梁,屬性變化的訂閱者,做了如下的事情:

1.自身實例化時在屬性訂閱器集合dep里添加自己
2.自身需有update方法
3.調(diào)用dep.notice時,watcher調(diào)用自身的update ,觸發(fā)Compile中定義的回調(diào)

function Watcher(vm, expOrFn, cb) {
  this.cb = cb;
  this.vm = vm;
  this.expOrFn = expOrFn;
  this.value = this.get();
}

Watcher.prototype = {
  update: function() {
    this.run();
  },
  run: function() {
    var value = this.get();
    var oldVal = this.value;
    if (value !== oldVal) {
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  },
  get: function() {
    Dep.target = this;
    var value = this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
  }
};

這里需要注意的點是,實例化watcher的時候,調(diào)用get方法,通過Dep.target = curInstance,強行觸發(fā)獲屬性值的getter方法,在屬性的訂閱器中添加當前watcher實例。

小結(jié)

雙向綁定的原理很簡單,通過數(shù)據(jù)劫持,當設(shè)置新屬性值的時候通過訂閱者更新視圖;編譯指令,替換變量,同時綁定更新函數(shù)到訂閱者;對應(yīng)事件綁定調(diào)用addEventListener進行監(jiān)聽。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

本文名稱:詳解Vue雙向數(shù)據(jù)綁定原理解析
網(wǎng)頁鏈接:http://muchs.cn/article28/gdiicp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、定制網(wǎng)站網(wǎng)站導航、標簽優(yōu)化服務(wù)器托管、用戶體驗

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)