史上最全的vue.js源碼解析(四)

雖然vue3已經(jīng)出來很久了花沉,但我覺得vue.js的源碼還是非常值得去學(xué)習(xí)一下柳爽。vue.js里面封裝的很多工具類在我們平時工作項(xiàng)目中也會經(jīng)常用到。所以我近期會對vue.js的源碼進(jìn)行解讀主穗,分享值得去學(xué)習(xí)的代碼片段,這篇文章將會持續(xù)更新毙芜。

一忽媒、4000~6200代碼有哪些內(nèi)容?:

1.initState:初始化實(shí)例狀態(tài)(initProps腋粥,initData晦雨,initComputed,initMethods隘冲,initWatch闹瞧,initMixin等)
2.initAssetRegisters:創(chuàng)建組件、指令展辞、過濾器
3.initGlobalAPI:初始化全局api
4.renderClass,mergeClassData,stringifyClass:對 class 的轉(zhuǎn)碼奥邮、合并和其他二次封裝的工具函數(shù)
5.ref的注冊和實(shí)現(xiàn)原理(ref是給元素或者子組件注冊引用信息的)

二.4000~6200行代碼的重點(diǎn):

1.Watcher(重點(diǎn))
在 Watcher 的原型鏈上定義了get、addDep罗珍、cleanupDeps洽腺、update、run覆旱、evaluate蘸朋、depend、teardown 方法扣唱,進(jìn)行新增依賴藕坯、清除、更新視圖等操作噪沙。
每個Vue組件都有一個對應(yīng)的watcher炼彪,這個watcher將會在組件render的時候收集組件所依賴的數(shù)據(jù),并在依賴有更新的時候正歼,觸發(fā)組件重新渲染
2.proxy代理
通過 vm 對象(即this)來代理 data 對象中所有屬性的操作霹购,更方便的操作 data 中的數(shù)據(jù)
通過 Object.defineProperty()給 vm 添加與 data 對象的屬性對應(yīng)的屬性描述符,所有添加的屬性都包含 getter/setter朋腋,getter/setter 內(nèi)部去操作 data 中對應(yīng)的屬性數(shù)據(jù)
3齐疙、keep-alive的 緩存原理
keep-alive是 vue 內(nèi)置組件膜楷,在源碼定義中,也具有自己的組件選項(xiàng)如 data 贞奋、 method 赌厅、 computed 、 props 等轿塔,具有抽象組件標(biāo)識 abstract特愿,通常會與動態(tài)組件一同使用,包裹動態(tài)組件時勾缭,會緩存不活動的組件實(shí)例揍障,將它們停用,而不是銷毀它們俩由;緩存的組件會觸發(fā) activated 或 deactivated 生命周期鉤子毒嫡;會緩存組件實(shí)例的 vnode 對象 ,和真實(shí) dom 節(jié)點(diǎn)幻梯,所以會有 max 屬性設(shè)置兜畸;不會在函數(shù)式組件中正常工作,因?yàn)樗鼈儧]有緩存實(shí)例
4碘梢、VNode的diff與更新
銷毀一個DOM節(jié)點(diǎn)并創(chuàng)建一個新的再插入是消耗非常大的咬摇,無論是DOM對象本身的復(fù)雜性還是操作引起的重繪重排,所以虛擬DOM的目標(biāo)是盡可能復(fù)用現(xiàn)有DOM進(jìn)行更新

