史上最全的vue.js源码解析(三)

154 阅读6分钟

虽然vue3已经出来很久了,但我觉得vue.js的源码还是非常值得去学习一下。vue.js里面封装的很多工具类在我们平时工作项目中也会经常用到。所以我近期会对vue.js的源码进行解读,分享值得去学习的代码片段,这篇文章将会持续更新。

一、2400~4000代码有哪些内容?:

1.children 的规范化:normalizeArrayChildren 2.组件实例化:initInjections 3.slot插槽函数:resolveSlots,normalizeScopedSlots,normalizeScopedSlot,proxyNormalSlot,renderSlot 4.Vue 的各类渲染方法--辅助函数: markOnce;// 标记v-once toNumber;// 转换成Number类型 toString;//转换成字符串 renderList;//生成列表VNode renderSlot;//生成解析slot节点 looseEqual; looseIndexOf; renderStatic;//生成静态元素 resolveFilter;// 获取过滤器 checkKeyCodes;//检查键盘事件keycode bindObjectProps;//绑定对象属性 createTextVNode;//创建文本VNod createEmptyVNode;//创建空节点VNode resolveScopedSlots;//获取作用域插槽 bindObjectListeners;//处理v-on=’{}'到vnode data上 bindDynamicKeys;//处理动态属性名 prependModifier;//处理修饰符

二.2400~4000行代码的重点:

1.vue的事件机制 ①.监听事件:$on

②.监听事件,只监听1次:$once

③.移除自定义事件监听器:$off

④.触发事件: $emit

2.函数式组件的实现 createFunctionalComponent 3.组件的渲染和更新过程 componentVNodeHooks 在组件初始化的时候实现init、prepatch、insert、destroy钩子函数

三、2400~4000行的代码解读:

 //children 的规范化,simpleNormalizeChildren和normalizeChildren都是用来把children由树状结构变成一维数组
  // 模板编译器试图通过在编译时静态分析模板来最小化规范化的需要
  // 对于纯HTML标记,可以完全跳过规范化,因为生成的呈现函数保证返回Array<VNode>。有两种情况需要额外的规范化:
  // 当子级包含组件时-因为功能组件可能返回数组而不是单个根。在这种情况下,只需要一个简单的规范化—如果任何子对象是数组,
  // 我们就用Array.prototype.concat将整个对象展平。它保证只有1级深度,因为功能组件已经规范化了它们自己的子级
  function simpleNormalizeChildren (children) {
    for (var i = 0; i < children.length; i++) {
      if (Array.isArray(children[i])) {
        return Array.prototype.concat.apply([], children)
      }
    }
    return children
  }

  // 2.当子级包含总是生成嵌套数组的构造时,例如<template>、<slot>、v-for,或者当子级由用户提供手写的呈现函数/JSX时。
  // 在这种情况下,需要完全正常化,以满足所有可能类型的儿童价值观。
  function normalizeChildren (children) {
    return isPrimitive(children)
      ? [createTextVNode(children)]
      : Array.isArray(children)
        ? normalizeArrayChildren(children)
        : undefined
  }
  // node节点的判断条件
  function isTextNode (node) {
    return isDef(node) && isDef(node.text) && isFalse(node.isComment)
  }
  //children 的规范化,normalizeArrayChildren接收 2 个参数,children 表示要规范的子节点,nestedIndex 表示嵌套的索引
  function normalizeArrayChildren (children, nestedIndex) {
    var res = [];
    var i, c, lastIndex, last;
    //遍历children,
    for (i = 0; i < children.length; i++) {
      //将单个节点赋值给c
      c = children[i];
      //判断c的类型,如果是一个数组类型,则递归调用 normalizeArrayChildren; 
      //否则通过 createTextVNode 方法转换成 VNode 类型;
      if (isUndef(c) || typeof c === 'boolean') { continue }
      lastIndex = res.length - 1;
      last = res[lastIndex];
      //如果是一个数组类型,则递归调用 normalizeArrayChildren
      if (Array.isArray(c)) {
        if (c.length > 0) {
          // 如果 children 是一个列表并且列表还存在嵌套的情况,则根据 nestedIndex 去更新它的 key
          c = normalizeArrayChildren(c, ((nestedIndex || '') + "_" + i));
          if (isTextNode(c[0]) && isTextNode(last)) {
            res[lastIndex] = createTextVNode(last.text + (c[0]).text);
            c.shift();
          }
          res.push.apply(res, c);
        }
      } else if (isPrimitive(c)) {
        if (isTextNode(last)) {
          //通过 createTextVNode 方法转换成 VNode 类型
          res[lastIndex] = createTextVNode(last.text + c);
        } else if (c !== '') {
          res.push(createTextVNode(c));
        }
      } else {
        if (isTextNode(c) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + c.text);
        } else {
          if (isTrue(children._isVList) &&
            isDef(c.tag) &&
            isUndef(c.key) &&
            isDef(nestedIndex)) {
            c.key = "__vlist" + nestedIndex + "_" + i + "__";
          }
          res.push(c);
        }
      }
    }
    return res
  }

  /* initProvide的作用就是将$options里的provide赋值到当前实例上 */

  function initProvide (vm) {
    //如果provide存在,当它是函数时执行该返回,否则直接将provide保存到Vue实例的_provided属性上
    var provide = vm.$options.provide;
    if (provide) {
      vm._provided = typeof provide === 'function'
        ? provide.call(vm)
        : provide;
    }
  }
  //组件实例化,初始化Inject参数, initInjections在初始化data/props之前被调用,主要作用是初始化vue实例的inject
  function initInjections (vm) {
    //遍历祖先节点,获取对应的inject
    var result = resolveInject(vm.$options.inject, vm);
    if (result) {
      // toggleObserving是vue内部对逻辑的一个优化,就是禁止掉根组件 props的依赖收集
      toggleObserving(false);
      Object.keys(result).forEach(function (key) {
        //将key编程响应式,这样就可以访问该元素
        {
          defineReactive$$1(vm, key, result[key], function () {
            warn(
              "Avoid mutating an injected value directly since the changes will be " +
              "overwritten whenever the provided component re-renders. " +
              "injection being mutated: \"" + key + "\"",
              vm
            );
          });
        }
      });
      toggleObserving(true);
    }
  }
