vue源碼的淺解析

vue是當下前端比較火的框架,中文地址:vue中文文檔

翻看源碼: 在其工程目錄src下發現以下文件夾:前端

1. /compiler   目錄是編譯模板;
2. /core       目錄是vue.js的核心
3. /planforms  目錄是針對核心模塊的‘平臺’模塊 (web   weex)
4. /server     目錄是處理服務端渲染;
5. /sfc        目錄是處理單文件.vue;
6. /shared     目錄是提供全局用到的工具函數         
複製代碼

vue.js的組成是有core + 對應的‘平臺’補充代碼構成(獨立構建和運行時構建只是platforms下web平臺的兩種選擇)vue

vue的雙向數據綁定

雙向綁定(響應式原理)所涉及到的技術node

1. Object.defineProperty
2. Observer
3. Watcher
4. Dep
5. Directive
複製代碼

1. Object.definePropertygit

var obj = {};
var a;
Object.defineProperty(obj,'a',{
  get: function(){
    console.log('get val');
    return a;
  },
  set: function(newVal){
    console.log('set val:' + newVal);
    a = newVal;
  }
});
obj.a // get val;
obj.a = '111'; // set val:111
複製代碼

雙向數據綁定github

2. Observerweb

觀察者模式是軟件設計模式的一種。
在此種模式中,一個目標對象管理全部相依於它的觀察者對象,而且在它自己的狀態改變時主動發出通知。
這一般透過呼叫各觀察者所提供的 方法來實現。此種模式一般被用來實時事件處理系統。           
訂閱者模式涉及三個對象:發佈者、主題對象、訂閱者,三個對象間的是一對多的關係, 每當主題對象狀態發生改變時,其相關依賴對象都會獲得通知,並被自動更新。
例如:
複製代碼

vue裏邊怎麼操做的呢?設計模式

3. watcher瀏覽器

4. Depbash

5. Directiveweex

疑問一 vue哪來的?

function Vue(options) {
    this.data = options.data;
    var data = this.data;
    observe(data, this);
    var id = options.el;
    var dom = new Compile(document.getElementById(id), this);
    // 編譯完成後,將dom返回到app中
    document.getElementById(id).appendChild(dom);
}
複製代碼

是經過上述方法實例化的一個對象;可是裏邊有兩個未知生物 observe ? Compile?

observer.js

<!--定義 observe方法 -->
function observe(obj, vm) {
  Object.keys(obj).forEach(function (key) {
    defineReactive(vm, key, obj[key]);
  })
}
function defineReactive(obj, key, val) {

  var dep = new Dep();
  Object.defineProperty(obj, key, {
    get: function () {
      //添加訂閱者watcher到主題對象Dep
      if (Dep.target) {
        // JS的瀏覽器單線程特性,保證這個全局變量在同一時間內,只會有同一個監聽器使用
        dep.addSub(Dep.target);
      }
      return val;
    },
    set: function (newVal) {
      if (newVal === val) return;
      val = newVal;
      console.log(val);
      // 做爲發佈者發出通知
      dep.notify();
    }
  })
}

複製代碼

有oberver方法,就是初始觀察者,去遍歷它自身的屬性,而後defineReactive(定義一些反應)(key + value + vm),而後給vm設置key的value(即set和get方法); ** Dep()???**

Dep.js

function Dep() {
  this.subs = [];
}
Dep.prototype = {
  // 添加訂閱事件
  addSub: function (sub) {
    this.subs.push(sub);
  },
  // 添加發布通知事件
  notify: function () {
    this.subs.forEach(function (sub) {
      sub.update();
    })
  }
}
複製代碼

明白了,在觀察者看來,我須要有人訂閱個人消息,添加一個dep對象(主題對象);而後我給它添加訂閱,而後我做爲消息的發出者給訂閱者發消息;

compile

function Compile(node, vm) {
  if (node) {
    this.$frag = this.nodeToFragment(node, vm);
    return this.$frag;
  }
}
Compile.prototype = {
  nodeToFragment: function (node, vm) {
    var self = this;
    var frag = document.createDocumentFragment();
    var child;

    while (child = node.firstChild) {
      self.compileElement(child, vm);
      frag.append(child); // 將全部子節點添加到fragment中
    }
    return frag;
  },
  compileElement: function (node, vm) {
    var reg = /\{\{(.*)\}\}/;

    //節點類型爲元素
    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; // 獲取v-model綁定的屬性名
          node.addEventListener('input', function (e) {
            // 給相應的data屬性賦值,進而觸發該屬性的set方法
            vm[name] = e.target.value;
          });
          // node.value = vm[name]; // 將data的值賦給該node
          new Watcher(vm, node, name, 'value');
        }
      };
    }
    //節點類型爲text
    if (node.nodeType === 3) {
      if (reg.test(node.nodeValue)) {
        var name = RegExp.$1; // 獲取匹配到的字符串
        name = name.trim();
        // node.nodeValue = vm[name]; // 將data的值賦給該node
        new Watcher(vm, node, name, 'nodeValue');
      }
    }
  },
}
複製代碼

編譯,就是建立節點,而後根據節點類型,而後去進行賦值操做, watcher???

watch.js

function Watcher(vm, node, name, type) {
    Dep.target = this;
    this.name = name;
    this.node = node;
    this.vm = vm;
    this.type = type;
    this.update();
    Dep.target = null;
}

Watcher.prototype = {
    update: function() {
        this.get();
        var batcher = new Batcher();
        batcher.push(this);
        // this.node[this.type] = this.value; // 訂閱者執行相應操做
    },
    cb:function(){
        this.node[this.type] = this.value; // 訂閱者執行相應操做
    },
    // 獲取data的屬性值
    get: function() {
        this.value = this.vm[this.name]; //觸發相應屬性的get
    }
}

複製代碼

就是訂閱者模式中的訂閱者了,接收四個參數,vm(實例),node(節點),name(屬性key), type(節點類型了);作三個操做,get(獲取值),update(值),cb(保持原值); ** Batcher????**

Batcher.js

/**
 * 批處理構造函數
 * @constructor
 */
function Batcher() {
    this.reset();
}

/**
 * 批處理重置
 */
Batcher.prototype.reset = function () {
    this.has = {};
    this.queue = [];
    this.waiting = false;
};

/**
 * 將事件添加到隊列中
 * @param job {Watcher} watcher事件
 */
Batcher.prototype.push = function (job) {
    if (!this.has[job.name]) {
        this.queue.push(job);
        this.has[job.name] = job;
        if (!this.waiting) {
            this.waiting = true;
            setTimeout(() => {
                this.flush();
            });
        }
    }
};

/**
 * 執行並清空事件隊列
 */
Batcher.prototype.flush = function () {
    this.queue.forEach((job) => {
        job.cb();
    });
    this.reset();
};
複製代碼

這裏邊就是批量處理的函數了,事件隊列的相關知識了,執行三個操做

  1. reset(批處理重置)
  2. push( 將事件添加到隊列中)
  3. flush(執行並清空事件隊列) ;

如今咱們回過頭去看整個事件,串起來就是vue的實現機制了....

源碼看:github地址

未完待續……

相關文章
相關標籤/搜索