三煞躬、4000~6200行的代碼解讀:

  var activeInstance = null;
  var isUpdatingChildComponent = false;
  //設(shè)置當(dāng)前的Vue實(shí)例 其返還結(jié)果為恢復(fù)上一個Vue實(shí)例,類似棧的思想來進(jìn)行樹狀結(jié)構(gòu)的構(gòu)建
  function setActiveInstance(vm) {
    var prevActiveInstance = activeInstance;
    activeInstance = vm;
    return function () {
      activeInstance = prevActiveInstance;
    }
  }
  //方法主要用來初始化生命周期相關(guān)的屬性肛鹏,以及為parent,child屬性賦值
  function initLifecycle (vm) {
    // 定義 options,它是 vm.$options 的引用恩沛,后面的代碼使用的都是 options 常量
    var options = vm.$options;
    // 查找第一個非抽象父級
    // 定義 parent龄坪,它引用當(dāng)前實(shí)例的父實(shí)例
    var parent = options.parent;
    // 如果當(dāng)前實(shí)例有父組件,且當(dāng)前實(shí)例不是抽象的
    if (parent && !options.abstract) {
      //循環(huán)查找第一個非抽象的父組件
      while (parent.$options.abstract && parent.$parent) {
        parent = parent.$parent;
      }
      // 經(jīng)過上面的 while 循環(huán)后复唤,parent 應(yīng)該是一個非抽象的組件健田,將它作為當(dāng)前實(shí)例的父級,
      // 所以將當(dāng)前實(shí)例 vm 添加到父級的 $children 屬性里
      parent.$children.push(vm);
    }
    // 設(shè)置當(dāng)前實(shí)例的 $parent 屬性佛纫,指向父級
    vm.$parent = parent;
    // 設(shè)置 $root 屬性妓局,有父級就是用父級的 $root,否則 $root 指向自身
    vm.$root = parent ? parent.$root : vm;
    // 設(shè)置當(dāng)前實(shí)例的 $children 屬性為空數(shù)組
    vm.$children = [];
    // 設(shè)置當(dāng)前實(shí)例的 $ref 屬性為空對象
    vm.$refs = {};
    // 設(shè)置當(dāng)前實(shí)例的 _watcher 屬性為null
    vm._watcher = null;
    vm._inactive = null;
    vm._directInactive = false;
    vm._isMounted = false;
    vm._isDestroyed = false;
    vm._isBeingDestroyed = false;
  }
   //混入生命周期相關(guān)屬性和方法
  function lifecycleMixin (Vue) {
    Vue.prototype._update = function (vnode, hydrating) {
      // 存儲數(shù)據(jù)做以后update用
      var vm = this;
      var prevEl = vm.$el;
      var prevVnode = vm._vnode;
      var restoreActiveInstance = setActiveInstance(vm);
      vm._vnode = vnode;
      //第一次渲染會調(diào)用vm.__patch__方法
      if (!prevVnode) {
        // 初始渲染
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
      } else {
        // 更新
        vm.$el = vm.__patch__(prevVnode, vnode);
      }
      restoreActiveInstance();
      // 更新參考
      if (prevEl) {
        prevEl.__vue__ = null;
      }
      if (vm.$el) {
        vm.$el.__vue__ = vm;
      }
      // 如果parent是HOC呈宇,則也更新其$el
      if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
        vm.$parent.$el = vm.$el;
      }
      // 調(diào)度程序調(diào)用更新的掛鉤好爬,以確保在父級的更新掛鉤中更新子級
    };
    // 強(qiáng)制刷新的作用
    //迫使Vue實(shí)例重新渲染,注意它僅僅影響實(shí)例本身喝插入插槽內(nèi)容的子組件甥啄,而不是所有的子組件
    Vue.prototype.$forceUpdate = function () {
      var vm = this;
      if (vm._watcher) {
        vm._watcher.update();
      }
    };
    // $destroy是組件內(nèi)部銷毀自己存炮。
    // 清理它與其它實(shí)例的連接,解綁它的全部指令及事件監(jiān)聽器,
    // 斷掉虛擬dom和真實(shí)dom之間的聯(lián)系,并沒有真正地回收這個vue實(shí)例
    Vue.prototype.$destroy = function () {
      var vm = this;
      if (vm._isBeingDestroyed) {
        return
      }
      //回調(diào)觸發(fā)beforeDestroy鉤子函數(shù)
      callHook(vm, 'beforeDestroy');
      vm._isBeingDestroyed = true;
      //從父對象中刪除vm
      var parent = vm.$parent;
      if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
        remove(parent.$children, vm);
      }
      //拆卸觀察者穆桂,調(diào)用teardown方法移出watcher
      if (vm._watcher) {
        vm._watcher.teardown();
      }
      var i = vm._watchers.length;
      while (i--) {
        vm._watchers[i].teardown();
      }
      // 從數(shù)據(jù)中刪除引用對象
      // 可能沒有觀察者
      if (vm._data.__ob__) {
        vm._data.__ob__.vmCount--;
      }
      // call the last hook...
      vm._isDestroyed = true;
      //在當(dāng)前渲染樹上調(diào)用銷毀掛鉤
      vm.__patch__(vm._vnode, null);
      //回調(diào)觸發(fā)destroyed鉤子函數(shù)
      callHook(vm, 'destroyed');
      //關(guān)閉所有實(shí)例的偵聽器
      vm.$off();
      if (vm.$el) {
        vm.$el.__vue__ = null;
      }
      if (vm.$vnode) {
        vm.$vnode.parent = null;
      }
    };
  }
  // $mount函數(shù)會調(diào)用mountComponent方法
  function mountComponent (
    vm,
    el,//當(dāng)前掛載的元素
    hydrating //和服務(wù)端渲染相關(guān)
  ) {
    vm.$el = el;
     // 如果沒有!vm.$options.render方法宫盔,就創(chuàng)建一個空的VNODE,
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode;
      {
        if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
          vm.$options.el || el) {
          warn(
            'You are using the runtime-only build of Vue where the template ' +
            'compiler is not available. Either pre-compile the templates into ' +
            'render functions, or use the compiler-included build.',
            vm
          );
        } else {
          warn(
            'Failed to mount component: template or render function not defined.',
            vm
          );
        }
      }
    }
    //回調(diào)觸發(fā)beforeMount鉤子函數(shù)
    callHook(vm, 'beforeMount');

    var updateComponent;
    /* istanbul ignore if */
    // 如果config中配置了performance(記錄性能)
    if (config.performance && mark) {
      updateComponent = function () {
        var name = vm._name;
        var id = vm._uid;
        var startTag = "vue-perf-start:" + id;
        var endTag = "vue-perf-end:" + id;
        //開始標(biāo)記
        mark(startTag);
        var vnode = vm._render();
         //開始結(jié)束
        mark(endTag);
        // measure方法是在執(zhí)行了Performance的measure方法后享完,把開始結(jié)束兩個mark值刪除
        measure(("vue " + name + " render"), startTag, endTag);

        mark(startTag);
        vm._update(vnode, hydrating);
        mark(endTag);
        measure(("vue " + name + " patch"), startTag, endTag);
      };
    } else {
      updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };
    }

    //將其設(shè)置為vm._watcher在watcher的構(gòu)造函數(shù)中
    //因?yàn)橛^察者的初始補(bǔ)丁可能會調(diào)用$forceUpdate(例如灼芭,在child內(nèi)部組件的掛載鉤子),它依賴于已定義的vm.\u監(jiān)視程序
    new Watcher(vm, updateComponent, noop, {
      // vm._isMounted 為 true,表示這個實(shí)例已經(jīng)掛載了般又,執(zhí)行 beforeUpdate 鉤子函數(shù)
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true);
    hydrating = false;

    //手動裝入實(shí)例彼绷,調(diào)用自行裝入
    //在插入的鉤子中為渲染創(chuàng)建的子組件調(diào)用mounted
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
  }
    //對占位符 vm.$vnode 的更新、slot的更新茴迁,listeners 的更新寄悯,props 的更新等
  function updateChildComponent (
    vm,
    propsData,
    listeners,
    parentVnode,
    renderChildren
  ) {
    {
      isUpdatingChildComponent = true;
    }

    // 確定組件是否具有插槽子級
    var newScopedSlots = parentVnode.data.scopedSlots;
    var oldScopedSlots = vm.$scopedSlots;
    var hasDynamicScopedSlot = !!(
      (newScopedSlots && !newScopedSlots.$stable) ||
      (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
      (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
      (!newScopedSlots && vm.$scopedSlots.$key)
    );

    // 來自父級的任何靜態(tài)插槽子級在父級的更新。動態(tài)作用域插槽也可能已更改堕义。
    // 在這種情況下猜旬,需要強(qiáng)制更新以確保正確性
    var needsForceUpdate = !!(
      renderChildren ||               // 有一個新的靜態(tài)插槽
      vm.$options._renderChildren ||  // 有一個舊的靜態(tài)插槽
      hasDynamicScopedSlot
    );

    vm.$options._parentVnode = parentVnode;
    // 更新vm的占位符節(jié)點(diǎn)而不重新渲染
    vm.$vnode = parentVnode;
    // 更新子節(jié)點(diǎn)的父節(jié)點(diǎn)
    if (vm._vnode) { 
      vm._vnode.parent = parentVnode;
    }
    vm.$options._renderChildren = renderChildren;

    // 更新$attrs和$listeners散列它們也是被動的,因此如果子對象在渲染期間使用它們胳螟,
    // 它們可能會觸發(fā)子對象更新
    vm.$attrs = parentVnode.data.attrs || emptyObject;
    vm.$listeners = listeners || emptyObject;

    // 更新props
    if (propsData && vm.$options.props) {
      //禁止掉根組件 props的依賴收集
      toggleObserving(false);
      var props = vm._props;
      var propKeys = vm.$options._propKeys || [];
      for (var i = 0; i < propKeys.length; i++) {
        var key = propKeys[i];
        var propOptions = vm.$options.props;
        //校驗(yàn)props
        props[key] = validateProp(key, propOptions, propsData, vm);
      }
      toggleObserving(true);
      // 保留一份原始的propsData
      vm.$options.propsData = propsData;
    }

    // 更新listeners
    listeners = listeners || emptyObject;
    var oldListeners = vm.$options._parentListeners;
    vm.$options._parentListeners = listeners;
    updateComponentListeners(vm, listeners, oldListeners);

    // 解決插槽和強(qiáng)制更新
    if (needsForceUpdate) {
      vm.$slots = resolveSlots(renderChildren, parentVnode.context);
      vm.$forceUpdate();
    }

    {
      isUpdatingChildComponent = false;
    }
  }
  //判斷是否直接激活
  function isInInactiveTree (vm) {
    while (vm && (vm = vm.$parent)) {
      if (vm._inactive) { return true }
    }
    return false
  }
  // 激活子組件
  function activateChildComponent (vm, direct) {
    if (direct) {
      vm._directInactive = false;
      //判斷是否直接激活
      if (isInInactiveTree(vm)) {
        return
      }
    } else if (vm._directInactive) {
      return
    }
    if (vm._inactive || vm._inactive === null) {
      vm._inactive = false;
      // 循環(huán)激活 vm.$children
      for (var i = 0; i < vm.$children.length; i++) {
        activateChildComponent(vm.$children[i]);
      }
      // 調(diào)用 callHook(vm, 'activated')
      callHook(vm, 'activated');
    }
  }
  // 不激活組件
  function deactivateChildComponent (vm, direct) {
    if (direct) {
      vm._directInactive = true;
      if (isInInactiveTree(vm)) {
        return
      }
    }
    // 判斷是否是直接不激活
    if (!vm._inactive) {
      vm._inactive = true;
      // 循環(huán)不激活 vm.$children
      for (var i = 0; i < vm.$children.length; i++) {
        deactivateChildComponent(vm.$children[i]);
      }
      // 調(diào)用 callHook(vm, "deactivated")
      callHook(vm, 'deactivated');
    }
  }
  // 先入棧操作昔馋,拿到 options.hook筹吐,處理錯誤問題糖耸,vm.$emit('hook:' + hook),出棧操作
  function callHook (vm, hook) {
    // 在調(diào)用生命周期鉤子時禁用dep收集
    pushTarget();
    var handlers = vm.$options[hook];
    var info = hook + " hook";
    if (handlers) {
      for (var i = 0, j = handlers.length; i < j; i++) {
        invokeWithErrorHandling(handlers[i], vm, null, vm, info);
      }
    }
    if (vm._hasHookEvent) {
      vm.$emit('hook:' + hook);
    }
    popTarget();
  }

  /*  */

  var MAX_UPDATE_COUNT = 100;

  var queue = [];
  var activatedChildren = [];
  var has = {};
  var circular = {};
  var waiting = false;
  var flushing = false;
  var index = 0;

  /**
   * 重置scheduler的狀態(tài).
   */
  function resetSchedulerState () {
    index = queue.length = activatedChildren.length = 0;
    has = {};
    {
      circular = {};
    }
    waiting = flushing = false;
  }

  // 需要在附加事件監(jiān)聽器時保存時間戳丘薛。但是嘉竟,調(diào)用performance.now()會帶來性能開銷,
  // 尤其是當(dāng)頁面有數(shù)千個事件監(jiān)聽器時洋侨。相反舍扰,我們在調(diào)度程序每次刷新時獲取一個時間戳,并將其用于刷新期間附加的所有事件監(jiān)聽器希坚。
  var currentFlushTimestamp = 0;

  // 異步邊緣情況修復(fù)需要存儲事件偵聽器的附加時間戳
  var getNow = Date.now;

  // 確定瀏覽器正在使用的事件時間戳边苹。惱人的是,時間戳可以是高分辨率(相對于頁面加載)裁僧,
  // 也可以是低分辨率(相對于UNIX epoch)个束,所以為了比較時間,
  // 我們必須在保存刷新時間戳?xí)r使用相同的時間戳類型聊疲。所有IE版本都使用低分辨率的事件時間戳茬底,并且時鐘實(shí)現(xiàn)存在問題
  if (inBrowser && !isIE) {
    var performance = window.performance;
    if (
      performance &&
      typeof performance.now === 'function' &&
      getNow() > document.createEvent('Event').timeStamp
    ) {
      // 如果事件時間戳(盡管在Date.now()之后計算)小于它,則意味著事件使用的是高分辨率時間戳获洲,
      // 我們也需要為事件偵聽器的時間戳使用高分辨率版本阱表。
      getNow = function () { return performance.now(); };
    }
  }

  /**
   * 清空兩個隊(duì)列并運(yùn)行監(jiān)視器。
   * 據(jù)變化最終會把flushSchedulerQueue傳入到nextTick中執(zhí)行
   * 遍歷執(zhí)行watcher.run()方法,watcher.run()方法最終會完成視圖更新
   */
  function flushSchedulerQueue () {
    currentFlushTimestamp = getNow();
    flushing = true;
    var watcher, id;

    //在刷新之前對隊(duì)列進(jìn)行排序最爬。為了確保:
    // 1涉馁。組件從父組件更新到子組件。(因?yàn)楦改缚偸?    //在child之前創(chuàng)建
    // 2烂叔。組件的用戶監(jiān)視器在它的呈現(xiàn)監(jiān)視器之前運(yùn)行(因?yàn)?    //用戶觀察者在渲染觀察者之前創(chuàng)建)
    // 3谨胞。如果一個組件在父組件的監(jiān)視程序運(yùn)行期間被銷毀,
    //它的觀察者可以被跳過蒜鸡。
    queue.sort(function (a, b) { return a.id - b.id; });

    //不要緩存長度胯努,因?yàn)榭赡軙懈嗟谋O(jiān)視器被推送
    //我們運(yùn)行現(xiàn)有的觀察者
    for (index = 0; index < queue.length; index++) {
      watcher = queue[index];
      if (watcher.before) {
        watcher.before();
      }
      id = watcher.id;
      has[id] = null;
      watcher.run();
      // 在開發(fā)構(gòu)建中,檢查并停止循環(huán)更新逢防。
      if (has[id] != null) {
        circular[id] = (circular[id] || 0) + 1;
        if (circular[id] > MAX_UPDATE_COUNT) {
          warn(
            'You may have an infinite update loop ' + (
              watcher.user
                ? ("in watcher with expression \"" + (watcher.expression) + "\"")
                : "in a component render function."
            ),
            watcher.vm
          );
          break
        }
      }
    }

    // 在重置狀態(tài)之前保留帖子隊(duì)列的副本
    var activatedQueue = activatedChildren.slice();
    var updatedQueue = queue.slice();

    resetSchedulerState();

    // 執(zhí)行actived鉤子函數(shù)
    callActivatedHooks(activatedQueue);
    //執(zhí)行updated鉤子函數(shù)
    callUpdatedHooks(updatedQueue);

    // devtool hook
    /* istanbul ignore if */
    if (devtools && config.devtools) {
      devtools.emit('flush');
    }
  }
  // 按索引遞減的順序執(zhí)行_watcher關(guān)聯(lián)實(shí)例的updated鉤子
  function callUpdatedHooks (queue) {
    var i = queue.length;
    while (i--) {
      var watcher = queue[i];
      var vm = watcher.vm;
      if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'updated');
      }
    }
  }

  /**
   *隊(duì)列一個在補(bǔ)丁期間激活的保持活動的組件叶沛。
   *隊(duì)列將在整個樹被修補(bǔ)后進(jìn)行處理。
   */
  function queueActivatedComponent (vm) {
    //將_inactive設(shè)置為false忘朝,這樣渲染函數(shù)就可以
    //依賴于檢查它是否在一個非活動的樹(例如路由器視圖)
    vm._inactive = false;
    activatedChildren.push(vm);
  }

  function callActivatedHooks (queue) {
    for (var i = 0; i < queue.length; i++) {
      queue[i]._inactive = true;
      activateChildComponent(queue[i], true /* true */);
    }
  }

  /**
   * 將一個觀察者推入觀察者隊(duì)列灰署。具有重復(fù)id的作業(yè)將被跳過,除非它是在隊(duì)列刷新時被推入局嘁。
   */
  function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // 如果已經(jīng)刷新溉箕,則根據(jù)其id拼接監(jiān)視程序
        // 如果已經(jīng)超過了它的id,它將立即運(yùn)行下一個
        var i = queue.length - 1;
        while (i > index && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(i + 1, 0, watcher);
      }
      if (!waiting) {
        waiting = true;

        if (!config.async) {
          // 頭尾插入代碼來獲取耗費(fèi)時間
          flushSchedulerQueue();
          return
        }
        nextTick(flushSchedulerQueue);
      }
    }
  }

  /*  */



  var uid$2 = 0;

  /**
   * 觀察者解析表達(dá)式悦昵,收集依賴項(xiàng)肴茄,并在表達(dá)式值改變時觸發(fā)回調(diào)。這用于$watch() api和指令
   * Watcher 用于初始化數(shù)據(jù)的watcher的實(shí)列,他的原型上有個update用于派發(fā)更新
   */
  var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if (options) {
      this.deep = !!options.deep;
      this.user = !!options.user;
      this.lazy = !!options.lazy;
      this.sync = !!options.sync;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // 用于懶惰的觀察者
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // getter的解析表達(dá)式
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get();
  };

  /**
   * 評估getter但指,并重新收集依賴項(xiàng)寡痰。
   */
  Watcher.prototype.get = function get () {
    //將當(dāng)前用戶watch保存到Dep.target中
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      //執(zhí)行用戶wathcer的getter()方法,此方法會將當(dāng)前用戶watcher作為訂閱者訂閱起來
      value = this.getter.call(vm, vm);
    } catch (e) {
      if (this.user) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw e
      }
    } finally {
      //"touch"每一個屬性棋凳,所以他們都被跟蹤
      //深度監(jiān)視的依賴項(xiàng)
      if (this.deep) {
        traverse(value);
      }
      //恢復(fù)之前的watcher
      popTarget();
      this.cleanupDeps();
    }
    return value
  };

  /**
   *給這個指令添加一個依賴項(xiàng).
   添加依賴(重點(diǎn))
   */
  Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      //將依賴的每個dep拦坠,添加到 watcher 的 deps集合中,完成數(shù)據(jù)的收集
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  };

  /**
   * 清理依賴項(xiàng)集合.
   */
  Watcher.prototype.cleanupDeps = function cleanupDeps () {
    var i = this.deps.length;
    while (i--) {
      var dep = this.deps[i];
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this);
      }
    }
    var tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();
    tmp = this.deps;
    // 把 this.deps = this.newDeps剩岳,緩存到 deps 里贞滨,然后清空newDeps,來做下一次的收集
    this.deps = this.newDeps;
    this.newDeps = tmp;
    this.newDeps.length = 0;
  };

  /**
   * 更新數(shù)據(jù)的方法拍棕,在派發(fā)更新的時候會用到晓铆。 computed 更新數(shù)據(jù)的時候,用 dep 的 notify 方法進(jìn) 
   * 行更新數(shù)據(jù)尤蒿,更新數(shù)據(jù)主要調(diào)用的是 run 方法
   */
  Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

  /**
   * 在這個階段主要是運(yùn)行g(shù)et方法腰池,拿到數(shù)據(jù)
   */
  Watcher.prototype.run = function run () {
    if (this.active) {
      var value = this.get();
      if (
        value !== this.value ||
        // 深度觀察者和對象/數(shù)組的觀察者應(yīng)該發(fā)射,當(dāng)值相同時示弓,因?yàn)橹悼赡苡型蛔儭?        isObject(value) ||
        this.deep
      ) {
        //設(shè)置新的值
        var oldValue = this.value;
        this.value = value;
        if (this.user) {
          var info = "callback for watcher \"" + (this.expression) + "\"";
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info);
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  };

  // 用來重新計算,更新緩存值奏属,并重置 dirty 為false囱皿,表示緩存已更新
  Watcher.prototype.evaluate = function evaluate () {
    this.value = this.get();
     // 執(zhí)行完更新函數(shù)之后嘱腥,立即重置標(biāo)志
    this.dirty = false;
  };

  /**
   * 深度收集依賴齿兔,computed 可以收集 computed 數(shù)據(jù)就是依靠這個方法
   */
  Watcher.prototype.depend = function depend () {
    var i = this.deps.length;
    while (i--) {
      //注意這里的 depend 方法是 Dep 原型上的方法添诉,不是Watcher 的法
      this.deps[i].depend();
    }
  };

  /**
   * 從所有依賴項(xiàng)的訂閱者列表中刪除self
   */
  Watcher.prototype.teardown = function teardown () {
    if (this.active) {
      // 從vm的監(jiān)視列表中移除self這是一個有點(diǎn)昂貴的操作,所以我們跳過它艾帐,如果vm正在被銷毀。
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this);
      }
      var i = this.deps.length;
      while (i--) {
        this.deps[i].removeSub(this);
      }
      this.active = false;
    }
  };

  //***************************************代理proxy*************************** */
  /* 初始化 */

  var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
  };
  // 定義代理函數(shù),target:當(dāng)前對象,sourceKey:代理對象的名稱,key:要訪問的屬性
  function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
      //訪問vm[message]求橄,就返回vm[data][message]
      return this[sourceKey][key]
    };
    //設(shè)值vm[message],就變成設(shè)值vm[data][message]
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val;
    };
    //通過defineProperty對vm[message]進(jìn)行攔截,并將攔截的set和get方法通過sharedPropertyDefinition傳進(jìn)去
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }
  //初始化實(shí)例狀態(tài)
  function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    //初始化props和methods
    if (opts.props) { initProps(vm, opts.props); }
    if (opts.methods) { initMethods(vm, opts.methods); }
    if (opts.data) {
      //把data屬性注入到Vue實(shí)例上
      initData(vm);
    } else {
      // 調(diào)用observe(data)將data對象轉(zhuǎn)化成響應(yīng)式的對象
      observe(vm._data = {}, true /* asRootData */);
    }
    //初始化computed
    if (opts.computed) { initComputed(vm, opts.computed); }
    if (opts.watch && opts.watch !== nativeWatch) {
      //初始化watch
      initWatch(vm, opts.watch);
    }
  }

  function initProps (vm, propsOptions) {
    var propsData = vm.$options.propsData || {};
    var props = vm._props = {};
    // 緩存props的key涵亏,以便將來的道具更新可以使用Array進(jìn)行迭代气筋,而不是使用動態(tài)對象鍵枚舉麸恍。
    var keys = vm.$options._propKeys = [];
    var isRoot = !vm.$parent;
    // 根實(shí)例道具需要被轉(zhuǎn)換
    if (!isRoot) {
      toggleObserving(false);
    }
    var loop = function ( key ) {
      keys.push(key);
      //用來處理校驗(yàn)規(guī)范,設(shè)置默認(rèn)值采够,取值傳遞過來得參數(shù)值等操作
      var value = validateProp(key, propsOptions, propsData, vm);
      /* istanbul ignore else */
      {
        var hyphenatedKey = hyphenate(key);
        if (isReservedAttribute(hyphenatedKey) ||
            config.isReservedAttr(hyphenatedKey)) {
          warn(
            ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
            vm
          );
        }
        // 來處理props中參數(shù)得響應(yīng)式
        defineReactive$$1(props, key, value, function () {
          if (!isRoot && !isUpdatingChildComponent) {
            warn(
              "Avoid mutating a prop directly since the value will be " +
              "overwritten whenever the parent component re-renders. " +
              "Instead, use a data or computed property based on the prop's " +
              "value. Prop being mutated: \"" + key + "\"",
              vm
            );
          }
        });
      }
      // 在Vue.extend()期間,靜態(tài)道具已經(jīng)被代理到組件的原型上了逝薪,只需要在這里實(shí)例化時定義的代理道具。
      if (!(key in vm)) {
        proxy(vm, "_props", key);
      }
    };

    for (var key in propsOptions) loop( key );
    toggleObserving(true);
  }
  // 運(yùn)行 observe 函數(shù)深度遍歷數(shù)據(jù)中的每一個屬性,進(jìn)行數(shù)據(jù)劫持
  function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      warn(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    // proxy data on instance
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, key)) {
        warn(
          "The data property \"" + key + "\" is already declared as a prop. " +
          "Use prop default value instead.",
          vm
        );
      } else if (!isReserved(key)) {
        proxy(vm, "_data", key);
      }
    }
    // observe data
    observe(data, true /* asRootData */);
  }
  
  function getData (data, vm) {
    // 在調(diào)用數(shù)據(jù)獲取器時禁用dep收集
    pushTarget();
    try {
      return data.call(vm, vm)
    } catch (e) {
      handleError(e, vm, "data()");
      return {}
    } finally {
      popTarget();
    }
  }

  var computedWatcherOptions = { lazy: true };
  // 始化computed的時候(initComputed),會監(jiān)測數(shù)據(jù)是否已經(jīng)存在data或props上吹埠,如果存在則拋出警告,
  // 否則調(diào)用defineComputed函數(shù)刷袍,監(jiān)聽數(shù)據(jù)鸽心,為組件中的屬性綁定getter及setter顽频。如果computed中屬性的值是一個函數(shù),
  // 則默認(rèn)為屬性的getter函數(shù)蟀淮。此外屬性的值還可以是一個對象,他只有三個有效字段set策治、get和cache,
  // 分別表示屬性的setter、getter和是否啟用緩存遵湖,其中g(shù)et是必須的,cache默認(rèn)為true

  function initComputed (vm, computed) {
    var watchers = vm._computedWatchers = Object.create(null);
    // 計算屬性只是SSR時期的getter
    var isSSR = isServerRendering();

    for (var key in computed) {
      var userDef = computed[key];
      var getter = typeof userDef === 'function' ? userDef : userDef.get;
      if (getter == null) {
        warn(
          ("Getter is missing for computed property \"" + key + "\"."),
          vm
        );
      }

      if (!isSSR) {
        // 為computed屬性創(chuàng)建內(nèi)部監(jiān)視器垄潮。
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
        );
      }

      // 如果定義的計算屬性不在組件實(shí)例上旅急,對屬性進(jìn)行數(shù)據(jù)劫持
      if (!(key in vm)) {
        defineComputed(vm, key, userDef);
      } else {
        // 如果定義的計算屬性在data和props有溺拱,拋出警告
        if (key in vm.$data) {
          warn(("The computed property \"" + key + "\" is already defined in data."), vm);
        } else if (vm.$options.props && key in vm.$options.props) {
          warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
        } else if (vm.$options.methods && key in vm.$options.methods) {
          warn(("The computed property \"" + key + "\" is already defined as a method."), vm);
        }
      }
    }
  }
