vue源碼學習

1、直接用 <script> 引入vue.jshtml

直接下載並用 <script> 標籤引入,Vue 會被註冊爲一個全局變量。vue

<div id="app"></div>
<script src="vue.js"></script>
<script>
  var vm = new Vue({
    el: '#app',
    data: {},
    components: {},
    created: function() {},
    methods: {
      xxx: function() {
      }
    }
  })
</script>
// vue.js
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.Vue = factory());
}(this, function () { 'use strict';
  function Vue (options) {
    this._init(options);
  }
  return Vue;
}));

在此處瀏覽器上下文中this即爲window對象,此處是將Vue方法(構造函數)暴露爲一個全局方法。node

el:提供一個在頁面上已存在的 DOM 元素做爲 Vue 實例的掛載目標。若是在實例化時存在這個選項,實例將當即進入編譯過程,不然,須要顯式調用 vm.$mount()手動開啓編譯。若是 Vue 實例在實例化時沒有收到 el 選項,則它處於「未掛載」狀態,沒有關聯的 DOM 元素。可使用 vm.$mount() 手動地掛載一個未掛載的實例react

Vue.prototype._init = function (options) {
  var vm = this;
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);
  callHook(vm, 'beforeCreate');
  initInjections(vm); // resolve injections before data/props
  initState(vm);
  initProvide(vm); // resolve provide after data/props
  callHook(vm, 'created');
  if (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }
};
Vue.prototype.$mount = function (el, hydrating) {}

部分生命週期截圖,參考:https://cn.vuejs.org/v2/guide/instance.html#生命週期圖示數組

// public mount method
// 2)mount.call調用該方法
Vue.prototype.$mount = function (el, hydrating) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};
var mount = Vue.prototype.$mount; // 1)初始化先執行如下$mount掛載
Vue.prototype.$mount = function (el, hydrating) {
  el = el && query(el);
  return mount.call(this, el, hydrating)
};

mountComponent瀏覽器

function mountComponent (vm, el, hydrating) {
  vm.$el = el;
  callHook(vm, 'beforeMount');
  var updateComponent;
  if (config.performance && mark) {
  } else {
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
  }
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
}

vm._render()app

Vue 選項中的 render 函數若存在,則 Vue 構造函數不會從 template 選項或經過 el選項指定的掛載元素中提取出的 HTML 模板編譯渲染函數dom

<div id="app">
  <p>message: {{message}}</p>
</div>
<script src="vue.js"></script>
<script>
  var vm = new Vue({
    el: '#app',
    data: {
      message: 'Hello Vue!',
      blogTitle: 'render'
    },
    render: function(createElement) { return createElement('h1', this.blogTitle) }
  })
</script>

vue.js設置了默認的render函數,若是Vue 選項中的 render 函數存在,則執行用戶自定義的render函數。ide

Vue 經過創建一個虛擬 DOM 來追蹤本身要如何改變真實 DOMreturn createElement('h1', this.blogTitle)。createElement 到底會返回什麼呢?其實不是一個實際的 DOM 元素。它更準確的名字多是 createNodeDescription,由於它所包含的信息會告訴 Vue 頁面上須要渲染什麼樣的節點,包括及其子節點的描述信息。咱們把這樣的節點描述爲「虛擬節點 (virtual node)」,也常簡寫它爲「VNode」。「虛擬 DOM」是咱們對由 Vue 組件樹創建起來的整個 VNode 樹的稱呼函數

參考:https://cn.vuejs.org/v2/guide/render-function.html#虛擬-DOM

Vue.prototype._render = function () {
  var vm = this;
  var ref = vm.$options;
  var render = ref.render;
  var vnode;
  vnode = render.call(vm._renderProxy, vm.$createElement);
  return vnode
};
Vue.prototype._update = function (vnode, hydrating) {
  var vm = this;
  var prevVnode = vm._vnode;
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
};

patch操做dom渲染頁面