// 确定Inject,vm指当前组件的实例
  function resolveInject (inject, vm) {
    if (inject) {
      // inject is :any because flow is not smart enough to figure out cached
      var result = Object.create(null);
       //如果有符号类型,调用Reflect.ownKeys()返回所有的key,再调用filter
      var keys = hasSymbol
        ? Reflect.ownKeys(inject)
        : Object.keys(inject);
      //获取所有的key,此时keys就是个字符串数组
      for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        // #6574 in case the inject object is observed...
        if (key === '__ob__') { continue }
        var provideKey = inject[key].from;
        var source = vm;
        while (source) {
          //如果source存在_provided 且 含有provideKey这个属性,则将值保存到result[key]中
          if (source._provided && hasOwn(source._provided, provideKey)) {
            result[key] = source._provided[provideKey];
            break
          }
          // 否则将source赋值给父Vue实例,直到找到对应的providekey为止
          source = source.$parent;
        }
        // 如果最后source不存在,即没有从当前实例或祖先实例的_provide找到privideKey这个key
        if (!source) {
          // 如果有定义defult,则使用默认值
          if ('default' in inject[key]) {
            var provideDefault = inject[key].default;
            result[key] = typeof provideDefault === 'function'
              ? provideDefault.call(vm)
              : provideDefault;
          } else {
            warn(("Injection \"" + key + "\" not found"), vm);
          }
        }
      }
      return result
    }
  }

  /*  */



  /**
   * 主要作用是将children VNodes转化成一个slots对象,处理组件slot,返回slot插槽对象
   * children指占位符Vnode里的内容
   * context指占位符Vnode所在的vue实例
   */
  function resolveSlots (
    children,
    context
  ) {
    // 判断是否有children,即是否有插槽VNode
    if (!children || !children.length) {
      return {}
    }
    var slots = {};
  // 遍历每一个子节点
    for (var i = 0, l = children.length; i < l; i++) {
      var child = children[i];
    // data为VNodeData,保存父组件传递到子组件的props以及attrs等
      var data = child.data;
      //移出slot,删除该节点attrs的slot
      if (data && data.attrs && data.attrs.slot) {
        delete data.attrs.slot;
      }
      // 判断是否为具名插槽,如果为具名插槽,还需要 子组件 / 函数子组件 渲染上下文一致
      // 当需要向子组件的子组件传递具名插槽时,不会保持插槽的名字
      if ((child.context === context || child.fnContext === context) &&
        data && data.slot != null
      ) {
        var name = data.slot;
        var slot = (slots[name] || (slots[name] = []));
        //处理父组件采用template形式的插槽
        if (child.tag === 'template') {
          slot.push.apply(slot, child.children || []);
        } else {
          slot.push(child);
        }
      } else {
        //返回匿名default插槽VNode数组
        (slots.default || (slots.default = [])).push(child);
      }
    }
    // 忽略仅仅包含whitespace的插槽
    for (var name$1 in slots) {
      if (slots[name$1].every(isWhitespace)) {
        delete slots[name$1];
      }
    }
    return slots
  }
  // 方法用于判断指定字符是否为空白字符,空白符包含:空格、tab键、换行符
  function isWhitespace (node) {
    return (node.isComment && !node.asyncFactory) || node.text === ' '
  }

  /* 是否为异步占位 */

  function isAsyncPlaceholder (node) {
    return node.isComment && node.asyncFactory
  }

  /*normalizeScopedSlots函数的核心就是返回res对象,其key为slotTarget,value为fn  */
  //slots: 某节点 data 属性上 scopedSlots
  //normalSlots: 当前节点下的普通插槽
  //prevSlots 当前节点下的特殊插槽
  function normalizeScopedSlots (
    slots,
    normalSlots,
    prevSlots
  ) {
    var res;
    //判断是否拥有普通插槽
    var hasNormalSlots = Object.keys(normalSlots).length > 0;
    var isStable = slots ? !!slots.$stable : !hasNormalSlots;
    var key = slots && slots.$key;
    if (!slots) {
      res = {};
    } else if (slots._normalized) {
      return slots._normalized
    } else if (
      isStable &&
      prevSlots &&
      prevSlots !== emptyObject &&
       // slots $key 值与 prevSlots $key 相等
      key === prevSlots.$key &&
      //slots中没有普通插槽
      !hasNormalSlots &&
      //prevSlots中没有普通插槽
      !prevSlots.$hasNormal
    ) {
      return prevSlots
    } else {
      res = {};
      //遍历作用域插槽
      for (var key$1 in slots) {
        if (slots[key$1] && key$1[0] !== '$') {
          res[key$1] = normalizeScopedSlot(normalSlots, key$1, slots[key$1]);
        }
      }
    }
    // 对普通插槽进行遍历,将slot代理到scopeSlots上
    for (var key$2 in normalSlots) {
      if (!(key$2 in res)) {
        res[key$2] = proxyNormalSlot(normalSlots, key$2);
      }
    }
    // avoriaz seems to mock a non-extensible $scopedSlots object
    // and when that is passed down this would cause an error
    if (slots && Object.isExtensible(slots)) {
      (slots)._normalized = res;
    }
    // $key , $hasNormal , $stable 是直接使用 vue 内部对 Object.defineProperty 封装好的 def() 方法进行赋值的
    def(res, '$stable', isStable);
    def(res, '$key', key);
    def(res, '$hasNormal', hasNormalSlots);
    return res
  }
  //将scopeSlots对应属性和方法挂载到scopeSlots,生成闭包,返回一个名为normalized的函数,$scopedSlots对象中的值就是此函数
  function normalizeScopedSlot(normalSlots, key, fn) {
    var normalized = function () {
      var res = arguments.length ? fn.apply(null, arguments) : fn({});
      res = res && typeof res === 'object' && !Array.isArray(res)
        ? [res] // single vnode
        : normalizeChildren(res);
      var vnode = res && res[0];
      return res && (
        !vnode ||
        (res.length === 1 && vnode.isComment && !isAsyncPlaceholder(vnode)) // #9658, #10391
      ) ? undefined
        : res
    };
    // this is a slot using the new v-slot syntax without scope. although it is
    // compiled as a scoped slot, render fn users would expect it to be present
    // on this.$slots because the usage is semantically a normal slot.
    if (fn.proxy) {
      Object.defineProperty(normalSlots, key, {
        get: normalized,
        enumerable: true,
        configurable: true
      });
    }
    return normalized
  }
  // 将slot代理到scopeSlots上
  function proxyNormalSlot(slots, key) {
    return function () { return slots[key]; }
  }

  /*  */

  /**
   * 用于呈现v-for列表的运行时帮助程序
   */
  function renderList (
    val,
    render
  ) {
    var ret, i, l, keys, key;
    //如果val为数组,则遍历val
    if (Array.isArray(val) || typeof val === 'string') {
      ret = new Array(val.length);
      // 调用传入的函数,把值传入,数组保存结果
      for (i = 0, l = val.length; i < l; i++) {
        ret[i] = render(val[i], i);
      }
      //如果val为数字类型
    } else if (typeof val === 'number') {
      ret = new Array(val);
      // 调用传入的函数,把值传入,数组保存结果
      for (i = 0; i < val; i++) {
        ret[i] = render(i + 1, i);
      }
      //如果val为object类型,则遍历对象
    } else if (isObject(val)) {
      if (hasSymbol && val[Symbol.iterator]) {
        ret = [];
        var iterator = val[Symbol.iterator]();
        var result = iterator.next();
        // 调用传入的函数,把值传入,数组保存结果
        while (!result.done) {
          ret.push(render(result.value, ret.length));
          result = iterator.next();
        }
      } else {
        keys = Object.keys(val);
        ret = new Array(keys.length);
        for (i = 0, l = keys.length; i < l; i++) {
          key = keys[i];
          ret[i] = render(val[key], key, i);
        }
      }
    }
    if (!isDef(ret)) {
      ret = [];
    }
    (ret)._isVList = true;
    return ret
  }

  /*  */

  // 调用renderSlot用函数的返回值进行渲染
  // renderSlot函数会根据插槽名字找到对应的作用域Slot包装成的函数,
  // 然后执行它,把子组件内的数据{ child:child }传进去
  function renderSlot (
    name,//插槽名
    fallbackRender,//插槽默认内容生成的 vnode 数组
    props,// props 对象
    bindObject //v-bind 绑定对象
  ) {
    var scopedSlotFn = this.$scopedSlots[name];
    var nodes;
    if (scopedSlotFn) {
      props = props || {};
      if (bindObject) {
        if (!isObject(bindObject)) {
          warn('slot v-bind without argument expects an Object', this);
        }
        props = extend(extend({}, bindObject), props);
      }
      nodes =
        scopedSlotFn(props) ||
        (typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender);
    } else {
      nodes =
        this.$slots[name] ||
        (typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender);
    }

    var target = props && props.slot;
    if (target) {
      return this.$createElement('template', { slot: target }, nodes)
    } else {
      return nodes
    }
  }

  /*  */

  /**
   *找到我们写的过滤器,并将参数传入进去
   */
  function resolveFilter (id) {
    // resolveAsset用于获取资源,也就是获取组件的构造函数
    return resolveAsset(this.$options, 'filters', id, true) || identity
  }

  /* 检查按下的键,是否和配置的键值对匹配 */

  function isKeyNotMatch (expect, actual) {
    if (Array.isArray(expect)) {
      return expect.indexOf(actual) === -1
    } else {
      return expect !== actual
    }
  }

  /**
   * 用于检查config.prototype中的键代码的运行时帮助程序,以Vue.prototype的形式公开
   */
  function checkKeyCodes (
    eventKeyCode,
    key,
    builtInKeyCode,
    eventKeyName,
    builtInKeyName
  ) {
     // 比如 key 传入的是自定义名字  aaaa
    // keyCode 从Vue 定义的 keyNames 获取 aaaa 的实际数字
    // keyName 从 Vue 定义的 keyCode 获取 aaaa 的别名
    // 并且以用户定义的为准,可以覆盖Vue 内部定义的
    var mappedKeyCode = config.keyCodes[key] || builtInKeyCode;
    // 该键只在 Vue 内部定义的 keyCode 中
    if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {
      return isKeyNotMatch(builtInKeyName, eventKeyName)
    // 该键只在 用户自定义配置的 keyCode 中
    } else if (mappedKeyCode) {
      return isKeyNotMatch(mappedKeyCode, eventKeyCode)
      //原始键名
    } else if (eventKeyName) {
      return hyphenate(eventKeyName) !== key
    }
    return eventKeyCode === undefined
  }


  /**
   * 用于将v-bind=“object”合并到VNode数据中的运行时帮助程序
   */
  function bindObjectProps (
    data,
    tag,
    value,
    asProp,
    isSync
  ) {
    if (value) {
      if (!isObject(value)) {
        warn(
          'v-bind without argument expects an Object or Array value',
          this
        );
      } else {
        if (Array.isArray(value)) {
          value = toObject(value);
        }
        var hash;
        var loop = function ( key ) {
          if (
            key === 'class' ||
            key === 'style' ||
            isReservedAttribute(key)
          ) {
            hash = data;
          } else {
            var type = data.attrs && data.attrs.type;
            hash = asProp || config.mustUseProp(tag, type, key)
              ? data.domProps || (data.domProps = {})
              : data.attrs || (data.attrs = {});
          }
          var camelizedKey = camelize(key);
          var hyphenatedKey = hyphenate(key);
          if (!(camelizedKey in hash) && !(hyphenatedKey in hash)) {
            hash[key] = value[key];

            if (isSync) {
              var on = data.on || (data.on = {});
              on[("update:" + key)] = function ($event) {
                value[key] = $event;
              };
            }
          }
        };

        for (var key in value) loop( key );
      }
    }
    return data
  }

  /*  */

  /**
   * 生成静态元素
   */
  function renderStatic (
    index,
    isInFor
  ) {
    var cached = this._staticTrees || (this._staticTrees = []);
    var tree = cached[index];
    // if has already-rendered static tree and not inside v-for,
    // we can reuse the same tree.
    if (tree && !isInFor) {
      return tree
    }
    // otherwise, render a fresh tree.
    tree = cached[index] = this.$options.staticRenderFns[index].call(
      this._renderProxy,
      null,
      this // for render fns generated for functional component templates
    );
    markStatic(tree, ("__static__" + index), false);
    return tree
  }

  /**
   * 标记v-once
   */
  function markOnce (
    tree,
    index,
    key
  ) {
    markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true);
    return tree
  }
  // 标记静态元素
  function markStatic (
    tree,
    key,
    isOnce
  ) {
    if (Array.isArray(tree)) {
      for (var i = 0; i < tree.length; i++) {
        if (tree[i] && typeof tree[i] !== 'string') {
          markStaticNode(tree[i], (key + "_" + i), isOnce);
        }
      }
    } else {
      markStaticNode(tree, key, isOnce);
    }
  }
  //标记静态节点
  function markStaticNode (node, key, isOnce) {
    node.isStatic = true;
    node.key = key;
    node.isOnce = isOnce;
  }

  /* //处理v-on=’{}'到vnode data上 */

  function bindObjectListeners (data, value) {
    if (value) {
      if (!isPlainObject(value)) {
        warn(
          'v-on without argument expects an Object value',
          this
        );
      } else {
        var on = data.on = data.on ? extend({}, data.on) : {};
        for (var key in value) {
          var existing = on[key];
          var ours = value[key];
          on[key] = existing ? [].concat(existing, ours) : ours;
        }
      }
    }
    return data
  }

  /* 获取作用域插槽 */

  function resolveScopedSlots (
    fns, // see flow/vnode
    res,
    // the following are added in 2.6
    hasDynamicKeys,
    contentHashKey
  ) {
    res = res || { $stable: !hasDynamicKeys };
    for (var i = 0; i < fns.length; i++) {
      var slot = fns[i];
      if (Array.isArray(slot)) {
        resolveScopedSlots(slot, res, hasDynamicKeys);
      } else if (slot) {
        // marker for reverse proxying v-slot without scope on this.$slots
        if (slot.proxy) {
          slot.fn.proxy = true;
        }
        res[slot.key] = slot.fn;
      }
    }
    if (contentHashKey) {
      (res).$key = contentHashKey;
    }
    return res
  }

  /*//处理动态属性名  */

  function bindDynamicKeys (baseObj, values) {
    for (var i = 0; i < values.length; i += 2) {
      var key = values[i];
      if (typeof key === 'string' && key) {
        baseObj[values[i]] = values[i + 1];
      } else if (key !== '' && key !== null) {
        // null is a special value for explicitly removing a binding
        warn(
          ("Invalid value for dynamic directive argument (expected string or null): " + key),
          this
        );
      }
    }
    return baseObj
  }

  //处理修饰符
  // 帮助程序将修改器运行时标记动态追加到事件名称。
  // 请确保仅在值已为字符串时追加,否则将转换为字符串并导致类型检查丢失。
  function prependModifier (value, symbol) {
    return typeof value === 'string' ? symbol + value : value
  }

  /* 安装渲染工具函数,大多数服务于编译器编译出来的代码 */
  function installRenderHelpers (target) {
    target._o = markOnce;// 标记v-once
    target._n = toNumber;// 转换成Number类型
    target._s = toString;//转换成字符串
    target._l = renderList;//生成列表VNode
    target._t = renderSlot;//生成解析slot节点
    target._q = looseEqual;
    target._i = looseIndexOf;
    target._m = renderStatic;//生成静态元素
    target._f = resolveFilter;// 获取过滤器
    target._k = checkKeyCodes;//检查键盘事件keycode
    target._b = bindObjectProps;//绑定对象属性
    target._v = createTextVNode;//创建文本VNod
    target._e = createEmptyVNode;//创建空节点VNode
    target._u = resolveScopedSlots;//获取作用域插槽
    target._g = bindObjectListeners;//处理v-on=’{}'到vnode data上
    target._d = bindDynamicKeys;//处理动态属性名
    target._p = prependModifier;//处理修饰符
  }

  /*  */
  //创建一个包含渲染要素的函数
  function FunctionalRenderContext (
    data,//组件的数据
    props,//父组件传递过来的数据
    children,//引用该组件时定义的子节点
    parent,
    Ctor//组件的构造对象(Vue.extend()里的那个Sub函数)
  ) {
    var this$1 = this;

    var options = Ctor.options;
    // 确保functional components中的createElement函数获得唯一的上下文
    var contextVm;
    if (hasOwn(parent, '_uid')) {
      contextVm = Object.create(parent);
      contextVm._original = parent;
    } else {
      //确保能够获得对真实上下文实例的保留
      contextVm = parent;
      parent = parent._original;
    }
    var isCompiled = isTrue(options._compiled);
    var needNormalization = !isCompiled;

    this.data = data;
    this.props = props;
    this.children = children;
    this.parent = parent;
    this.listeners = data.on || emptyObject;
    this.injections = resolveInject(options.inject, parent);
    this.slots = function () {
      if (!this$1.$slots) {
        normalizeScopedSlots(
          data.scopedSlots,
          this$1.$slots = resolveSlots(children, parent)
        );
      }
      return this$1.$slots
    };

    Object.defineProperty(this, 'scopedSlots', ({
      enumerable: true,
      get: function get () {
        return normalizeScopedSlots(data.scopedSlots, this.slots())
      }
    }));

    // 对已编译函数模板的支持
    if (isCompiled) {
      // 公开renderStatic()的$options
      this.$options = options;
      // 预解析renderSlot()的插槽
      this.$slots = this.slots();
      this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots);
    }

    if (options._scopeId) {
      this._c = function (a, b, c, d) {
        var vnode = createElement(contextVm, a, b, c, d, needNormalization);
        if (vnode && !Array.isArray(vnode)) {
          vnode.fnScopeId = options._scopeId;
          vnode.fnContext = parent;
        }
        return vnode
      };
    } else {
      this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); };
    }
  }
  //渲染辅助函数
  installRenderHelpers(FunctionalRenderContext.prototype);
  //函数式组件的实现
  function createFunctionalComponent (
    Ctor,//Ctro:组件的构造对象(Vue.extend()里的那个Sub函数)
    propsData, //propsData:父组件传递过来的数据(还未验证)
    data,//data:组件的数据
    contextVm, //contextVm:Vue实例 
    children //children:引用该组件时定义的子节点
  ) {
    var options = Ctor.options;
    var props = {};
    var propOptions = options.props;
     //如果propOptions非空(父组件向当前组件传入了信息),则遍历propOptions
    if (isDef(propOptions)) {
      for (var key in propOptions) {
        // 调用validateProp()依次进行检验
        props[key] = validateProp(key, propOptions, propsData || emptyObject);
      }
    } else {
      if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
      if (isDef(data.props)) { mergeProps(props, data.props); }
    }
    //创建一个函数的上下文
    var renderContext = new FunctionalRenderContext(
      data,
      props,
      children,
      contextVm,
      Ctor
    );
    //执行render函数,参数1为createElement,参数2为renderContext,也就是我们在组件内定义的render函数
    var vnode = options.render.call(null, renderContext._c, renderContext);

    if (vnode instanceof VNode) {
      // 为了避免复用节点,fnContext 导致命名槽点不匹配的情况,
      // 直接在设置 fnContext 之前克隆节点,最后返回克隆好的 vnode
      return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
    } else if (Array.isArray(vnode)) {
      var vnodes = normalizeChildren(vnode) || [];
      var res = new Array(vnodes.length);
      for (var i = 0; i < vnodes.length; i++) {
        res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext);
      }
      return res
    }
  }
  // 为了避免复用节点,fnContext 导致命名槽点不匹配的情况,
  // 直接在设置 fnContext 之前克隆节点,最后返回克隆好的 vnode
  function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
    // 在设置fnContext之前克隆节点,否则,如果重复使用该节点
    // (例如,它来自缓存的正常插槽),fnContext将导致不应匹配的命名插槽。
    var clone = cloneVNode(vnode);
    clone.fnContext = contextVm;
    clone.fnOptions = options;
    {
      (clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext;
    }
    if (data.slot) {
      (clone.data || (clone.data = {})).slot = data.slot;
    }
    return clone
  }
  // 将包含 VNode prop 的多个对象合并为一个单独的对象;
  function mergeProps (to, from) {
    for (var key in from) {
      to[camelize(key)] = from[key];
    }
  }

  //component初始化和更新的方法,
  // 是组件初始化的时候实现的几个钩子函数,分别有 init、prepatch、insert、destroy
  var componentVNodeHooks = {
    // 当 vnode 为 keep-alive 组件时、存在实例且没被销毁,为了防止组件流动,
    // 直接执行了 prepatch。否则直接通过执行 createComponentInstanceForVnode 
    // 创建一个 Component 类型的 vnode 实例,并进行 $mount 操作
    init: function init (vnode, hydrating) {
      if (
        vnode.componentInstance &&
        !vnode.componentInstance._isDestroyed &&
        vnode.data.keepAlive
      ) {
        // kept-alive components, treat as a patch
        var mountedNode = vnode; // work around flow
        componentVNodeHooks.prepatch(mountedNode, mountedNode);
      } else {
        //根据Vnode生成VueComponent实例
        var child = vnode.componentInstance = createComponentInstanceForVnode(
          vnode,
          activeInstance
        );
        //将VueComponent实例挂载到dom节点上,本文是挂载到<my-component></my-component>节点
        child.$mount(hydrating ? vnode.elm : undefined, hydrating);
      }
    },
    // 将已有组件更新成最新的 vnode 上的数据
    prepatch: function prepatch (oldVnode, vnode) {
      var options = vnode.componentOptions;
      var child = vnode.componentInstance = oldVnode.componentInstance;
      updateChildComponent(
        child,
        options.propsData, // 更新props
        options.listeners, // 更新listeners
        vnode, // 创建父节点
        options.children //创建子节点
      );
    },
    //insert钩子函数
    insert: function insert (vnode) {
      var context = vnode.context;
      var componentInstance = vnode.componentInstance;
      //判断组件实例是否已经被mounted,
      if (!componentInstance._isMounted) {
        // 直接将componentInstance作为参数执行mounted钩子函数
        componentInstance._isMounted = true;
        callHook(componentInstance, 'mounted');
      }
      //如果组件为kepp-alive内置组件
      if (vnode.data.keepAlive) {
        //如果组件已经mounted
        if (context._isMounted) {
          // 为了防止 keep-alive 子组件更新触发 activated 钩子函数,
          // 直接就放弃了 walking tree 的更新机制,而是直接将组件实例 componentInstance 
          // 丢到 activatedChildren 这个数组中
          queueActivatedComponent(componentInstance);
        } else {
          // 否则直接出发activated钩子函数进行mounted
          activateChildComponent(componentInstance, true);
        }
      }
    },
    // 组件销毁函数
    destroy: function destroy (vnode) {
      var componentInstance = vnode.componentInstance;
      if (!componentInstance._isDestroyed) {
        // 如果不是 keep-alive 组件,直接执行 $destory 销毁组件实例,
        if (!vnode.data.keepAlive) {
          componentInstance.$destroy();
          // 否则触发 deactivated 钩子函数进行销毁
        } else {
          deactivateChildComponent(componentInstance, true /* direct */);
        }
      }
    }
  };

  var hooksToMerge = Object.keys(componentVNodeHooks);

  function createComponent (
    Ctor,
    data,
    context,
    children,
    tag
  ) {
    if (isUndef(Ctor)) {
      return
    }

    var baseCtor = context.$options._base;

    // 普通选项对象:将其转换为构造函数
    if (isObject(Ctor)) {
      Ctor = baseCtor.extend(Ctor);
    }

    // 如果在此阶段不是构造函数或异步组件工厂则提示
    if (typeof Ctor !== 'function') {
      {
        warn(("Invalid Component definition: " + (String(Ctor))), context);
      }
      return
    }

    // 异步组件
    var asyncFactory;
    if (isUndef(Ctor.cid)) {
      asyncFactory = Ctor;
      // 处理了 3 种异步组件的创建方式
      Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
      if (Ctor === undefined) {
        // 异步组件 patch,创建一个注释节点作为占位符
        return createAsyncPlaceholder(
          asyncFactory,
          data,
          context,
          children,
          tag
        )
      }
    }
    data = data || {};
    // 解析constructor上的options属性
    resolveConstructorOptions(Ctor);
    //将组件v-modal数据转换为props和events
    if (isDef(data.model)) {
      transformModel(Ctor.options, data);
    }

    //提取props
    var propsData = extractPropsFromVNodeData(data, Ctor, tag);

    //创建虚拟dom组件
    if (isTrue(Ctor.options.functional)) {
      return createFunctionalComponent(Ctor, propsData, data, context, children)
    }

    // 提取侦听器,因为这些侦听器需要作为子组件侦听器而不是DOM侦听器
    var listeners = data.on;
    // 替换为带有.native修饰符的侦听器,以便在父组件修补程序期间对其进行处理
    data.on = data.nativeOn;

    if (isTrue(Ctor.options.abstract)) {
      // 抽象组件只保留props、侦听器和插槽
      var slot = data.slot;
      data = {};
      if (slot) {
        data.slot = slot;
      }
    }

    // 在占位符节点上安装组件管理挂钩
    installComponentHooks(data);

    // 返回一个占位符节点
    var name = Ctor.options.name || tag;
    var vnode = new VNode(
      ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
      data, undefined, undefined, undefined, context,
      { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
      asyncFactory
    );

    return vnode
  }

  function createComponentInstanceForVnode (
     // 当前组件的vnode
    vnode,
    // 当前的vue实例 就是div#app,也就是当前组件的父vue实例
    parent
  ) {
    // 增加 component 特有options
    var options = {
      _isComponent: true,
      _parentVnode: vnode,// 这里就是站位父VNode,也就是app.vue的占位符
      parent: parent// vue实例
    };
    // 检查内联模板渲染函数
    var inlineTemplate = vnode.data.inlineTemplate;
    if (isDef(inlineTemplate)) {
      options.render = inlineTemplate.render;
      options.staticRenderFns = inlineTemplate.staticRenderFns;
    }
    return new vnode.componentOptions.Ctor(options)
  }
  // 安装组件钩子函数,等待patch过程时去执行
  function installComponentHooks (data) {
    var hooks = data.hook || (data.hook = {});
    // 遍历hooksToMerge,不断向data.hook插入componentVNodeHooks对象中对应的钩子函数
    for (var i = 0; i < hooksToMerge.length; i++) {
      var key = hooksToMerge[i];
      var existing = hooks[key];
      var toMerge = componentVNodeHooks[key];
      if (existing !== toMerge && !(existing && existing._merged)) {
        hooks[key] = existing ? mergeHook$1(toMerge, existing) : toMerge;
      }
    }
  }

  function mergeHook$1 (f1, f2) {
    var merged = function (a, b) {
      f1(a, b);
      f2(a, b);
    };
    merged._merged = true;
    return merged
  }

  // 将组件v-modal(值和回调)转换为prop和event。
  function transformModel (options, data) {
    // 默认prop是value
    var prop = (options.model && options.model.prop) || 'value';
    // 默认event是Input
    var event = (options.model && options.model.event) || 'input';
     // 保存到props属性中
    (data.attrs || (data.attrs = {}))[prop] = data.model.value;
     // 将input事件添加到on对象中
    var on = data.on || (data.on = {});
    var existing = on[event];
    var callback = data.model.callback;
    if (isDef(existing)) {
      if (
        Array.isArray(existing)
          ? existing.indexOf(callback) === -1
          : existing !== callback
      ) {
        on[event] = [callback].concat(existing);
      }
    } else {
      on[event] = callback;
    }
  }

  /*  */

  var SIMPLE_NORMALIZE = 1;
  var ALWAYS_NORMALIZE = 2;

  // 包装器函数,用于提供更灵活的接口
  function createElement (
    context,
    tag,
    data,
    children,
    normalizationType,
    alwaysNormalize
  ) {
    // 首先检测data的类型,通过判断data是不是数组,以及是不是基本类型,来判断data是否传入
    // 如果传入,则将所有的参数向前赋值
    if (Array.isArray(data) || isPrimitive(data)) {
      normalizationType = children;
      children = data;
      data = undefined;
    }
    if (isTrue(alwaysNormalize)) {
      normalizationType = ALWAYS_NORMALIZE;
    }
    // 首先判断data是不是响应式的,vnode中的data不能是响应式的。如果是,则Vue抛出警告
    return _createElement(context, tag, data, children, normalizationType)
  }
  
  function _createElement (
    context,//context表示vnode上上线文环境
    tag,
    data,//vnode数据
    children,//当前vnode子节点
    normalizationType
  ) {
    // 首先判断data是不是响应式的,vnode中的data不能是响应式的。如果是,则Vue抛出警告
    if (isDef(data) && isDef((data).__ob__)) {
      warn(
        "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" +
        'Always create fresh vnode data objects in each render!',
        context
      );
      return createEmptyVNode()
    }
    //v-bind中的对象语法
    if (isDef(data) && isDef(data.is)) {
      tag = data.is;
    }
    if (!tag) {
      // 如果是组件:则设置为falsy值
      return createEmptyVNode()
    }
    // 如果data.key不属于基本类型,则发出警告
    if (isDef(data) && isDef(data.key) && !isPrimitive(data.key)
    ) {
      {
        warn(
          'Avoid using non-primitive value as key, ' +
          'use string/number value instead.',
          context
        );
      }
    }
    // 支持单功能子项作为默认作用域插槽
    if (Array.isArray(children) &&
      typeof children[0] === 'function'
    ) {
      data = data || {};
      data.scopedSlots = { default: children[0] };
      children.length = 0;
    }
    // 当alwaysNormalize等于2的时候,调用normalizeChildren去处理children类数组
    if (normalizationType === ALWAYS_NORMALIZE) {
      children = normalizeChildren(children);
      // 当alwaysNormalize等于1的时候,调用调用normalizeChildren去处理children类数组
    } else if (normalizationType === SIMPLE_NORMALIZE) {
      children = simpleNormalizeChildren(children);
    }
    var vnode, ns;
    // 判断tag的类型,如果是string就创建普通dom
    if (typeof tag === 'string') {
      var Ctor;
      ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
      if (config.isReservedTag(tag)) {
        // 如果存在data.nativeOn并且data.tag不等于component时,发出警告
        if (isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
          warn(
            ("The .native modifier for v-on is only valid on components but it was used on <" + tag + ">."),
            context
          );
        }
        vnode = new VNode(
          config.parsePlatformTagName(tag), data, children,
          undefined, undefined, context
        );
      } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
        //创建组件
        vnode = createComponent(Ctor, data, context, children, tag);
      } else {
        // 未知或未列出的命名空间元素在运行时检查,因为当其父元素规范化子元素时,可能会为其分配命名空间
        vnode = new VNode(
          tag, data, children,
          undefined, undefined, context
        );
      }
    } else {
      // 如果不是string就会调用createComponent创建组件
      vnode = createComponent(tag, data, context, children);
    }
    if (Array.isArray(vnode)) {
      return vnode
    } else if (isDef(vnode)) {
      //如果存在vnode和ns,则将ns赋值给vnode.ns
      if (isDef(ns)) { applyNS(vnode, ns); }
      if (isDef(data)) { registerDeepBindings(data); }
      return vnode
    } else {
      return createEmptyVNode()
    }
  }
  //通过递归的方法把ns赋值给vnode.ns
  function applyNS (vnode, ns, force) {
    vnode.ns = ns;
    // 如果vnode.tag等于foreignObject则ns赋值为undefined
    if (vnode.tag === 'foreignObject') {
      //在foreignObject中使用默认命名空间
      ns = undefined;
      force = true;
    }
    //如果vnode有子节点则进行递归循环
    if (isDef(vnode.children)) {
      for (var i = 0, l = vnode.children.length; i < l; i++) {
        var child = vnode.children[i];
        if (isDef(child.tag) && (
          isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) {
          applyNS(child, ns, force);
        }
      }
    }
  }

  // 确保父级在深度绑定的样式和类在插槽节点上使用
  function registerDeepBindings (data) {
    if (isObject(data.style)) {
    //如果有data.style,则调用traverse进行深度监听
      traverse(data.style);
    }
    //如果有data.class,则调用traverse进行深度监听
    if (isObject(data.class)) {
      traverse(data.class);
    }
  }
  function initRender (vm) {
    vm._vnode = null; // 子树的根
    vm._staticTrees = null; // v-once缓存树
    var options = vm.$options;
    var parentVnode = vm.$vnode = options._parentVnode; // 父树中的占位符节点
    var renderContext = parentVnode && parentVnode.context;
    vm.$slots = resolveSlots(options._renderChildren, renderContext);
    vm.$scopedSlots = emptyObject;
    //将createElement fn绑定到此实例
    //这样我们就可以在其中获得适当的渲染上下文。
    //参数顺序:标记、数据、子项、规范化类型、alwaysNormalize
    //内部版本由从模板编译的渲染函数使用
    vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
    // 规范化始终应用于公共版本,用于用户编写的渲染函数。
    vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

    // $attrs&amp;$listeners已公开,以便更轻松地进行临时创建。它们需要是反应式的,以便使用它们的HOC始终得到更新
    var parentData = parentVnode && parentVnode.data;

    {
      // Vue的data监听,也是通过defineReactive$$1方法
      defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
        !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
      }, true);
      defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
        !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
      }, true);
    }
  }

  var currentRenderingInstance = null;

  function renderMixin (Vue) {
    // 里面是一些渲染时候的帮助方法,全部挂载到Vue.prototype上,比如创建空节点、创建文本节点、toNumber、toString等等
    installRenderHelpers(Vue.prototype);
    // 使用异步函数来执行
    Vue.prototype.$nextTick = function (fn) {
      return nextTick(fn, this)
    };
// 调用render函数
    Vue.prototype._render = function () {
      var vm = this;
      var ref = vm.$options;
      var render = ref.render;
      var _parentVnode = ref._parentVnode;

      if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
          _parentVnode.data.scopedSlots,
          vm.$slots,
          vm.$scopedSlots
        );
      }

      // 设置父vnode。这允许渲染函数具有访问权限添加到占位符节点上的数据。
      vm.$vnode = _parentVnode;
      // render self
      var vnode;
      try {
        // There's no need to maintain a stack because all render fns are called
        // separately from one another. Nested component's render fns are called
        // when parent component is patched.
        currentRenderingInstance = vm;
        vnode = render.call(vm._renderProxy, vm.$createElement);
      } catch (e) {
        handleError(e, vm, "render");
        // 返回错误渲染结果,或上一个vnode,以防止渲染错误导致空白组件
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
            handleError(e, vm, "renderError");
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      } finally {
        currentRenderingInstance = null;
      }
      // 如果返回的数组只包含一个节点
      if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0];
      }
      // 如果渲染函数出错,则返回空vnode
      if (!(vnode instanceof VNode)) {
        if (Array.isArray(vnode)) {
          warn(
            'Multiple root nodes returned from render function. Render function ' +
            'should return a single root node.',
            vm
          );
        }
        //给vnode赋值一个空节点
        vnode = createEmptyVNode();
      }
      //设置父节点
      vnode.parent = _parentVnode;
      return vnode
    };
  }

  /* 是为了保证能找到异步组件上定义的组件对象而定义的函数 */

  function ensureCtor (comp, base) {
    // 如果发现它是普通对象,则直接通过 Vue.extend 将其转换成组件的构造函数
    if (
      comp.__esModule ||
      (hasSymbol && comp[Symbol.toStringTag] === 'Module')
    ) {
      comp = comp.default;
    }
    return isObject(comp)
      ? base.extend(comp)
      : comp
  }
  // 返回呈现的异步组件的占位符节点作为注释节点,但保留节点的所有原始信息,这些信息将用于异步服务器渲染和同步
  function createAsyncPlaceholder (
    factory,
    data,
    context,
    children,
    tag
  ) {
    var node = createEmptyVNode();
    node.asyncFactory = factory;
    node.asyncMeta = { data: data, context: context, children: children, tag: tag };
    return node
  }

  function resolveAsyncComponent (
    factory,//factory:异步组件的函数
    baseCtor
  ) {
    if (isTrue(factory.error) && isDef(factory.errorComp)) {
      return factory.errorComp
    }
    // 工厂函数异步组件第二次执行这里时会返回factory.resolved
    if (isDef(factory.resolved)) {
      return factory.resolved
    }

    var owner = currentRenderingInstance;
    if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
      // already pending
      factory.owners.push(owner);
    }

    if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
      return factory.loadingComp
    }

    if (owner && !isDef(factory.owners)) {
      var owners = factory.owners = [owner];
      var sync = true;
      var timerLoading = null;
      var timerTimeout = null;

      (owner).$on('hook:destroyed', function () { return remove(owners, owner); });
      // 遍历contexts里的所有元素,依次调用该元素的$forceUpdate()方法 该方法会强制渲染一次
      var forceRender = function (renderCompleted) {
        for (var i = 0, l = owners.length; i < l; i++) {
          (owners[i]).$forceUpdate();
        }

        if (renderCompleted) {
          owners.length = 0;
          if (timerLoading !== null) {
            clearTimeout(timerLoading);
            timerLoading = null;
          }
          if (timerTimeout !== null) {
            clearTimeout(timerTimeout);
            timerTimeout = null;
          }
        }
      };
      //定义一个resolve函数
      var resolve = once(function (res) {
        // 缓存解析
        factory.resolved = ensureCtor(res, baseCtor);
        // 当这不是同步解析时调用回调
        if (!sync) {
          forceRender(true);
        } else {
          owners.length = 0;
        }
      });
       //定义一个reject函数
      var reject = once(function (reason) {
        warn(
          "Failed to resolve async component: " + (String(factory)) +
          (reason ? ("\nReason: " + reason) : '')
        );
        if (isDef(factory.errorComp)) {
          factory.error = true;
          forceRender(true);
        }
      });
      //执行factory()函数
      var res = factory(resolve, reject);

      if (isObject(res)) {
        if (isPromise(res)) {
          // () => Promise
          if (isUndef(factory.resolved)) {
            res.then(resolve, reject);
          }
        } else if (isPromise(res.component)) {
          res.component.then(resolve, reject);

          if (isDef(res.error)) {
            factory.errorComp = ensureCtor(res.error, baseCtor);
          }

          if (isDef(res.loading)) {
            factory.loadingComp = ensureCtor(res.loading, baseCtor);
            if (res.delay === 0) {
              factory.loading = true;
            } else {
              timerLoading = setTimeout(function () {
                timerLoading = null;
                if (isUndef(factory.resolved) && isUndef(factory.error)) {
                  factory.loading = true;
                  forceRender(false);
                }
              }, res.delay || 200);
            }
          }

          if (isDef(res.timeout)) {
            timerTimeout = setTimeout(function () {
              timerTimeout = null;
              if (isUndef(factory.resolved)) {
                reject(
                  "timeout (" + (res.timeout) + "ms)"
                );
              }
            }, res.timeout);
          }
        }
      }

      sync = false;
      // 在同步解决的情况下返回
      return factory.loading
        ? factory.loadingComp
        : factory.resolved
    }
  }

  /*获取第一个组件的子节点  */
  function getFirstComponentChild (children) {
    //循环子节点,判断子节点是否位异步占位符
    if (Array.isArray(children)) {
      for (var i = 0; i < children.length; i++) {
        var c = children[i];
        if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
          return c
        }
      }
    }
  }