// 監(jiān)聽數(shù)據(jù)沐扳,為組件中的屬性綁定getter及sette
  function defineComputed (
    target,
    key,
    userDef
  ) {
    var shouldCache = !isServerRendering();
    if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : createGetterInvoker(userDef);
      sharedPropertyDefinition.set = noop;
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : createGetterInvoker(userDef.get)
        : noop;
      sharedPropertyDefinition.set = userDef.set || noop;
    }
    if (sharedPropertyDefinition.set === noop) {
      sharedPropertyDefinition.set = function () {
        warn(
          ("Computed property \"" + key + "\" was assigned to but it has no setter."),
          this
        );
      };
    }
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }
// 創(chuàng)建計算屬性的getter函數(shù)computedGetter
  function createComputedGetter (key) {
    return function computedGetter () {
      var watcher = this._computedWatchers && this._computedWatchers[key];
      if (watcher) {
        if (watcher.dirty) {
          //watcher.evaluate中更新watcher的值,并把watcher.dirty設(shè)置為false
                //這樣等下次依賴更新的時候才會把watcher.dirty設(shè)置為true,然后進(jìn)行取值的時候才會再次運(yùn)行這個函數(shù)
          watcher.evaluate();
        }
        //依賴追蹤
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }

  function createGetterInvoker(fn) {
    // 沒有緩存則直接執(zhí)行computed的getter函數(shù)
    return function computedGetter () {
      return fn.call(this, this)
    }
  }
// 綁定了所有method的this為vm
  function initMethods (vm, methods) {
    var props = vm.$options.props;
    for (var key in methods) {
      {
        if (typeof methods[key] !== 'function') {
          warn(
            "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
            "Did you reference the function correctly?",
            vm
          );
        }
        if (props && hasOwn(props, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a prop."),
            vm
          );
        }
        if ((key in vm) && isReserved(key)) {
          warn(
            "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
            "Avoid defining component methods that start with _ or $."
          );
        }
      }
      vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    }
  }
  // vm.$watch函數(shù)會直接使用Watcher構(gòu)建觀察者對象。watch中屬性的值作為watcher.cb存在屋吨,在觀察者update的時候离赫,在watcher.run函數(shù)中執(zhí)行
  function initWatch (vm, watch) {
    //遍歷watch渊胸,為每一個屬性創(chuàng)建偵聽器
    for (var key in watch) {
      var handler = watch[key];
      //如果屬性值是一個數(shù)組,則遍歷數(shù)組切厘,為屬性創(chuàng)建多個偵聽器
        //createWatcher函數(shù)中封裝了vm.$watch,會在vm.$watch中創(chuàng)建偵聽器
      if (Array.isArray(handler)) {
        for (var i = 0; i < handler.length; i++) {
          createWatcher(vm, key, handler[i]);
        }
      } else {
        //為屬性創(chuàng)建偵聽器
        createWatcher(vm, key, handler);
      }
    }
  }
  function createWatcher (
    vm,
    expOrFn,
    handler,
    options
  ) {
    //如果屬性值是一個對象,則取對象的handler屬性作為回調(diào)
    if (isPlainObject(handler)) {
      options = handler;
      handler = handler.handler;
    }
    //如果屬性值是一個字符串途蒋,則從組件實(shí)例上尋找
    if (typeof handler === 'string') {
      handler = vm[handler];
    }
    //為屬性創(chuàng)建偵聽器
    return vm.$watch(expOrFn, handler, options)
  }

  function stateMixin (Vue) {
    // 在使用object . defineproperty時懊烤,flow在直接聲明定義對象方面有一些問題,所以我們必須在這里過程地構(gòu)建對象越败。
    // 定義了dataDef和propsDef嗡综,主要是用來設(shè)置響應(yīng)式d a t a 和 data和data和props的get和set方法
    var dataDef = {};
    dataDef.get = function () { return this._data };
    var propsDef = {};
    propsDef.get = function () { return this._props };
    {
      dataDef.set = function () {
        warn(
          'Avoid replacing instance root $data. ' +
          'Use nested data properties instead.',
          this
        );
      };
      propsDef.set = function () {
        warn("$props is readonly.", this);
      };
    }
    Object.defineProperty(Vue.prototype, '$data', dataDef);
    Object.defineProperty(Vue.prototype, '$props', propsDef);

    Vue.prototype.$set = set;
    Vue.prototype.$delete = del;

    Vue.prototype.$watch = function (
      expOrFn,
      cb,
      options
    ) {
      var vm = this;
      if (isPlainObject(cb)) {
        return createWatcher(vm, expOrFn, cb, options)
      }
      options = options || {};
      options.user = true;
      options.user = true;
      //為需要觀察的 expOrFn 添加watcher ,expOrFn的值有改變時執(zhí)行cb氢卡,
      //在watcher的實(shí)例化的過程中會對expOrFn進(jìn)行解析,并為expOrFn涉及到的data數(shù)據(jù)下的def添加該watcher
      var watcher = new Watcher(vm, expOrFn, cb, options);
      if (options.immediate) {
        var info = "callback for immediate watcher \"" + (watcher.expression) + "\"";
        pushTarget();
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
        popTarget();
      }
       //取消觀察函數(shù)
      return function unwatchFn () {
        watcher.teardown();
      }
    };
  }

  /*  */

  var uid$3 = 0;

  function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      vm._uid = uid$3++;

      var startTag, endTag;
      /* istanbul ignore if */
      if (config.performance && mark) {
        startTag = "vue-perf-start:" + (vm._uid);
        endTag = "vue-perf-end:" + (vm._uid);
        mark(startTag);
      }

      // 如果是Vue的實(shí)例,則不需要被observe
      vm._isVue = true;
      // options參數(shù)的處理
      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(
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
        );
      }
      /* istanbul ignore else */
      {
        initProxy(vm);
      }
      // expose real self
      vm._self = vm;
      // vm的生命周期相關(guān)變量初始化
      initLifecycle(vm);
      // vm的事件監(jiān)聽初始化
      initEvents(vm);
      // vm的編譯render初始化
      initRender(vm);
      // vm的beforeCreate生命鉤子的回調(diào)
      callHook(vm, 'beforeCreate');
      // vm在data/props初始化之前要進(jìn)行綁定
      initInjections(vm); // resolve injections before data/props
      // vm的sate狀態(tài)初始化
      initState(vm);
      // vm在data/props之后要進(jìn)行提供
      initProvide(vm); // resolve provide after data/props
      // vm的created生命鉤子的回調(diào)
      callHook(vm, 'created');

      /* istanbul ignore if */
      if (config.performance && mark) {
        vm._name = formatComponentName(vm, false);
        mark(endTag);
        measure(("vue " + (vm._name) + " init"), startTag, endTag);
      }

      if (vm.$options.el) {
        vm.$mount(vm.$options.el);
      }
    };
  }
  // Vue初始化中的選項(xiàng)合并
  // 通過循環(huán)遞歸,找到Component這個類的繼承鏈,然后把所有的配置都進(jìn)行融合
  function initInternalComponent (vm, options) {
    var opts = vm.$options = Object.create(vm.constructor.options);
    // 該組件實(shí)例的vnode對象
    var parentVnode = options._parentVnode;
    opts.parent = options.parent;
    opts._parentVnode = parentVnode;

    var vnodeComponentOptions = parentVnode.componentOptions;
    opts.propsData = vnodeComponentOptions.propsData;
    opts._parentListeners = vnodeComponentOptions.listeners;
    opts._renderChildren = vnodeComponentOptions.children;
    opts._componentTag = vnodeComponentOptions.tag;

    if (options.render) {
      opts.render = options.render;
      opts.staticRenderFns = options.staticRenderFns;
    }
  }

  function resolveConstructorOptions (Ctor) {
    var options = Ctor.options;
    // 首先需要判斷該類是否是Vue的子類
    if (Ctor.super) {
      var superOptions = resolveConstructorOptions(Ctor.super);
      var cachedSuperOptions = Ctor.superOptions;
       // 來判斷父類中的options 有沒有發(fā)生變化
      if (superOptions !== cachedSuperOptions) {
        // 超級選項(xiàng)改變,需要解決新的選項(xiàng)
        Ctor.superOptions = superOptions;
        // 檢查是否有任何后期修改/附加的選項(xiàng)
        var modifiedOptions = resolveModifiedOptions(Ctor);
        // update base extend options
        if (modifiedOptions) {
          // 當(dāng)為Vue混入一些options時摔刁,superOptions會發(fā)生變化,此時于之前子類中存儲的cachedSuperOptions已經(jīng)不相等,所以下面的操作主要就是更新sub.superOptions
          extend(Ctor.extendOptions, modifiedOptions);
        }
        options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
        if (options.name) {
          options.components[options.name] = Ctor;
        }
      }
    }
    return options
  }

  function resolveModifiedOptions (Ctor) {
    var modified;
    var latest = Ctor.options;
    // 執(zhí)行Vue.extend時封裝的"自身"options,這個屬性就是方便檢查"自身"的options有沒有變化
    var sealed = Ctor.sealedOptions;
    // 遍歷當(dāng)前構(gòu)造器上的options屬性哼凯,如果在"自身"封裝的options里沒有,則證明是新添加的蝴光。執(zhí)行if內(nèi)的語句。
    // 調(diào)用dedupe方法,最終返回modified變量(即”自身新添加的options“)
  for (const key in latest) {
    for (var key in latest) {
      if (latest[key] !== sealed[key]) {
        if (!modified) { modified = {}; }
        modified[key] = latest[key];
      }
    }
    return modified
  }
}

  function Vue (options) {
    if (!(this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }
  initMixin(Vue);// 初始化 Mixin
  stateMixin(Vue);// 狀態(tài) Mixin
  eventsMixin(Vue);// 事件 Mixin
  lifecycleMixin(Vue);// 生命周期 Mixin
  renderMixin(Vue);// 渲染 Mixin

  /*  Vue.use() 的注冊本質(zhì)上就是執(zhí)行了一個 install 方法 */
  function initUse (Vue) {
    // 在全局api Vue 上定義了 use 方法装蓬,接收一個 plugin 參數(shù)
    Vue.use = function (plugin) {
      var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
      if (installedPlugins.indexOf(plugin) > -1) {
        return this
      }

      // 用來判斷該插件是不是已經(jīng)注冊過乳蛾,防止重復(fù)注冊
      var args = toArray(arguments, 1);
      args.unshift(this);
      // 判斷 Vue.use() 傳入的第一個參數(shù)是 Object 還是 Function
      if (typeof plugin.install === 'function') {
        plugin.install.apply(plugin, args);
      } else if (typeof plugin === 'function') {
        plugin.apply(null, args);
      }
      installedPlugins.push(plugin);
      return this
    };
  }

  /* 將options和mixin合并 */

  function initMixin$1 (Vue) {
    Vue.mixin = function (mixin) {
      this.options = mergeOptions(this.options, mixin);
      return this
    };
  }

    /**
     * Vue.extend的原理以及初始化過程
     */

  function initExtend (Vue) {
    // 每個實(shí)例構(gòu)造函數(shù)(包括Vue)都有一個唯一的cid蹂随。這使我們能夠?yàn)樵屠^承創(chuàng)建包裝子構(gòu)造函數(shù)并緩存它們。
    Vue.cid = 0;
    var cid = 1;


    Vue.extend = function (extendOptions) {
      // extendOptions就是我我們傳入的組件options
      extendOptions = extendOptions || {};
      var Super = this;
      var SuperId = Super.cid;
          // 每次創(chuàng)建完Sub構(gòu)造函數(shù)后咳燕,都會把這個函數(shù)儲存在extendOptions上的_Ctor中
          // 下次如果用再同一個extendOptions創(chuàng)建Sub時
          // 就會直接從_Ctor返回
      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }
      //校驗(yàn)組件 名
      var name = extendOptions.name || Super.options.name;
      if (name) {
        validateComponentName(name);
      }
      // 創(chuàng)建Sub構(gòu)造函數(shù)
      var Sub = function VueComponent (options) {
        this._init(options);
      };
      // 繼承Super,如果使用Vue.extend曹货,這里的Super就是Vue
      Sub.prototype = Object.create(Super.prototype);
      Sub.prototype.constructor = Sub;
      Sub.cid = cid++;
      Sub.options = mergeOptions(
        Super.options,
        extendOptions
      );
       // 將組件的options和Vue的options合并,得到一個完整的options
    // 可以理解為將Vue的一些全局的屬性,比如全局注冊的組件和mixin慨仿,分給了Sub
      Sub['super'] = Super;

      // 將props和computed代理到了原型上
      if (Sub.options.props) {
        initProps$1(Sub);
      }
      if (Sub.options.computed) {
        initComputed$1(Sub);
      }

       // 繼承Vue的global-api
      Sub.extend = Super.extend;
      Sub.mixin = Super.mixin;
      Sub.use = Super.use;

     // 繼承assets的api跑慕,比如注冊組件核行,指令减余,過濾器
      ASSET_TYPES.forEach(function (type) {
        Sub[type] = Super[type];
      });
      // 在components里添加一個自己
      if (name) {
        Sub.options.components[name] = Sub;
      }

      // 這些options保存起來
      Sub.superOptions = Super.options;
      Sub.extendOptions = extendOptions;
      Sub.sealedOptions = extend({}, Sub.options);

       // 設(shè)置緩存
      cachedCtors[SuperId] = Sub;
      return Sub
    };
  }
  //初始化自定義代理函數(shù)
  function initProps$1 (Comp) {
    var props = Comp.options.props;
    for (var key in props) {
      proxy(Comp.prototype, "_props", key);
    }
  }
  //為組件的屬性綁定setter和getter屬性
  function initComputed$1 (Comp) {
    var computed = Comp.options.computed;
    for (var key in computed) {
      defineComputed(Comp.prototype, key, computed[key]);
    }
  }

  /* 創(chuàng)建組件堡牡、指令擦剑、過濾器 */

  function initAssetRegisters (Vue) {
    //遍歷 ASSET_TYPES 數(shù)組赚抡,為Vue 定義相應(yīng)的方法
    // ASSET_TYPES 包含 directive  component  filter
    ASSET_TYPES.forEach(function (type) {
      Vue[type] = function (
        id,
        definition
      ) {
         // 判斷是否有第二個參數(shù)妇垢,沒有的話去之前的option的組件或者指令
        if (!definition) {
          return this.options[type + 's'][id]
        } else {
          //判斷是否是一個組件灼舍,校驗(yàn)組件名字
          if (type === 'component') {
            validateComponentName(id);
          }
           // 判斷傳入的是不是一個原始對象
          if (type === 'component' && isPlainObject(definition)) {
            definition.name = definition.name || id;
            // 把組件配置轉(zhuǎn)換為組件的構(gòu)造函數(shù)
            definition = this.options._base.extend(definition);
          }
          if (type === 'directive' && typeof definition === 'function') {
            definition = { bind: definition, update: definition };
          }
           // 全局注冊,存儲資源賦值
          this.options[type + 's'][id] = definition;
          return definition
        }
      };
    });
  }
//獲取組件的名字
  function getComponentName (opts) {
    return opts && (opts.Ctor.options.name || opts.tag)
  }
  // 檢測name是否匹配
  function matches (pattern, name) {
    if (Array.isArray(pattern)) {
      return pattern.indexOf(name) > -1
    } else if (typeof pattern === 'string') {
      return pattern.split(',').indexOf(name) > -1
    } else if (isRegExp(pattern)) {
      return pattern.test(name)
    }
    /* istanbul ignore next */
    return false
  }

  function pruneCache (keepAliveInstance, filter) {
    //獲取keepAliveInstance的緩存
    var cache = keepAliveInstance.cache;
    var keys = keepAliveInstance.keys;
    var _vnode = keepAliveInstance._vnode;
    for (var key in cache) {
      var entry = cache[key];
      if (entry) {
        var name = entry.name;
        // name不符合filter條件的侠姑,同時不是目前渲染的vnode時妥畏,銷毀vnode對應(yīng)的組件實(shí)例(Vue實(shí)例),并從cache中移除
        if (name && !filter(name)) {
          //pruneCache 函數(shù)的核心就是去調(diào)用pruneCacheEntry
          pruneCacheEntry(cache, key, keys, _vnode);
        }
      }
    }
  }

  function pruneCacheEntry (
    cache,
    key,
    keys,
    current
  ) {
    // 通過cached$$1 = cache[key]` 獲取頭部數(shù)據(jù)對應(yīng)的值 `vnode`,執(zhí)行 `cached$$1.componentInstance.$destroy() 將組件實(shí)例銷毀
    var entry = cache[key];
    if (entry && (!current || entry.tag !== current.tag)) {
      // 銷毀vnode對應(yīng)的組件實(shí)例
      entry.componentInstance.$destroy();
    }
    // 清空組件對應(yīng)的緩存節(jié)點(diǎn)
    cache[key] = null;
    // 刪除緩存中的頭部數(shù)據(jù) keys[0]
    remove(keys, key);
  }

  var patternTypes = [String, RegExp, Array];

  var KeepAlive = {
    name: 'keep-alive',
    // 抽象組件,判斷當(dāng)前組件虛擬dom是否渲染成真實(shí)dom的關(guān)鍵
    abstract: true,
  // 定義include罗捎、exclude及max屬性
  // include - 字符串或正則表達(dá)式。只有名稱匹配的組件會被緩存泻红。
  // exclude - 字符串或正則表達(dá)式。任何名稱匹配的組件都不會被緩存缠劝。
  // max - 數(shù)字。最多可以緩存多少組件實(shí)例脱羡。
    props: {
      include: patternTypes,
      exclude: patternTypes,
      max: [String, Number]
    },

    methods: {
      cacheVNode: function cacheVNode() {
        var ref = this;
        var cache = ref.cache;
        var keys = ref.keys;
        var vnodeToCache = ref.vnodeToCache;
        var keyToCache = ref.keyToCache;
        if (vnodeToCache) {
          var tag = vnodeToCache.tag;
          var componentInstance = vnodeToCache.componentInstance;
          var componentOptions = vnodeToCache.componentOptions;
          cache[keyToCache] = {
            name: getComponentName(componentOptions),
            tag: tag,
            componentInstance: componentInstance,
          };
          keys.push(keyToCache);
          // prune oldest entry
          if (this.max && keys.length > parseInt(this.max)) {
            pruneCacheEntry(cache, keys[0], keys, this._vnode);
          }
          this.vnodeToCache = null;
        }
      }
    },
    //在 keep-alive 的創(chuàng)建階段绕娘, created鉤子會創(chuàng)建一個cache對象抖拦,用來保存vnode節(jié)點(diǎn)
    created: function created () {
      this.cache = Object.create(null);
      this.keys = [];
    },
    // 在銷毀階段,destroyed 鉤子則會調(diào)用pruneCacheEntry方法清除cache緩存中的所有組件實(shí)例
    destroyed: function destroyed () {
      for (var key in this.cache) {
        // 銷毀vnode對應(yīng)的組件實(shí)例
        pruneCacheEntry(this.cache, key, this.keys);
      }
    },

    mounted: function mounted () {
      var this$1 = this;

      this.cacheVNode();
      // 通過 watch 來監(jiān)聽 include 和 exclude,在其改變時調(diào)用 pruneCache 以修改 cache 緩存中的緩存數(shù)據(jù)
      this.$watch('include', function (val) {
        pruneCache(this$1, function (name) { return matches(val, name); });
      });
      this.$watch('exclude', function (val) {
        pruneCache(this$1, function (name) { return !matches(val, name); });
      });
    },

    updated: function updated () {
      this.cacheVNode();
    },
    // 渲染階段
    render: function render () {
      var slot = this.$slots.default;
      // 得到slot插槽中的第一個組件
      var vnode = getFirstComponentChild(slot);
      var componentOptions = vnode && vnode.componentOptions;
      if (componentOptions) {
        //獲取組件名稱耗啦,優(yōu)先獲取組件的name字段,否則是組件的tag
        var name = getComponentName(componentOptions);
        var ref = this;
        var include = ref.include;
        var exclude = ref.exclude;
        if (
         // 不需要緩存机杜,則返回 vnode
          (include && (!name || !matches(include, name))) ||
          // excluded
          (exclude && name && matches(exclude, name))
        ) {
          return vnode
        }

        var ref$1 = this;
        var cache = ref$1.cache;
        var keys = ref$1.keys;
        var key = vnode.key == null
        // 同一個構(gòu)造函數(shù)可能會被注冊為不同的本地組件帜讲,所以單獨(dú)使用cid是不夠的
          ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '')
          : vnode.key;
        if (cache[key]) {
           // 有緩存則取緩存的組件實(shí)例
          vnode.componentInstance = cache[key].componentInstance;
          // make current key freshest
          remove(keys, key);
          keys.push(key);
        } else {
           // 無緩存則創(chuàng)建緩存
          this.vnodeToCache = vnode;
          this.keyToCache = key;
        }
          // keepAlive標(biāo)記
        vnode.data.keepAlive = true;
      }
      return vnode || (slot && slot[0])
    }
  };

  var builtInComponents = {
    KeepAlive: KeepAlive
  };

  // 初始化全局API
  function initGlobalAPI (Vue) {
    // config
    var configDef = {};
    // 給configDef添加了一個get屬性,這個屬性返回得是一個config對象椒拗,這個cofig對象里面获黔,有n個屬性
    configDef.get = function () { return config; };
    {
      // 給configDef添加了一個set屬性,返回的一個警告
      configDef.set = function () {
        warn(
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    // 為Vue的構(gòu)造函數(shù),添加一個要通過Object.defineProperty監(jiān)聽的屬性config
    Object.defineProperty(Vue, 'config', configDef);

    // 公開util
    // 設(shè)置了一個公開的util對象在验,但是它不是公共的api玷氏,避免依賴
    Vue.util = {
      warn: warn,
      extend: extend,
      mergeOptions: mergeOptions,
      defineReactive: defineReactive$$1
    };
    // 綁定全局API——Vue.set,Vue.delete腋舌,Vue.nextTick
    Vue.set = set;
    Vue.delete = del;
    Vue.nextTick = nextTick;

    // 2.6 explicit observable API
    Vue.observable = function (obj) {
      observe(obj);
      return obj
    };

    Vue.options = Object.create(null);
    ASSET_TYPES.forEach(function (type) {
      Vue.options[type + 's'] = Object.create(null);
    });

    // 這用于標(biāo)識“基”構(gòu)造函數(shù)盏触,以便在Weex的多實(shí)例場景中擴(kuò)展所有普通對象組件
    Vue.options._base = Vue;

    extend(Vue.options.components, builtInComponents);
    // 初始化Vue.extend,Vue.mixin块饺,Vue.extend
    // AssetRegisters就是component赞辩,directive,filter三者
    initUse(Vue);
    initMixin$1(Vue);
    initExtend(Vue);
    initAssetRegisters(Vue);
  }

  initGlobalAPI(Vue);
  // vue.prototype上掛載$isServer刨沦、$ssrContext诗宣、FunctionalRenderContext
  Object.defineProperty(Vue.prototype, '$isServer', {
    get: isServerRendering
  });

  Object.defineProperty(Vue.prototype, '$ssrContext', {
    get: function get () {
      /* istanbul ignore next */
      return this.$vnode && this.$vnode.ssrContext
    }
  });

  //為ssr運(yùn)行時助手安裝暴露FunctionalRenderContext
  Object.defineProperty(Vue, 'FunctionalRenderContext', {
    value: FunctionalRenderContext
  });
  // 在vue 上掛載 version 屬性
  Vue.version = '2.6.14';
  //檢驗(yàn)是否存在'style','class'字符串
  var isReservedAttr = makeMap('style,class');

  // 應(yīng)該使用props進(jìn)行綁定的屬性
  //檢驗(yàn)是否存在'input','textarea','option','select','progress'字符串
  var acceptValue = makeMap('input,textarea,option,select,progress');
  //校驗(yàn)元素值是否通過prop指定
  var mustUseProp = function (tag, type, attr) {
    return (
      (attr === 'value' && acceptValue(tag)) && type !== 'button' ||
      (attr === 'selected' && tag === 'option') ||
      (attr === 'checked' && tag === 'input') ||
      (attr === 'muted' && tag === 'video')
    )
  };
//檢驗(yàn)是否存在'contenteditable','draggable','spellcheck'字符串
  var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck');
//檢驗(yàn)是否存在'events','caret','typing','plaintext-only'字符串
  var isValidContentEditableValue = makeMap('events,caret,typing,plaintext-only');

  var convertEnumeratedValue = function (key, value) {
    return isFalsyAttrValue(value) || value === 'false'
      ? 'false'
      // 允許content - itable的任意字符串值
      : key === 'contenteditable' && isValidContentEditableValue(value)
        ? value
        : 'true'
  };
//校驗(yàn)是否包含以下字符串
  var isBooleanAttr = makeMap(
    'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +
    'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +
    'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +
    'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +
    'required,reversed,scoped,seamless,selected,sortable,' +
    'truespeed,typemustmatch,visible'
  );

  var xlinkNS = 'http://www.w3.org/1999/xlink';

  var isXlink = function (name) {
    return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'
  };

  var getXlinkProp = function (name) {
    return isXlink(name) ? name.slice(6, name.length) : ''
  };
  // 通過isFalsyAttrValue方法判斷值是否是false與null膘怕,如果isFalsyAttrValue返回為true則表示傳入的值
  var isFalsyAttrValue = function (val) {
    return val == null || val === false
  };

  /* class 轉(zhuǎn)碼獲取vonde 中的staticClass 靜態(tài)class 和class動態(tài)class轉(zhuǎn)義成真實(shí)dom需要的class格式想诅。然后返回class字符串 */
  function genClassForVnode (vnode) {
    var data = vnode.data;
    var parentNode = vnode;
    var childNode = vnode;
    while (isDef(childNode.componentInstance)) {
      childNode = childNode.componentInstance._vnode;
      if (childNode && childNode.data) {
        data = mergeClassData(childNode.data, data);
      }
    }
    while (isDef(parentNode = parentNode.parent)) {
      if (parentNode && parentNode.data) {
        data = mergeClassData(data, parentNode.data);
      }
    }
    // 
    return renderClass(data.staticClass, data.class)
  }

  function mergeClassData (child, parent) {
    return {
      staticClass: concat(child.staticClass, parent.staticClass),
      class: isDef(child.class)
        ? [child.class, parent.class]
        : parent.class
    }
  }
  // 渲染calss 這里獲取到已經(jīng)轉(zhuǎn)碼的calss
  function renderClass (
    staticClass,
    dynamicClass
  ) {
    if (isDef(staticClass) || isDef(dynamicClass)) {
      // 轉(zhuǎn)碼 class,把數(shù)組格式岛心,對象格式的calss 全部轉(zhuǎn)化成 字符串格式
      return concat(staticClass, stringifyClass(dynamicClass))
    }
    /* istanbul ignore next */
    return ''
  }

  function concat (a, b) {
    return a ? b ? (a + ' ' + b) : a : (b || '')
  }
  // 轉(zhuǎn)碼 class来破,把數(shù)組格式,對象格式的calss 全部轉(zhuǎn)化成 字符串格式
  function stringifyClass (value) {
    if (Array.isArray(value)) {
      // 數(shù)組字符串變成字符串忘古,然后用空格 隔開 拼接 起來變成字符串
      return stringifyArray(value)
    }
    if (isObject(value)) {
      // 對象字符串變成字符串徘禁,然后用空格 隔開 拼接 起來變成字符串
      return stringifyObject(value)
    }
    if (typeof value === 'string') {
      return value
    }
    /* istanbul ignore next */
    return ''
  }
  // 數(shù)組字符串變成字符串,然后用空格 隔開 拼接 起來變成字符串
  function stringifyArray (value) {
    var res = '';
    var stringified;
    for (var i = 0, l = value.length; i < l; i++) {
      if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
        if (res) { res += ' '; }
        res += stringified;
      }
    }
    return res
  }
// 對象字符串變成字符串髓堪,然后用空格 隔開 拼接 起來變成字符串
  function stringifyObject (value) {
    var res = '';
    for (var key in value) {
      if (value[key]) {
        if (res) { res += ' '; }
        res += key;
      }
    }
    return res
  }

  /*  */

  var namespaceMap = {
    svg: 'http://www.w3.org/2000/svg',
    math: 'http://www.w3.org/1998/Math/MathML'
  };
//判斷html標(biāo)簽
  var isHTMLTag = makeMap(
    'html,body,base,head,link,meta,style,title,' +
    'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
    'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
    'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
    's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
    'embed,object,param,source,canvas,script,noscript,del,ins,' +
    'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
    'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
    'output,progress,select,textarea,' +
    'details,dialog,menu,menuitem,summary,' +
    'content,element,shadow,template,blockquote,iframe,tfoot'
  );

  //判斷svg 標(biāo)簽
  var isSVG = makeMap(
    'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +
    'foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +
    'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',
    true
  );
  // 判斷tag是不是pre
  var isPreTag = function (tag) { return tag === 'pre'; };
 // 判斷tag是不是html標(biāo)簽或者svg標(biāo)簽
  var isReservedTag = function (tag) {
    return isHTMLTag(tag) || isSVG(tag)
  };
  function getTagNamespace (tag) {
    if (isSVG(tag)) {
      return 'svg'
    }
    // 對MathML的基本支持注意送朱,它不支持其他MathML元素作為組件根
    if (tag === 'math') {
      return 'math'
    }
  }

  var unknownElementCache = Object.create(null);
  // 檢查dom 節(jié)點(diǎn)的tag標(biāo)簽 類型 是否是VPre 標(biāo)簽 或者是判斷是否是瀏覽器自帶原有的標(biāo)簽
  function isUnknownElement (tag) {
    /* istanbul ignore if */
    if (!inBrowser) {
      return true
    }
    // 判斷tag是不是html標(biāo)簽或者svg標(biāo)簽
    if (isReservedTag(tag)) {
      return false
    }
    tag = tag.toLowerCase();
    /* istanbul ignore if */
    if (unknownElementCache[tag] != null) {
      return unknownElementCache[tag]
    }
    var el = document.createElement(tag);
    if (tag.indexOf('-') > -1) {
      // http://stackoverflow.com/a/28210364/1070244
      return (unknownElementCache[tag] = (
        el.constructor === window.HTMLUnknownElement ||
        el.constructor === window.HTMLElement
      ))
    } else {
      return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))
    }
  }

  var isTextInputType = makeMap('text,number,password,search,email,tel,url');

  /*  */

  /**
   * 查詢一個元素選擇器,如果他不是元素選擇器則創(chuàng)建div
   */
  function query (el) {
    if (typeof el === 'string') {
      var selected = document.querySelector(el);
      if (!selected) {
        warn(
          'Cannot find element: ' + el
        );
        return document.createElement('div')
      }
      return selected
    } else {
      return el
    }
  }

  //創(chuàng)建一個真實(shí)的dom
  function createElement$1 (tagName, vnode) {
    var elm = document.createElement(tagName);
    if (tagName !== 'select') {
      return elm
    }
    // False或null將刪除該屬性干旁,但undefined不會
    if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
      elm.setAttribute('multiple', 'multiple');
    }
    return elm
  }
  // 創(chuàng)建由tagName 指定的HTML元素
  function createElementNS (namespace, tagName) {
    return document.createElementNS(namespaceMap[namespace], tagName)
  }
  // 創(chuàng)建文本節(jié)點(diǎn)
  function createTextNode (text) {
    return document.createTextNode(text)
  }
  // 創(chuàng)建一個注釋節(jié)點(diǎn)
  function createComment (text) {
    return document.createComment(text)
  }
  // 插入節(jié)點(diǎn) 在某個元素前面插入一個節(jié)點(diǎn)
  function insertBefore (parentNode, newNode, referenceNode) {
    parentNode.insertBefore(newNode, referenceNode);
  }
  // 刪除子節(jié)點(diǎn)
  function removeChild (node, child) {
    node.removeChild(child);
  }
  //添加子節(jié)點(diǎn) 尾部
  function appendChild (node, child) {
    node.appendChild(child);
  }
  // 獲取父親子節(jié)點(diǎn)dom
  function parentNode (node) {
    return node.parentNode
  }
  //獲取下一個兄弟節(jié)點(diǎn)
  function nextSibling (node) {
    return node.nextSibling
  }
  //獲取dom標(biāo)簽名稱
  function tagName (node) {
    return node.tagName
  }
   //設(shè)置dom 文本
  function setTextContent (node, text) {
    node.textContent = text;
  }
  //設(shè)置組建樣式的作用域
  function setStyleScope (node, scopeId) {
    node.setAttribute(scopeId, '');
  }
  // Object.freeze凍結(jié)對象
  var nodeOps = /*#__PURE__*/Object.freeze({
    createElement: createElement$1,
    createElementNS: createElementNS,
    createTextNode: createTextNode,
    createComment: createComment,
    insertBefore: insertBefore,
    removeChild: removeChild,
    appendChild: appendChild,
    parentNode: parentNode,
    nextSibling: nextSibling,
    tagName: tagName,
    setTextContent: setTextContent,
    setStyleScope: setStyleScope
  });

  /* ref是給元素或者子組件注冊引用信息的 */

  var ref = {
    create: function create (_, vnode) {
      registerRef(vnode);
    },
    update: function update (oldVnode, vnode) {
      if (oldVnode.data.ref !== vnode.data.ref) {
        registerRef(oldVnode, true);
        registerRef(vnode);
      }
    },
    destroy: function destroy (vnode) {
      registerRef(vnode, true);
    }
  };
  // ref模塊初始化時會執(zhí)行registerRef函數(shù)
  function registerRef (vnode, isRemoval) {
    var key = vnode.data.ref;
    //如果沒有定義ref屬性驶沼,則直接返回
    if (!isDef(key)) { return }
    //當(dāng)前的根Vue實(shí)例
    var vm = vnode.context;
     //優(yōu)先獲取vonde的組件實(shí)例(對于組件來說),或者el(該Vnode對應(yīng)的DOM節(jié)點(diǎn)争群,非組件來說)
    var ref = vnode.componentInstance || vnode.elm;
    var refs = vm.$refs;
    if (isRemoval) {
      if (Array.isArray(refs[key])) {
        remove(refs[key], ref);
      } else if (refs[key] === ref) {
        refs[key] = undefined;
      }
    } else {
       //如果不是移除,當(dāng)在v-for之內(nèi)時回怜,則保存為數(shù)組形式
      if (vnode.data.refInFor) {
        if (!Array.isArray(refs[key])) {
          refs[key] = [ref];
        } else if (refs[key].indexOf(ref) < 0) {
          // $flow-disable-line
          refs[key].push(ref);
        }
         //不是在v-for之內(nèi)時,直接保存到refs對應(yīng)的key屬性上
      } else {
        refs[key] = ref;
      }
    }
  }

  /**
   * Virtual DOM patching algorithm based on Snabbdom by
   * Simon Friis Vindum (@paldepind)
   * Licensed under the MIT License
   * https://github.com/paldepind/snabbdom/blob/master/LICENSE
   *
   * modified by Evan You (@yyx990803)
   *
   * Not type-checking this because this file is perf-critical and the cost
   * of making flow understand it is not worth it.
   */

  var emptyNode = new VNode('', {}, []);

  var hooks = ['create', 'activate', 'update', 'remove', 'destroy'];
  // 判斷當(dāng)前VNode可復(fù)用,銷毀一個DOM節(jié)點(diǎn)并創(chuàng)建一個新的再插入是消耗非常大的,
  // 無論是DOM對象本身的復(fù)雜性還是操作引起的重繪重排换薄,所以虛擬DOM的目標(biāo)是盡可能復(fù)用現(xiàn)有DOM進(jìn)行更新
  function sameVnode (a, b) {
    return (
      a.key === b.key &&
      a.asyncFactory === b.asyncFactory && (
        (
          a.tag === b.tag &&
          a.isComment === b.isComment &&
          // 這個涉及屬性的更新玉雾,如果一個節(jié)點(diǎn)沒有任何屬性,即data為undefined轻要,與一個有data屬性的節(jié)點(diǎn)進(jìn)行更新不如直接渲染一個新的
          isDef(a.data) === isDef(b.data) &&
          // 這個主要是input標(biāo)簽type屬性異同判斷复旬,不同的type相當(dāng)于不同的tag
          sameInputType(a, b)
        ) || (
          isTrue(a.isAsyncPlaceholder) &&
          isUndef(b.asyncFactory.error)
        )
      )
    )
  }
  //這個主要是input標(biāo)簽type屬性異同判斷,不同的type相當(dāng)于不同的tag
  function sameInputType (a, b) {
    if (a.tag !== 'input') { return true }
    var i;
    var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;
    var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;
    return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
  }
  // 接收一個 children 數(shù)組冲泥,生成 key 與 index 索引對應(yīng)的一個 map 表
  function createKeyToOldIdx (children, beginIdx, endIdx) {
    var i, key;
    var map = {};
    for (i = beginIdx; i <= endIdx; ++i) {
      key = children[i].key;
      if (isDef(key)) { map[key] = i; }
    }
    return map
  }
  // patch核心函數(shù)
  // backend的nodeOps是節(jié)點(diǎn)的功能函數(shù)驹碍,包括createElement創(chuàng)建元素失都、removeChild刪除子元素,
  // tagName獲取到標(biāo)簽名等幸冻,backend的modules是vue框架用于分別執(zhí)行某個渲染任務(wù)的功能函數(shù)
  function createPatchFunction (backend) {
    var i, j;
    var cbs = {};

    var modules = backend.modules;
    var nodeOps = backend.nodeOps;
    // 循環(huán)hooks和modules
    for (i = 0; i < hooks.length; ++i) {
      cbs[hooks[i]] = [];
      for (j = 0; j < modules.length; ++j) {
        if (isDef(modules[j][hooks[i]])) {
          cbs[hooks[i]].push(modules[j][hooks[i]]);
        }
      }
    }
    // 將原有的節(jié)點(diǎn)粹庞,同時也是DOM節(jié)點(diǎn)包裝成虛擬節(jié)點(diǎn)
    function emptyNodeAt (elm) {
      return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
    }
    // 創(chuàng)建remove函數(shù)
    // remove$$1函數(shù)作為一個對象,第一個參數(shù)是vnode所屬的dom元素洽损,第二個參數(shù)是監(jiān)聽器個數(shù)庞溜。
    // 內(nèi)部實(shí)現(xiàn)remove函數(shù)擁有l(wèi)isteners屬性,等到這個屬性的值每一次減少直到0時將直接移除節(jié)點(diǎn)
    function createRmCb (childElm, listeners) {
      function remove$$1 () {
        if (--remove$$1.listeners === 0) {
          removeNode(childElm);
        }
      }
      remove$$1.listeners = listeners;
      return remove$$1
    }
    // 移除節(jié)點(diǎn)碑定,先找到父節(jié)點(diǎn)流码,然后通過removeChild移除掉這個節(jié)點(diǎn)
    function removeNode (el) {
      var parent = nodeOps.parentNode(el);
      // 元素可能已經(jīng)由于v-html / v-text而被刪除
      if (isDef(parent)) {
        nodeOps.removeChild(parent, el);
      }
    }
    function isUnknownElement$$1 (vnode, inVPre) {
      return (
        !inVPre &&
        !vnode.ns &&
        !(
          config.ignoredElements.length &&
          config.ignoredElements.some(function (ignore) {
            return isRegExp(ignore)
              ? ignore.test(vnode.tag)
              : ignore === vnode.tag
          })
        ) &&
        // 檢測是否未知el
        config.isUnknownElement(vnode.tag)
      )
    }

    var creatingElmInVPre = 0;
    // 創(chuàng)建新節(jié)點(diǎn)
    function createElm (
      vnode,
      insertedVnodeQueue,
      parentElm,
      refElm,
      nested,
      ownerArray,
      index
    ) {
      if (isDef(vnode.elm) && isDef(ownerArray)) {
        //這個vnode在以前的渲染中使用過,現(xiàn)在它被用作一個新節(jié)點(diǎn),覆蓋它的榆樹將導(dǎo)致
        //當(dāng)它被用作插入時延刘,潛在的補(bǔ)丁錯誤,引用節(jié)點(diǎn)漫试。相反,我們在創(chuàng)建節(jié)點(diǎn)之前按需克隆節(jié)點(diǎn)對應(yīng)的DOM元素碘赖。
        vnode = ownerArray[index] = cloneVNode(vnode);
      }

      vnode.isRootInsert = !nested; //過渡進(jìn)入檢查
      if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
        return
      }

      var data = vnode.data;
      var children = vnode.children;
      var tag = vnode.tag;
      if (isDef(tag)) {
        {
          if (data && data.pre) {
            creatingElmInVPre++;
          }
          if (isUnknownElement$$1(vnode, creatingElmInVPre)) {
            warn(
              'Unknown custom element: <' + tag + '> - did you ' +
              'register the component correctly? For recursive components, ' +
              'make sure to provide the "name" option.',
              vnode.context
            );
          }
        }

        vnode.elm = vnode.ns
          ? nodeOps.createElementNS(vnode.ns, tag)
          : nodeOps.createElement(tag, vnode);
        setScope(vnode);

        /* istanbul ignore if */
        {
          createChildren(vnode, children, insertedVnodeQueue);
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue);
          }
          insert(parentElm, vnode.elm, refElm);
        }

        if (data && data.pre) {
          creatingElmInVPre--;
        }
      } 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);
      }
    }

    function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
      var i = vnode.data;
      if (isDef(i)) {
        var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
        if (isDef(i = i.hook) && isDef(i = i.init)) {
          i(vnode, false /* hydrating */);
        }
        // 調(diào)用init鉤子后驾荣,如果vnode是子組件它應(yīng)該創(chuàng)建一個子實(shí)例并掛載它。這個子組件也設(shè)置了占位符vnode的榆樹普泡。
        //在這種情況下播掷,我們只需要返回元素就可以了。
        if (isDef(vnode.componentInstance)) {
          initComponent(vnode, insertedVnodeQueue);
          insert(parentElm, vnode.elm, refElm);
          if (isTrue(isReactivated)) {
            reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
          }
          return true
        }
      }
    }

    function initComponent (vnode, insertedVnodeQueue) {
      if (isDef(vnode.data.pendingInsert)) {
        insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
        vnode.data.pendingInsert = null;
      }
      vnode.elm = vnode.componentInstance.$el;
      
      if (isPatchable(vnode)) {
        // div#app的創(chuàng)建時會調(diào)用invokeCreateHooks
        invokeCreateHooks(vnode, insertedVnodeQueue);
        setScope(vnode);
      } else {
        // 根空的組件撼班。跳過所有元素相關(guān)的模塊歧匈,除了ref
        registerRef(vnode);
        // 確保調(diào)用了插入鉤子
        insertedVnodeQueue.push(vnode);
      }
    }

    function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
      var i;
      // 重新激活的內(nèi)部轉(zhuǎn)換組件不觸發(fā),因?yàn)閮?nèi)部節(jié)點(diǎn)創(chuàng)建的鉤子沒有被調(diào)用一次砰嘁。
      var innerNode = vnode;
      while (innerNode.componentInstance) {
        innerNode = innerNode.componentInstance._vnode;
        if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
          for (i = 0; i < cbs.activate.length; ++i) {
            cbs.activate[i](emptyNode, innerNode);
          }
          insertedVnodeQueue.push(innerNode);
          break
        }
      }
      // 與新創(chuàng)建的組件不同件炉,重新激活的keep-alive組件不插入自己
      insert(parentElm, vnode.elm, refElm);
    }
    // 通過insertBefore或者appendChild添加元素
    function insert (parent, elm, ref$$1) {
      if (isDef(parent)) {
        if (isDef(ref$$1)) {
          if (nodeOps.parentNode(ref$$1) === parent) {
            nodeOps.insertBefore(parent, elm, ref$$1);
          }
        } else {
          nodeOps.appendChild(parent, elm);
        }
      }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市矮湘,隨后出現(xiàn)的幾起案子斟冕,更是在濱河造成了極大的恐慌,老刑警劉巖板祝,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宫静,死亡現(xiàn)場離奇詭異,居然都是意外死亡券时,警方通過查閱死者的電腦和手機(jī)孤里,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橘洞,“玉大人捌袜,你說我怎么就攤上這事≌ㄔ妫” “怎么了虏等?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵弄唧,是天一觀的道長。 經(jīng)常有香客問我霍衫,道長候引,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任敦跌,我火速辦了婚禮澄干,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柠傍。我一直安慰自己麸俘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布惧笛。 她就那樣靜靜地躺著从媚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪患整。 梳的紋絲不亂的頭發(fā)上拜效,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機(jī)與錄音并级,去河邊找鬼拂檩。 笑死,一個胖子當(dāng)著我的面吹牛嘲碧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播父阻,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼愈涩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了加矛?” 一聲冷哼從身側(cè)響起履婉,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斟览,沒想到半個月后毁腿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苛茂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年已烤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妓羊。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡胯究,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躁绸,到底是詐尸還是另有隱情裕循,我是刑警寧澤臣嚣,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站剥哑,受9級特大地震影響硅则,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜株婴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一抢埋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧督暂,春花似錦揪垄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至八回,卻和暖如春酷愧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缠诅。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工溶浴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人管引。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓士败,卻偏偏與公主長得像,于是被迫代替她去往敵國和親褥伴。 傳聞我的和親對象是個殘疾皇子谅将,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容