Vue.prototype.__patch__ = inBrowser ? patch : noop;
function patch (oldVnode, vnode, hydrating, removeOnly) {
  var isInitialPatch = false;
  var insertedVnodeQueue = [];
  // replacing existing element
  var oldElm = oldVnode.elm;
  var parentElm = nodeOps.parentNode(oldElm);
  // create new node
 createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ); // destroy old node
  if (isDef(parentElm)) {
    removeVnodes(parentElm, [oldVnode], 0, 0);
  }
  return vnode.elm
}

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
  var data = vnode.data;
  var children = vnode.children;
  var tag = vnode.tag; // 示例中tag爲'h1'
  if (isDef(tag)) {
    vnode.elm = vnode.ns
      ? nodeOps.createElementNS(vnode.ns, tag)
      : nodeOps.createElement(tag, vnode);
    createChildren(vnode, children, insertedVnodeQueue); // 將h1的子節點元素插入h1中
    insert(parentElm, vnode.elm, refElm); // 將h1插入父元素body中
  }
}

此例執行insert後頁面渲染以下(底層是使用insertBefore這些原生js方法操做dom)

    

 

當使用新數組替換舊數組時(以下),vue處理流程分析

<div id="app">
    <p v-for="item in arr">{{item.name}}</p>
    <button type="button" @click="testConcat">測試concat</button>
</div>
<script src="vue.js"></script>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            arr: [{name: 'cl'}]
        },
        methods: {
            testConcat: function() {
                var newArr = [{name: 'lalala'}]
                this.arr = this.arr.concat(newArr)
            }
        }
    })
</script>

參考:https://cn.vuejs.org/v2/guide/list.html#數組更新檢測

 變異方法,顧名思義,會改變調用了這些方法的原始數組。相比之下,也有非變異 (non-mutating method) 方法,例如 filter()concat() 和 slice() 。它們不會改變原始數組,而老是返回一個新數組。當使用非變異方法時,能夠用新數組替換舊數組。你可能認爲這將致使 Vue 丟棄現有 DOM 並從新渲染整個列表。幸運的是,事實並不是如此。Vue 爲了使得 DOM 元素獲得最大範圍的重用而實現了一些智能的啓發式方法,因此用一個含有相同元素的數組去替換原來的數組是很是高效的操做。

function defineReactive$$1 (obj, key, val, customSetter, shallow) {
  var dep = new Dep();
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    set: function reactiveSetter (newVal) {
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}

Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。 

  

 

須要對this._data.arr操做纔會觸發set。爲了偷懶,咱們須要一種方便的方法經過this.arr直接設置就能觸發set對視圖進行重繪。那麼就須要用到代理。這樣咱們就把data上面的屬性代理到了vm實例上。

 初始化時調用observe這個函數將Vue的數據設置成observable的

 new Vue -> this._init(options); -> initState -> ... -> observe -> new Observer -> defineReactive$$1

function initState (vm) {
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
}
function initData (vm) {
  var data = vm.$options.data;
  var keys = Object.keys(data);
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    proxy(vm, "_data", key);
  }
  observe(data, true /* asRootData */);
}
// 代理:把data上面的屬性代理到了vm實例上
var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};
function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]; // sourceKey即爲_data
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val; // sourceKey即爲_data
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
// 將Vue的數據設置成observable的
function observe (value, asRootData) {
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (...) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

初始化渲染1:new Vue -> this._init(options); -> vm.$mount -> mountComponent

// 二、再執行該mount方法
Vue.prototype.$mount = function (el, hydrating) {
  return mountComponent(this, el, hydrating)
};
var mount = Vue.prototype.$mount;
// 一、先執行該mount方法
Vue.prototype.$mount = function (el, hydrating) {
  return mount.call(this, el, hydrating)
};

初始化渲染2:mountComponent -> new Watcher -> Watcher.prototype.get -> vm._update(vm._render(), hydrating)

function mountComponent (vm, el, hydrating) {
  vm.$el = el;
  callHook(vm, 'beforeMount');

  var updateComponent;
  updateComponent = function () {
    vm._update(vm._render(), hydrating);
  };
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);

  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}