//初始化事件
  function initEvents (vm) {
    vm._events = Object.create(null);
    vm._hasHookEvent = false;
    //初始化父附加事件
    var listeners = vm.$options._parentListeners;
    if (listeners) {
      //修改组件的监听器
      updateComponentListeners(vm, listeners);
    }
  }

  var target;
//添加监听事件
  function add (event, fn) {
    target.$on(event, fn);
  }
//移出自定义事件监听器
  function remove$1 (event, fn) {
    target.$off(event, fn);
  }
  // 在执行完回调之后,移除事件绑定
  function createOnceHandler (event, fn) {
    var _target = target;
    return function onceHandler () {
      var res = fn.apply(null, arguments);
      if (res !== null) {
        _target.$off(event, onceHandler);
      }
    }
  }
//更新组件的监听器
  function updateComponentListeners (
    vm,
    listeners,
    oldListeners
  ) {
    target = vm;
    // 调用updateListeners来修改监听配置
    updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm);
    target = undefined;
  }

  function eventsMixin (Vue) {
    var hookRE = /^hook:/;
    // 监听事件
    Vue.prototype.$on = function (event, fn) {
      var vm = this;
      //当传入的监听事件为数组,则循环遍历调用$on,否则将监听事件和回调函数添加到事件处理中心_events对象中
      if (Array.isArray(event)) {
        for (var i = 0, l = event.length; i < l; i++) {
          vm.$on(event[i], fn);
        }
      } else {
        // 之前已经有监听event事件,则将此次监听的回调函数添加到其数组中,否则创建一个新数组并添加fn
        (vm._events[event] || (vm._events[event] = [])).push(fn);
        if (hookRE.test(event)) {
          vm._hasHookEvent = true;
        }
      }
      return vm
    };
    //监听事件,只监听1次, Vue中的事件机制,Vue中的事件机制本身就是一个订阅-发布模式的实现
    Vue.prototype.$once = function (event, fn) {
      var vm = this;
       // 定义监听事件的回调函数
      function on () {
        // 从事件中心移除监听事件的回调函数
        vm.$off(event, on); 
        // 执行回调函数
        fn.apply(vm, arguments);
      }
       // 这个赋值是在$off方法里会用到的
    // 比如我们调用了vm.$off(fn)来移除fn回调函数,然而我们在调用$once的时候,实际执行的是vm.$on(event, on)
    // 所以在event的回调函数数组里添加的是on函数,这个时候要移除fn,我们无法在回调函数数组里面找到fn函数移除,只能找到on函数
    // 我们可以通过on.fn === fn来判断这种情况,并在回调函数数组里移除on函数
      on.fn = fn;
      // 通过$on方法注册事件,$once最终调用的是$on,并且回调函数是on
      vm.$on(event, on);
      return vm
    };
    // 移除自定义事件监听器。
    // 如果没有提供参数,则移除所有的事件监听器;
    // 如果只提供了事件,则移除该事件所有的监听器;
    // 如果同时提供了事件与回调,则只移除这个回调的监听器。
    Vue.prototype.$off = function (event, fn) {
      var vm = this;
      //调用this.$off()没有传参数,则清空事件处理中心缓存的事件及其回调
      if (!arguments.length) {
        vm._events = Object.create(null);
        return vm
      }
      //当event为数组,则循环遍历调用$off,否则将监听事件和回调函数添加到事件处理中心_events对象中
      if (Array.isArray(event)) {
        for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
          vm.$off(event[i$1], fn);
        }
        return vm
      }
     // 获取当前event里所有的回调函数
      var cbs = vm._events[event];
      // 如果不存在回调函数,则直接返回,因为没有可以移除监听的内容
      if (!cbs) {
        return vm
      }
      // 如果没有指定要移除的回调函数,则移除该事件下所有的回调函数
      if (!fn) {
        vm._events[event] = null;
        return vm
      }
      // 指定了要移除的回调函数
      var cb;
      var i = cbs.length;
      while (i--) {
        cb = cbs[i];
        // 在事件对应的回调函数数组里面找出要移除的回调函数,并从数组里移除
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1);
          break
        }
      }
      return vm
    };
    // 触发事件
    Vue.prototype.$emit = function (event) {
      var vm = this;
      {
        var lowerCaseEvent = event.toLowerCase();
        if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
          tip(
            "Event \"" + lowerCaseEvent + "\" is emitted in component " +
            (formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +
            "Note that HTML attributes are case-insensitive and you cannot use " +
            "v-on to listen to camelCase events when using in-DOM templates. " +
            "You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
          );
        }
      }
      // 触发事件对应的回调函数列表
      var cbs = vm._events[event];
      if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
         // $emit方法可以传参,这些参数会在调用回调函数的时候传进去
        var args = toArray(arguments, 1);
        var info = "event handler for \"" + event + "\"";
        // 遍历回调函数列表,调用每个回调函数
        for (var i = 0, l = cbs.length; i < l; i++) {
          invokeWithErrorHandling(cbs[i], vm, args, vm, info);
        }
      }
      return vm
    };
  }