(解析)vue源碼解讀

前言

A: 爲何要作源碼解讀?
Q: 咱們新到一個環境,第一件事情就是熟悉環境熟悉項目,這個很考驗閱讀源碼的能力以及耐心。vue是個很好的庫,知名度高,對js的學習具備向上性,因此搞清楚邏輯是有好處的。
A: 閱讀源碼的程度?
Q: 咱們徹底不必從頭至尾細細品味,只須要知道一些核心的實現就行了,畢竟vue也算是個產品,咱們不必搞清楚一個產品,咱們只須要知道產品的核心就夠了,多餘的也是業務代碼。(相對來講)vue

開始

Vue應用的實例化以及掛載

main.js

new Vue({                      // 初始化vue實例
  //components: { App }        // vue1.0的寫法
  render: h => h(App)          // 最早執行,返回一個符合component的對象,vue2.0的寫法
})
.$mount('#app')                // 掛載vue實例到 ‘#app’

render函數

render: h => h(App) 
就是
render:function(h){
    return h(App)
}
即
render: function (createElement) {
    return createElement(App)
}

找到Vue引用源文件(debug順序)

import Vue from 'vue'

找到node

node-modules/vue

打開package.json 找到react

"main": "dist/vue.runtime.common.js"

"main"是 npm模塊曝光的主要文件.npm

打開vue.runtime.common.js
ctrl/command + a, ctrl/command + k, ctrl/command + 1
,快捷鍵把全部方法摺疊json

debug執行順序

你會發現 update執行了2次,我明明只初始化了一次vue實例,爲何update2次了呢?緣由在下方代表。數組

構造函數

拉到最下面,app

module.exports = Vue;

導出的是一個Vue構造函數。
當前文件搜索 Vue 找到 構造函數dom

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options); // 調用 初始化方法
}

初始化函數

var uid$3 = 0;
vm._uid = uid$3++; //每一個vue實例 擁有惟一id,從0開始 ++

// 合併初始化vue實例的參數(入參options和默認參數)
vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );

initLifecycle();// 初始化實例生命週期相關的參數
使用Object.create(null)用來獲取一個沒有原型鏈的對象類型

initEvents(); // 初始化實例事件觸發相關的參數
initRender(); // 初始化實例渲染相關的參數

//create的準備工做作好了,觸發beforeCreate的生命週期
callHook(vm, 'beforeCreate');

initState(); // 初始化實例狀態

// 狀態也初始化好了,觸發create的生命週期,因此 create和 breforCreate的區別就在 create的時候  有狀態。
callHook(vm, 'created');

至此,vue實例的初始化完成,而後掛載到節點函數

掛載實例到節點

// 將mount('#app') => query('#app') 查找到dom對象,賦值給vue.$el
// 觸發beforeMount的生命週期,因此beforeMount 和 create的區別就在  beforeMount的時候 有掛載節點。
callHook(vm, 'beforeMount');

// 拿到當前將要掛載的Vnode(虛擬dom對象)
vm._render()  vm.$vnode

// (更新)渲染頁面
vm._update(vm._render(), hydrating);
vm.__patch__(); 
createElm(); // 按照虛擬dom生成真實dom

createElm函數

// 這是一個遞歸方法,vue實例的初始化是建立一個根節點,而後再將render函數傳入的組件掛載,這就是流程圖執行2次update的緣由。
// 若是是組件,則去作 組件的初始化
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  return
}
// 已經刪減,只留主要邏輯,判斷虛擬dom 的 tag屬性
if (isDef(tag)) {
  {
    // 遍歷虛擬dom的子節點,而且建立,而後遞歸當前方法。
    createChildren(vnode, children, insertedVnodeQueue); 
    if (isDef(data)) {
      invokeCreateHooks(vnode, insertedVnodeQueue);
    }
    insert(parentElm, vnode.elm, refElm);
  }
}
// 若是沒有子節點的,直接建立dom,而後插入
 else if (isTrue(vnode.isComment)) {
  vnode.elm = nodeOps.createComment(vnode.text);
  insert(parentElm, vnode.elm, refElm);
} else {
  vnode.elm = nodeOps.createTextNode(vnode.text);
  insert(parentElm, vnode.elm, refElm);
}
// 觸發Mount的生命週期
callHook(vm, 'mounted');

至此就是 Vue根節點初始化掛載和渲染的流程.學習

Vue數據更新

首先 咱們改造下 app.vue,像官網同樣,咱們新增一個雙向綁定的文本框。
如今咱們知道了,第一次的update只是掛載了根節點,那麼咱們新增了文本框的組件實際上是在第二次init的時候初始化的。
咱們能夠着重看第二次的流程,搞清楚,數據的監聽與更新。

initState函數

在每次vue實例初始化的時候 都會執行initState,這裏面作了vue實例 數據的監聽。

function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {   // 這裏判斷vm.$options.data,從而執行initData()或者 直接 監聽 vm._data={}做爲根狀態 
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

vm.$options.data 哪裏來的呢?
是在 Vue._init方法中

if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options);
    } else {
      vm.$options = mergeOptions(     // 這個方法中  給data賦值,也就是咱們render中 的  data(){return{//咱們組件的數據}}
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }

initData函數

function initData (vm) {
  var data = vm.$options.data;
  // 把從render函數返回的data函數對象賦值給data,而後data.call(this,this),也就是vm.data();拿到data返回值
  data = vm._data = typeof data === 'function'      
    ? getData(data, vm)
    : data || {};
  // observe data
  observe(data, true /* asRootData */);
}
function getData (data, vm) {
  pushTarget();
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, "data()");
    return {}
  } finally {
    popTarget();
  }
}

拿到data返回值

function observe (value, asRootData) {
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);   // 返回一個 新建的 Observer
  }
  return ob
}

Observer對象(監聽者)

var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep(); // 這裏的dep 稍後會說
  this.vmCount = 0;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value); 
  } else {
    this.walk(value);
  }
};
// 若是是對象,則按照key來劫持
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
};

// 若是是數組,就遍歷每一個數組元素,再每一個元素再判斷是否爲數組,對象
Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

數據劫持

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep(); // 這裏的dep 稍後會說
    
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  if (!getter && arguments.length === 2) {
    val = obj[key];
  }
  var setter = property && property.set;

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend(); // 這裏的dep 稍後會說
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify(); // 這裏的dep 稍後會說
    }
  });
}

依賴收集

咱們在使用vue的時候,data方法會返回對象,包含了全部咱們想要觀察的數據屬性,一樣 vue也會幫咱們監聽這些屬性的變化,可是,假如咱們在data中設置了多個屬性,可是在模板中只使用了1個,又會如何呢?咱們在腳本中設置value2的值(this.value3= 'hello world'),那麼vue監聽到變化 還會去通知模板從新渲染麼?

new Vue({
    template: 
        `<div>
            <span>value1:</span> {{value1}}
        <div>`,
    data: {
        value1: 'value1',
        value2: 'value2',
        value3: 'value3',
        ...
    }
});

天然是不會的,vue很聰明的使用了依賴收集
Dep : 一個訂閱者的容器,能夠增長或刪除訂閱者,能夠向訂閱者發送消息;
Watcher : 訂閱者類。它在初始化時能夠接受getter, callback兩個函數做爲參數。getter用來計算Watcher對象的值。當Watcher被觸發時,會從新經過getter計算當前Watcher的值,若是值改變,則會執行callback.

Dep(訂閱者容器)

Watcher對象(訂閱者)

小結

未完待續。。。

相關文章
相關標籤/搜索