var Watcher = function Watcher (vm, expOrFn, cb, options, isRenderWatcher) {
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  }
  this.get();
};
Watcher.prototype.get = function get () {
  var value;
  var vm = this.vm;
  value = this.getter.call(vm, vm);   return value
};

this.arr = this.arr.concat(newArr) -> dep.notify() -> Dep.prototype.notify -> Watcher.prototype.update -> Watcher.prototype.run -> Watcher.prototype.get -> vm._update(vm._render(), hydrating)

【Dep就是一個發佈者,能夠訂閱多個觀察者,依賴收集以後Deps中會存在一個或多個Watcher對象,在數據變動的時候通知全部的Watcher。】

Vue.prototype._update = function (vnode, hydrating) {
  var vm = this;
  var prevVnode = vm._vnode;
  if (!prevVnode) { //  初始化
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else { // 更新
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
};

patch渲染頁面

return function patch (oldVnode, vnode, hydrating, removeOnly) {
  var insertedVnodeQueue = [];
  if (isUndef(oldVnode)) {
  } else {
    var isRealElement = isDef(oldVnode.nodeType);
    if (!isRealElement && sameVnode(oldVnode, vnode)) { // 更新
      // patch existing root node
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
    } else { // 初始化
      if (isRealElement) {
        oldVnode = emptyNodeAt(oldVnode);
      }
      // replacing existing element
      var oldElm = oldVnode.elm;
      var parentElm = nodeOps.parentNode(oldElm);
      // create new node
      createElm(
        vnode,
        insertedVnodeQueue,
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(oldElm)
      );
      // destroy old node
      if (isDef(parentElm)) {
        removeVnodes(parentElm, [oldVnode], 0, 0);
      }
    }
  }
  return vnode.elm
}

 patchVnode更新

function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
  var elm = vnode.elm = oldVnode.elm;
  var data = vnode.data;
  var oldCh = oldVnode.children;
  var ch = vnode.children;
  if (isDef(data) && isPatchable(vnode)) {
    for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
  }
  if (isUndef(vnode.text)) {
    if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
    }
  } else if (oldVnode.text !== vnode.text) {
    nodeOps.setTextContent(elm, vnode.text);
  }
}
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  var oldStartIdx = 0;
  var newStartIdx = 0;
  var oldEndIdx = oldCh.length - 1;
  var oldStartVnode = oldCh[0];
  var oldEndVnode = oldCh[oldEndIdx];
  var newEndIdx = newCh.length - 1;
  var newStartVnode = newCh[0];
  var newEndVnode = newCh[newEndIdx];
  var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
  // 比較結點
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {   if (isUndef(oldStartVnode)) {
      oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
    } else if (isUndef(oldEndVnode)) {
      oldEndVnode = oldCh[--oldEndIdx];
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
      oldStartVnode = oldCh[++oldStartIdx];
      newStartVnode = newCh[++newStartIdx];
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
      oldEndVnode = oldCh[--oldEndIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
      patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
      canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
      oldStartVnode = oldCh[++oldStartIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
      patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
      canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
      oldEndVnode = oldCh[--oldEndIdx];
      newStartVnode = newCh[++newStartIdx];
    } else {
      if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
      idxInOld = isDef(newStartVnode.key)
        ? oldKeyToIdx[newStartVnode.key]
        : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
      if (isUndef(idxInOld)) { // New element
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
      } else {
        vnodeToMove = oldCh[idxInOld];
        if (sameVnode(vnodeToMove, newStartVnode)) {
          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
          oldCh[idxInOld] = undefined;
          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
        } else {
          // same key but different element. treat as new element
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
        }
      }
      newStartVnode = newCh[++newStartIdx];
    }
  }
  if (oldStartIdx > oldEndIdx) {
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
  }
}
相關文章
相關標籤/搜索