[
  {
    "path": "README.md",
    "content": "# learnVue\n\n## 介绍\n\nVue.js源码分析，记录了个人学习Vue.js源码的过程中的一些心得以及收获。以及对于Vue框架，周边库的一些个人见解。\n\n在学习的过程中我为Vue.js（2.3.0）、Vuex（2.4.0）、Vue-router（3.0.1）加上了注释，分别在文件夹[vue-src](./vue-src)、[vuex-src](./vuex-src)以及[vue-router-src](./vue-router-src)中，希望可以帮助有需要的同学更好地学习理解Vue.js及周边库的源码。\n\n感谢[尤大](https://github.com/yyx990803)提高生产力。\n\n本项目希望对Vue.js做更进一步的探索与学习，Vue.js基础内容请参考Vue.js官网，[https://cn.vuejs.org/v2/guide/](https://cn.vuejs.org/v2/guide/)。\n可能会有理解存在偏差的地方，欢迎提issue指出，共同学习，共同进步。\n\n---\n\n## 目录\n\n### 源码相关\n\n[Vue.js响应式原理](./docs/响应式原理.MarkDown)\n\n[Vue.js依赖收集](./docs/依赖收集.MarkDown)\n\n[从Vue.js源码角度再看数据绑定](./docs/从源码角度再看数据绑定.MarkDown)\n\n[Vue.js事件机制](./docs/Vue事件机制.MarkDown)\n\n[VNode节点(Vue.js实现)](./docs/VNode节点.MarkDown)\n\n[Virtual DOM与diff(Vue.js实现)](./docs/VirtualDOM与diff(Vue实现).MarkDown)\n\n[聊聊Vue.js的template编译](./docs/聊聊Vue的template编译.MarkDown)\n\n[Vue.js异步更新DOM策略及nextTick](./docs/Vue.js异步更新DOM策略及nextTick.MarkDown)\n\n[从template到DOM（Vue.js源码角度看内部运行机制）](./docs/从template到DOM(Vue.js源码角度看内部运行机制).MarkDown)\n\n[Vuex源码解析](./docs/Vuex源码解析.MarkDown)\n\n[聊聊keep-alive组件的使用及其实现原理](./docs/聊聊keep-alive组件的使用及其实现原理.MarkDown)\n\n### 随笔杂谈\n\n[Vue组件间通信](./docs/Vue组件间通信.MarkDown)\n\n[说说element组件库broadcast与dispatch](./docs/说说element组件库broadcast与dispatch.MarkDown)\n\n---\n\n## 对于新手同学\n\n由于以上内容都是针对 Vue.js 源码进行讲解了，可能有一些不太熟悉源码的同学读起来感觉晦涩难懂。\n\n笔者撰写的[《剖析 Vue.js 内部运行机制》](https://juejin.im/book/5a36661851882538e2259c0f)或许可以帮到你。\n\n\n## 关于作者\n\n作者： 染陌\n\nEmail：answershuto@gmail.com\n\nGithub: [https://github.com/answershuto](https://github.com/answershuto)\n\n知乎：[https://www.zhihu.com/people/cao-yang-49/activities](https://www.zhihu.com/people/cao-yang-49/activities)\n\n掘金：[https://juejin.im/user/58f87ae844d9040069ca7507](https://juejin.im/user/58f87ae844d9040069ca7507)\n\n对内容有任何疑问，欢迎联系我。"
  },
  {
    "path": "docs/VNode节点.MarkDown",
    "content": "## 抽象DOM树\n\n在刀耕火种的年代，我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。\n\n那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树，在修改抽象树数据后将抽象树转化成真实DOM重绘到页面上呢？于是虚拟DOM出现了，它是真实DOM的一层抽象，用属性描述真实DOM的各个特性。当它发生变化的时候，就会去修改视图。\n\n可以想象，最简单粗暴的方法就是将整个DOM结构用innerHTML修改到页面上，但是这样进行重绘整个视图层是相当消耗性能的，我们是不是可以每次只更新它的修改呢？所以Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树，以VNode节点模拟真实DOM，可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作，在这过程中都不需要操作真实DOM，只需要操作JavaScript对象后只对差异修改，相对于整块的innerHTML的粗暴式修改，大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位，再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作，大大提高了性能。\n\nVue就使用了这样的抽象节点VNode，它是对真实DOM的一层抽象，而不依赖某个平台，它可以是浏览器平台，也可以是weex，甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作，这也为前后端同构提供了可能。\n\n## VNode基类\n\n先来看一下Vue.js源码中对VNode类的定义。\n\n```javascript\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  functionalContext: Component | void; // only for functional component root nodes\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n\n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions\n  ) {\n    /*当前节点的标签名*/\n    this.tag = tag\n    /*当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息*/\n    this.data = data\n    /*当前节点的子节点，是一个数组*/\n    this.children = children\n    /*当前节点的文本*/\n    this.text = text\n    /*当前虚拟节点对应的真实dom节点*/\n    this.elm = elm\n    /*当前节点的名字空间*/\n    this.ns = undefined\n    /*编译作用域*/\n    this.context = context\n    /*函数化组件作用域*/\n    this.functionalContext = undefined\n    /*节点的key属性，被当作节点的标志，用以优化*/\n    this.key = data && data.key\n    /*组件的option选项*/\n    this.componentOptions = componentOptions\n    /*当前节点对应的组件的实例*/\n    this.componentInstance = undefined\n    /*当前节点的父节点*/\n    this.parent = undefined\n    /*简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false*/\n    this.raw = false\n    /*静态节点标志*/\n    this.isStatic = false\n    /*是否作为根节点插入*/\n    this.isRootInsert = true\n    /*是否为注释节点*/\n    this.isComment = false\n    /*是否为克隆节点*/\n    this.isCloned = false\n    /*是否有v-once指令*/\n    this.isOnce = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n```\n\n这是一个最基础的VNode节点，作为其他派生VNode类的基类，里面定义了下面这些数据。\n\ntag: 当前节点的标签名\n\ndata: 当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息\n\nchildren: 当前节点的子节点，是一个数组\n\ntext: 当前节点的文本\n\nelm: 当前虚拟节点对应的真实dom节点\n\nns: 当前节点的名字空间\n\ncontext: 当前节点的编译作用域\n\nfunctionalContext: 函数化组件作用域\n\nkey: 节点的key属性，被当作节点的标志，用以优化\n\ncomponentOptions: 组件的option选项\n\ncomponentInstance: 当前节点对应的组件的实例\n\nparent: 当前节点的父节点\n\nraw: 简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false\n\nisStatic: 是否为静态节点\n\nisRootInsert: 是否作为跟节点插入\n\nisComment: 是否为注释节点\n\nisCloned: 是否为克隆节点\n\nisOnce: 是否有v-once指令\n\n---\n\n打个比方，比如说我现在有这么一个VNode树\n\n```JavaScript\n{\n    tag: 'div'\n    data: {\n        class: 'test'\n    },\n    children: [\n        {\n            tag: 'span',\n            data: {\n                class: 'demo'\n            }\n            text: 'hello,VNode'\n        }\n    ]\n}\n```\n\n渲染之后的结果就是这样的\n\n```html\n<div class=\"test\">\n    <span class=\"demo\">hello,VNode</span>\n</div>\n```\n\n## 生成一个新的VNode的方法\n\n下面这些方法都是一些常用的构造VNode的方法。\n\n### createEmptyVNode 创建一个空VNode节点\n\n```javascript\n/*创建一个空VNode节点*/\nexport const createEmptyVNode = () => {\n  const node = new VNode()\n  node.text = ''\n  node.isComment = true\n  return node\n}\n```\n\n### createTextVNode 创建一个文本节点\n\n```javascript\n/*创建一个文本节点*/\nexport function createTextVNode (val: string | number) {\n  return new VNode(undefined, undefined, undefined, String(val))\n}\n```\n\n### createComponent 创建一个组件节点\n\n```javascript\n // plain options object: turn it into a constructor\n  if (isObject(Ctor)) {\n    Ctor = baseCtor.extend(Ctor)\n  }\n\n  // if at this stage it's not a constructor or an async component factory,\n  // reject.\n  /*Github:https://github.com/answershuto*/\n  /*如果在该阶段Ctor依然不是一个构造函数或者是一个异步组件工厂则直接返回*/\n  if (typeof Ctor !== 'function') {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(`Invalid Component definition: ${String(Ctor)}`, context)\n    }\n    return\n  }\n\n  // async component\n  /*处理异步组件*/\n  if (isUndef(Ctor.cid)) {\n    Ctor = resolveAsyncComponent(Ctor, baseCtor, context)\n    if (Ctor === undefined) {\n      // return nothing if this is indeed an async component\n      // wait for the callback to trigger parent update.\n      /*如果这是一个异步组件则会不会返回任何东西（undifiened），直接return掉，等待回调函数去触发父组件更新。s*/\n      return\n    }\n  }\n\n  // resolve constructor options in case global mixins are applied after\n  // component constructor creation\n  resolveConstructorOptions(Ctor)\n\n  data = data || {}\n\n  // transform component v-model data into props & events\n  if (isDef(data.model)) {\n    transformModel(Ctor.options, data)\n  }\n\n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n\n  // functional component\n  if (isTrue(Ctor.options.functional)) {\n    return createFunctionalComponent(Ctor, propsData, data, context, children)\n  }\n\n  // extract listeners, since these needs to be treated as\n  // child component listeners instead of DOM listeners\n  const listeners = data.on\n  // replace with listeners with .native modifier\n  data.on = data.nativeOn\n\n  if (isTrue(Ctor.options.abstract)) {\n    // abstract components do not keep anything\n    // other than props & listeners\n    data = {}\n  }\n\n  // merge component management hooks onto the placeholder node\n  mergeHooks(data)\n\n  // return a placeholder vnode\n  const name = Ctor.options.name || tag\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children }\n  )\n  return vnode\n}\n\n```\n\n### cloneVNode 克隆一个VNode节点\n\n```javascript\nexport function cloneVNode (vnode: VNode): VNode {\n  const cloned = new VNode(\n    vnode.tag,\n    vnode.data,\n    vnode.children,\n    vnode.text,\n    vnode.elm,\n    vnode.context,\n    vnode.componentOptions\n  )\n  cloned.ns = vnode.ns\n  cloned.isStatic = vnode.isStatic\n  cloned.key = vnode.key\n  cloned.isCloned = true\n  return cloned\n}\n```\n\n## createElement\n\n```javascript\n// wrapper function for providing a more flexible interface\n// without getting yelled at by flow\nexport function createElement (\n  context: Component,\n  tag: any,\n  data: any,\n  children: any,\n  normalizationType: any,\n  alwaysNormalize: boolean\n): VNode {\n  /*兼容不传data的情况*/\n  if (Array.isArray(data) || isPrimitive(data)) {\n    normalizationType = children\n    children = data\n    data = undefined\n  }\n  /*如果alwaysNormalize为true，则normalizationType标记为ALWAYS_NORMALIZE*/\n  if (isTrue(alwaysNormalize)) {\n    normalizationType = ALWAYS_NORMALIZE\n  }\n  /*Github:https://github.com/answershuto*/\n  /*创建虚拟节点*/\n  return _createElement(context, tag, data, children, normalizationType)\n}\n\n/*创建虚拟节点*/\nexport function _createElement (\n  context: Component,\n  tag?: string | Class<Component> | Function | Object,\n  data?: VNodeData,\n  children?: any,\n  normalizationType?: number\n): VNode {\n  /*\n    如果传递data参数且data的__ob__已经定义（代表已经被observed，上面绑定了Oberver对象），\n    https://cn.vuejs.org/v2/guide/render-function.html#约束\n    那么创建一个空节点\n  */\n  if (isDef(data) && isDef((data: any).__ob__)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\\n` +\n      'Always create fresh vnode data objects in each render!',\n      context\n    )\n    return createEmptyVNode()\n  }\n  /*如果tag不存在也是创建一个空节点*/\n  if (!tag) {\n    // in case of component :is set to falsy value\n    return createEmptyVNode()\n  }\n  // support single function children as default scoped slot\n  /*默认默认作用域插槽*/\n  if (Array.isArray(children) &&\n      typeof children[0] === 'function') {\n    data = data || {}\n    data.scopedSlots = { default: children[0] }\n    children.length = 0\n  }\n  if (normalizationType === ALWAYS_NORMALIZE) {\n    children = normalizeChildren(children)\n  } else if (normalizationType === SIMPLE_NORMALIZE) {\n    children = simpleNormalizeChildren(children)\n  }\n  let vnode, ns\n  if (typeof tag === 'string') {\n    let Ctor\n    /*获取tag的名字空间*/\n    ns = config.getTagNamespace(tag)\n    /*判断是否是保留的标签*/\n    if (config.isReservedTag(tag)) {\n      // platform built-in elements\n      /*如果是保留的标签则创建一个相应节点*/\n      vnode = new VNode(\n        config.parsePlatformTagName(tag), data, children,\n        undefined, undefined, context\n      )\n    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n      // component\n      /*从vm实例的option的components中寻找该tag，存在则就是一个组件，创建相应节点，Ctor为组件的构造类*/\n      vnode = createComponent(Ctor, data, context, children, tag)\n    } else {\n      // unknown or unlisted namespaced elements\n      // check at runtime because it may get assigned a namespace when its\n      // parent normalizes children\n      /*未知的元素，在运行时检查，因为父组件可能在序列化子组件的时候分配一个名字空间*/\n      vnode = new VNode(\n        tag, data, children,\n        undefined, undefined, context\n      )\n    }\n  } else {\n    // direct component options / constructor\n    /*tag不是字符串的时候则是组件的构造类*/\n    vnode = createComponent(tag, data, context, children)\n  }\n  if (isDef(vnode)) {\n    /*如果有名字空间，则递归所有子节点应用该名字空间*/\n    if (ns) applyNS(vnode, ns)\n    return vnode\n  } else {\n    /*如果vnode没有成功创建则创建空节点*/\n    return createEmptyVNode()\n  }\n}\n```\n\ncreateElement用来创建一个虚拟节点。当data上已经绑定__ob__的时候，代表该对象已经被Oberver过了，所以创建一个空节点。tag不存在的时候同样创建一个空节点。当tag不是一个String类型的时候代表tag是一个组件的构造类，直接用new VNode创建。当tag是String类型的时候，如果是保留标签，则用new VNode创建一个VNode实例，如果在vm的option的components找得到该tag，代表这是一个组件，否则统一用new VNode创建。\n"
  },
  {
    "path": "docs/VirtualDOM与diff(Vue实现).MarkDown",
    "content": "## VNode\n\n在刀耕火种的年代，我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。\n\n那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树，在修改抽象树数据后将抽象树转化成真实DOM重绘到页面上呢？于是虚拟DOM出现了，它是真实DOM的一层抽象，用属性描述真实DOM的各个特性。当它发生变化的时候，就会去修改视图。\n\n可以想象，最简单粗暴的方法就是将整个DOM结构用innerHTML修改到页面上，但是这样进行重绘整个视图层是相当消耗性能的，我们是不是可以每次只更新它的修改呢？所以Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树，以VNode节点模拟真实DOM，可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作，在这过程中都不需要操作真实DOM，只需要操作JavaScript对象后只对差异修改，相对于整块的innerHTML的粗暴式修改，大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位，再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作，大大提高了性能。\n\nVue就使用了这样的抽象节点VNode，它是对真实DOM的一层抽象，而不依赖某个平台，它可以是浏览器平台，也可以是weex，甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作，这也为前后端同构提供了可能。\n\n具体VNode的细节可以看[VNode节点](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)。\n\n## 修改视图\n\n众所周知，Vue通过数据绑定来修改视图，当某个数据被修改的时候，set方法会让闭包中的Dep调用notify通知所有订阅者Watcher，Watcher通过get方法执行vm._update(vm._render(), hydrating)。\n\n这里看一下_update方法\n\n```JavaScript\nVue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n    const vm: Component = this\n    /*如果已经该组件已经挂载过了则代表进入这个步骤是个更新的过程，触发beforeUpdate钩子*/\n    if (vm._isMounted) {\n      callHook(vm, 'beforeUpdate')\n    }\n    const prevEl = vm.$el\n    const prevVnode = vm._vnode\n    const prevActiveInstance = activeInstance\n    activeInstance = vm\n    vm._vnode = vnode\n    // Vue.prototype.__patch__ is injected in entry points\n    // based on the rendering backend used.\n    /*基于后端渲染Vue.prototype.__patch__被用来作为一个入口*/\n    if (!prevVnode) {\n      // initial render\n      vm.$el = vm.__patch__(\n        vm.$el, vnode, hydrating, false /* removeOnly */,\n        vm.$options._parentElm,\n        vm.$options._refElm\n      )\n    } else {\n      // updates\n      vm.$el = vm.__patch__(prevVnode, vnode)\n    }\n    activeInstance = prevActiveInstance\n    // update __vue__ reference\n    /*更新新的实例对象的__vue__*/\n    if (prevEl) {\n      prevEl.__vue__ = null\n    }\n    if (vm.$el) {\n      vm.$el.__vue__ = vm\n    }\n    // if parent is an HOC, update its $el as well\n    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n      vm.$parent.$el = vm.$el\n    }\n    // updated hook is called by the scheduler to ensure that children are\n    // updated in a parent's updated hook.\n  }\n```\n\n_update方法的第一个参数是一个VNode对象，在内部会将该VNode对象与之前旧的VNode对象进行__patch__。\n\n什么是__patch__呢？\n\n## __patch__\n\npatch将新老VNode节点进行比对，然后将根据两者的比较结果进行最小单位地修改视图，而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法，这套算法可以高效地比较virtual DOM的变更，得出变化以修改视图。\n\n那么patch如何工作的呢？\n\n首先说一下patch的核心diff算法，diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式，所以时间复杂度只有O(n)，是一种相当高效的算法。\n\n![img](https://i.loli.net/2017/08/27/59a23cfca50f3.png)\n\n![img](https://i.loli.net/2017/08/27/59a2419a3c617.png)\n\n这两张图代表旧的VNode与新VNode进行patch的过程，他们只是在同层级的VNode之间进行比较得到变化（第二张图中相同颜色的方块代表互相进行比较的VNode节点），然后修改变化的视图，所以十分高效。\n\n让我们看一下patch的代码。\n\n```JavaScript\n  /*createPatchFunction的返回值，一个patch函数*/\n  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {\n    /*vnode不存在则直接调用销毁钩子*/\n    if (isUndef(vnode)) {\n      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)\n      return\n    }\n\n    let isInitialPatch = false\n    const insertedVnodeQueue = []\n\n    if (isUndef(oldVnode)) {\n      // empty mount (likely as component), create new root element\n      /*oldVnode未定义的时候，其实也就是root节点，创建一个新的节点*/\n      isInitialPatch = true\n      createElm(vnode, insertedVnodeQueue, parentElm, refElm)\n    } else {\n      /*标记旧的VNode是否有nodeType*/\n      /*Github:https://github.com/answershuto*/\n      const isRealElement = isDef(oldVnode.nodeType)\n      if (!isRealElement && sameVnode(oldVnode, vnode)) {\n        // patch existing root node\n        /*是同一个节点的时候直接修改现有的节点*/\n        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)\n      } else {\n        if (isRealElement) {\n          // mounting to a real element\n          // check if this is server-rendered content and if we can perform\n          // a successful hydration.\n          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n            /*当旧的VNode是服务端渲染的元素，hydrating记为true*/\n            oldVnode.removeAttribute(SSR_ATTR)\n            hydrating = true\n          }\n          if (isTrue(hydrating)) {\n            /*需要合并到真实DOM上*/\n            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n              /*调用insert钩子*/\n              invokeInsertHook(vnode, insertedVnodeQueue, true)\n              return oldVnode\n            } else if (process.env.NODE_ENV !== 'production') {\n              warn(\n                'The client-side rendered virtual DOM tree is not matching ' +\n                'server-rendered content. This is likely caused by incorrect ' +\n                'HTML markup, for example nesting block-level elements inside ' +\n                '<p>, or missing <tbody>. Bailing hydration and performing ' +\n                'full client-side render.'\n              )\n            }\n          }\n          // either not server-rendered, or hydration failed.\n          // create an empty node and replace it\n          /*如果不是服务端渲染或者合并到真实DOM失败，则创建一个空的VNode节点替换它*/\n          oldVnode = emptyNodeAt(oldVnode)\n        }\n        // replacing existing element\n        /*取代现有元素*/\n        const oldElm = oldVnode.elm\n        const parentElm = nodeOps.parentNode(oldElm)\n        createElm(\n          vnode,\n          insertedVnodeQueue,\n          // extremely rare edge case: do not insert if old element is in a\n          // leaving transition. Only happens when combining transition +\n          // keep-alive + HOCs. (#4590)\n          oldElm._leaveCb ? null : parentElm,\n          nodeOps.nextSibling(oldElm)\n        )\n\n        if (isDef(vnode.parent)) {\n          // component root element replaced.\n          // update parent placeholder node element, recursively\n          /*组件根节点被替换，遍历更新父节点element*/\n          let ancestor = vnode.parent\n          while (ancestor) {\n            ancestor.elm = vnode.elm\n            ancestor = ancestor.parent\n          }\n          if (isPatchable(vnode)) {\n            /*调用create回调*/\n            for (let i = 0; i < cbs.create.length; ++i) {\n              cbs.create[i](emptyNode, vnode.parent)\n            }\n          }\n        }\n\n        if (isDef(parentElm)) {\n          /*移除老节点*/\n          removeVnodes(parentElm, [oldVnode], 0, 0)\n        } else if (isDef(oldVnode.tag)) {\n          /*Github:https://github.com/answershuto*/\n          /*调用destroy钩子*/\n          invokeDestroyHook(oldVnode)\n        }\n      }\n    }\n\n    /*调用insert钩子*/\n    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)\n    return vnode.elm\n  }\n```\n\n从代码中不难发现，当oldVnode与vnode在sameVnode的时候才会进行patchVnode，也就是新旧VNode节点判定为同一节点的时候才会进行patchVnode这个过程，否则就是创建新的DOM，移除旧的DOM。\n\n怎么样的节点算sameVnode呢？\n\n## sameVnode\n\n我们来看一下sameVnode的实现。\n\n```JavaScript\n/*\n  判断两个VNode节点是否是同一个节点，需要满足以下条件\n  key相同\n  tag（当前节点的标签名）相同\n  isComment（是否为注释节点）相同\n  是否data（当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息）都有定义\n  当标签是<input>的时候，type必须相同\n*/\nfunction sameVnode (a, b) {\n  return (\n    a.key === b.key &&\n    a.tag === b.tag &&\n    a.isComment === b.isComment &&\n    isDef(a.data) === isDef(b.data) &&\n    sameInputType(a, b)\n  )\n}\n\n// Some browsers do not support dynamically changing type for <input>\n// so they need to be treated as different nodes\n/*\n  判断当标签是<input>的时候，type是否相同\n  某些浏览器不支持动态修改<input>类型，所以他们被视为不同节点\n*/\nfunction sameInputType (a, b) {\n  if (a.tag !== 'input') return true\n  let i\n  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type\n  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type\n  return typeA === typeB\n}\n```\n\n当两个VNode的tag、key、isComment都相同，并且同时定义或未定义data的时候，且如果标签为input则type必须相同。这时候这两个VNode则算sameVnode，可以直接进行patchVnode操作。\n\n## patchVnode\n\n还是先来看一下patchVnode的代码。\n\n```JavaScript\n  /*patch VNode节点*/\n  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {\n    /*两个VNode节点相同则直接返回*/\n    if (oldVnode === vnode) {\n      return\n    }\n    // reuse element for static trees.\n    // note we only do this if the vnode is cloned -\n    // if the new node is not cloned it means the render functions have been\n    // reset by the hot-reload-api and we need to do a proper re-render.\n    /*\n      如果新旧VNode都是静态的，同时它们的key相同（代表同一节点），\n      并且新的VNode是clone或者是标记了once（标记v-once属性，只渲染一次），\n      那么只需要替换elm以及componentInstance即可。\n    */\n    if (isTrue(vnode.isStatic) &&\n        isTrue(oldVnode.isStatic) &&\n        vnode.key === oldVnode.key &&\n        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {\n      vnode.elm = oldVnode.elm\n      vnode.componentInstance = oldVnode.componentInstance\n      return\n    }\n    let i\n    const data = vnode.data\n    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n      /*i = data.hook.prepatch，如果存在的话，见\"./create-component componentVNodeHooks\"。*/\n      i(oldVnode, vnode)\n    }\n    const elm = vnode.elm = oldVnode.elm\n    const oldCh = oldVnode.children\n    const ch = vnode.children\n    if (isDef(data) && isPatchable(vnode)) {\n      /*调用update回调以及update钩子*/\n      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n    }\n    /*如果这个VNode节点没有text文本时*/\n    if (isUndef(vnode.text)) {\n      if (isDef(oldCh) && isDef(ch)) {\n        /*新老节点均有children子节点，则对子节点进行diff操作，调用updateChildren*/\n        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)\n      } else if (isDef(ch)) {\n        /*如果老节点没有子节点而新节点存在子节点，先清空elm的文本内容，然后为当前节点加入子节点*/\n        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')\n        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)\n      } else if (isDef(oldCh)) {\n        /*当新节点没有子节点而老节点有子节点的时候，则移除所有ele的子节点*/\n        removeVnodes(elm, oldCh, 0, oldCh.length - 1)\n      } else if (isDef(oldVnode.text)) {\n        /*当新老节点都无子节点的时候，只是文本的替换，因为这个逻辑中新节点text不存在，所以直接去除ele的文本*/\n        nodeOps.setTextContent(elm, '')\n      }\n    } else if (oldVnode.text !== vnode.text) {\n      /*当新老节点text不一样时，直接替换这段文本*/\n      nodeOps.setTextContent(elm, vnode.text)\n    }\n    /*调用postpatch钩子*/\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)\n    }\n  }\n```\n\npatchVnode的规则是这样的：\n\n1.如果新旧VNode都是静态的，同时它们的key相同（代表同一节点），并且新的VNode是clone或者是标记了once（标记v-once属性，只渲染一次），那么只需要替换elm以及componentInstance即可。\n\n2.新老节点均有children子节点，则对子节点进行diff操作，调用updateChildren，这个updateChildren也是diff的核心。\n\n3.如果老节点没有子节点而新节点存在子节点，先清空老节点DOM的文本内容，然后为当前DOM节点加入子节点。\n\n4.当新节点没有子节点而老节点有子节点的时候，则移除该DOM节点的所有子节点。\n\n5.当新老节点都无子节点的时候，只是文本的替换。\n\n## updateChildren\n\n```JavaScript\n  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n    let oldStartIdx = 0\n    let newStartIdx = 0\n    let oldEndIdx = oldCh.length - 1\n    let oldStartVnode = oldCh[0]\n    let oldEndVnode = oldCh[oldEndIdx]\n    let newEndIdx = newCh.length - 1\n    let newStartVnode = newCh[0]\n    let newEndVnode = newCh[newEndIdx]\n    let oldKeyToIdx, idxInOld, elmToMove, refElm\n\n    // removeOnly is a special flag used only by <transition-group>\n    // to ensure removed elements stay in correct relative positions\n    // during leaving transitions\n    const canMove = !removeOnly\n\n    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n      if (isUndef(oldStartVnode)) {\n        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left\n      } else if (isUndef(oldEndVnode)) {\n        oldEndVnode = oldCh[--oldEndIdx]\n      } else if (sameVnode(oldStartVnode, newStartVnode)) {\n        /*前四种情况其实是指定key的时候，判定为同一个VNode，则直接patchVnode即可，分别比较oldCh以及newCh的两头节点2*2=4种情况*/\n        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)\n        oldStartVnode = oldCh[++oldStartIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else if (sameVnode(oldEndVnode, newEndVnode)) {\n        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))\n        oldStartVnode = oldCh[++oldStartIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else {\n        /*\n          生成一个key与旧VNode的key对应的哈希表（只有第一次进来undefined的时候会生成，也为后面检测重复的key值做铺垫）\n          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  \n          结果生成{key0: 0, key1: 1, key2: 2}\n        */\n        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld（即第几个节点，下标）*/\n        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null\n        if (isUndef(idxInOld)) { // New element\n          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/\n          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n          newStartVnode = newCh[++newStartIdx]\n        } else {\n          /*获取同key的老节点*/\n          elmToMove = oldCh[idxInOld]\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !elmToMove) {\n            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的DOM中，提示可能存在重复的key，确保v-for的时候item有唯一的key值*/\n            warn(\n              'It seems there are duplicate keys that is causing an update error. ' +\n              'Make sure each v-for item has a unique key.'\n            )\n          }\n          if (sameVnode(elmToMove, newStartVnode)) {\n            /*Github:https://github.com/answershuto*/\n            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/\n            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)\n            /*因为已经patchVnode进去了，所以将这个老节点赋值undefined，之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/\n            oldCh[idxInOld] = undefined\n            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实DOM节点前面*/\n            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)\n            newStartVnode = newCh[++newStartIdx]\n          } else {\n            // same key but different element. treat as new element\n            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候（比如说tag不一样或者是有不一样type的input标签），创建一个新的节点*/\n            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n            newStartVnode = newCh[++newStartIdx]\n          }\n        }\n      }\n    }\n    if (oldStartIdx > oldEndIdx) {\n      /*全部比较完成以后，发现oldStartIdx > oldEndIdx的话，说明老节点已经遍历完了，新节点比老节点多，所以这时候多出来的新节点需要一个一个创建出来加入到真实DOM中*/\n      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm\n      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)\n    } else if (newStartIdx > newEndIdx) {\n      /*如果全部比较完成以后发现newStartIdx > newEndIdx，则说明新节点已经遍历完了，老节点多余新节点，这个时候需要将多余的老节点从真实DOM中移除*/\n      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)\n    }\n  }\n```\n\n直接看源码可能比较难以捋清其中的关系，我们通过图来看一下。\n\n![img](https://i.loli.net/2017/08/28/59a4015bb2765.png)\n\n首先，在新老两个VNode节点的左右头尾两侧都有一个变量标记，在遍历过程中这几个变量都会向中间靠拢。当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环。\n\n索引与VNode节点的对应关系：\noldStartIdx => oldStartVnode\noldEndIdx => oldEndVnode\nnewStartIdx => newStartVnode\nnewEndIdx => newEndVnode\n\n在遍历中，如果存在key，并且满足sameVnode，会将该DOM节点进行复用，否则则会创建一个新的DOM节点。\n\n首先，oldStartVnode、oldEndVnode与newStartVnode、newEndVnode两两比较一共有2*2=4种比较方法。\n\n当新老VNode节点的start或者end满足sameVnode时，也就是sameVnode(oldStartVnode, newStartVnode)或者sameVnode(oldEndVnode, newEndVnode)，直接将该VNode节点进行patchVnode即可。\n\n![img](https://i.loli.net/2017/08/28/59a40c12c1655.png)\n\n如果oldStartVnode与newEndVnode满足sameVnode，即sameVnode(oldStartVnode, newEndVnode)。\n\n这时候说明oldStartVnode已经跑到了oldEndVnode后面去了，进行patchVnode的同时还需要将真实DOM节点移动到oldEndVnode的后面。\n\n![img](https://ooo.0o0.ooo/2017/08/28/59a4214784979.png)\n\n如果oldEndVnode与newStartVnode满足sameVnode，即sameVnode(oldEndVnode, newStartVnode)。\n\n这说明oldEndVnode跑到了oldStartVnode的前面，进行patchVnode的同时真实的DOM节点移动到了oldStartVnode的前面。\n\n![img](https://i.loli.net/2017/08/29/59a4c70685d12.png)\n\n如果以上情况均不符合，则通过createKeyToOldIdx会得到一个oldKeyToIdx，里面存放了一个key为旧的VNode，value为对应index序列的哈希表。从这个哈希表中可以找到是否有与newStartVnode一致key的旧的VNode节点，如果同时满足sameVnode，patchVnode的同时会将这个真实DOM（elmToMove）移动到oldStartVnode对应的真实DOM的前面。\n\n![img](https://i.loli.net/2017/08/29/59a4d7552d299.png)\n\n当然也有可能newStartVnode在旧的VNode节点找不到一致的key，或者是即便key相同却不是sameVnode，这个时候会调用createElm创建一个新的DOM节点。\n\n![img](https://i.loli.net/2017/08/29/59a4de0fa4dba.png)\n\n到这里循环已经结束了，那么剩下我们还需要处理多余或者不够的真实DOM节点。\n\n1.当结束时oldStartIdx > oldEndIdx，这个时候老的VNode节点已经遍历完了，但是新的节点还没有。说明了新的VNode节点实际上比老的VNode节点多，也就是比真实DOM多，需要将剩下的（也就是新增的）VNode节点插入到真实DOM节点中去，此时调用addVnodes（批量调用createElm的接口将这些节点加入到真实DOM中去）。\n\n![img](https://i.loli.net/2017/08/29/59a509f0d1788.png)\n\n2。同理，当newStartIdx > newEndIdx时，新的VNode节点已经遍历完了，但是老的节点还有剩余，说明真实DOM节点多余了，需要从文档中删除，这时候调用removeVnodes将这些多余的真实DOM删除。\n\n![img](https://i.loli.net/2017/08/29/59a4f389b98cb.png)\n\n## DOM操作\n\n由于Vue使用了虚拟DOM，所以虚拟DOM可以在任何支持JavaScript语言的平台上操作，譬如说目前Vue支持的浏览器平台或是weex，在虚拟DOM的实现上是一致的。那么最后虚拟DOM如何映射到真实的DOM节点上呢？\n\nVue为平台做了一层适配层，浏览器平台见[/platforms/web/runtime/node-ops.js](https://github.com/answershuto/learnVue/blob/master/vue-src/platforms/web/runtime/node-ops.js)以及weex平台见[/platforms/weex/runtime/node-ops.js](https://github.com/answershuto/learnVue/blob/master/vue-src/platforms/weex/runtime/node-ops.js)。不同平台之间通过适配层对外提供相同的接口，虚拟DOM进行操作真实DOM节点的时候，只需要调用这些适配层的接口即可，而内部实现则不需要关心，它会根据平台的改变而改变。\n\n现在又出现了一个问题，我们只是将虚拟DOM映射成了真实的DOM。那如何给这些DOM加入attr、class、style等DOM属性呢？\n\n这要依赖于虚拟DOM的生命钩子。虚拟DOM提供了如下的钩子函数，分别在不同的时期会进行调用。\n\n```JavaScript\nconst hooks = ['create', 'activate', 'update', 'remove', 'destroy']\n\n/*构建cbs回调函数，web平台上见/platforms/web/runtime/modules*/\n  for (i = 0; i < hooks.length; ++i) {\n    cbs[hooks[i]] = []\n    for (j = 0; j < modules.length; ++j) {\n      if (isDef(modules[j][hooks[i]])) {\n        cbs[hooks[i]].push(modules[j][hooks[i]])\n      }\n    }\n  }\n```\n\n同理，也会根据不同平台有自己不同的实现，我们这里以Web平台为例。Web平台的钩子函数见[/platforms/web/runtime/modules](https://github.com/answershuto/learnVue/tree/master/vue-src/platforms/web/runtime/modules)。里面有对attr、class、props、events、style以及transition（过渡状态）的DOM属性进行操作。\n\n以attr为例，代码很简单。\n\n```JavaScript\n/* @flow */\n\nimport { isIE9 } from 'core/util/env'\n\nimport {\n  extend,\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  isXlink,\n  xlinkNS,\n  getXlinkProp,\n  isBooleanAttr,\n  isEnumeratedAttr,\n  isFalsyAttrValue\n} from 'web/util/index'\n\n/*更新attr*/\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  /*如果旧的以及新的VNode节点均没有attr属性，则直接返回*/\n  if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {\n    return\n  }\n  let key, cur, old\n  /*VNode节点对应的Dom实例*/\n  const elm = vnode.elm\n  /*旧VNode节点的attr*/\n  const oldAttrs = oldVnode.data.attrs || {}\n  /*新VNode节点的attr*/\n  let attrs: any = vnode.data.attrs || {}\n  // clone observed objects, as the user probably wants to mutate it\n  /*如果新的VNode的attr已经有__ob__（代表已经被Observe处理过了）， 进行深拷贝*/\n  if (isDef(attrs.__ob__)) {\n    attrs = vnode.data.attrs = extend({}, attrs)\n  }\n\n  /*遍历attr，不一致则替换*/\n  for (key in attrs) {\n    cur = attrs[key]\n    old = oldAttrs[key]\n    if (old !== cur) {\n      setAttr(elm, key, cur)\n    }\n  }\n  // #4391: in IE9, setting type can reset value for input[type=radio]\n  /* istanbul ignore if */\n  if (isIE9 && attrs.value !== oldAttrs.value) {\n    setAttr(elm, 'value', attrs.value)\n  }\n  for (key in oldAttrs) {\n    if (isUndef(attrs[key])) {\n      if (isXlink(key)) {\n        elm.removeAttributeNS(xlinkNS, getXlinkProp(key))\n      } else if (!isEnumeratedAttr(key)) {\n        elm.removeAttribute(key)\n      }\n    }\n  }\n}\n\n/*设置attr*/\nfunction setAttr (el: Element, key: string, value: any) {\n  if (isBooleanAttr(key)) {\n    // set attribute for blank value\n    // e.g. <option disabled>Select one</option>\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      el.setAttribute(key, key)\n    }\n  } else if (isEnumeratedAttr(key)) {\n    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')\n  } else if (isXlink(key)) {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttributeNS(xlinkNS, getXlinkProp(key))\n    } else {\n      el.setAttributeNS(xlinkNS, key, value)\n    }\n  } else {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      el.setAttribute(key, value)\n    }\n  }\n}\n\nexport default {\n  create: updateAttrs,\n  update: updateAttrs\n}\n\n```\n\nattr只需要在create以及update钩子被调用时更新DOM的attr属性即可。\n"
  },
  {
    "path": "docs/Vue.js异步更新DOM策略及nextTick.MarkDown",
    "content": "## 操作DOM\n\n在使用vue.js的时候，有时候因为一些特定的业务场景，不得不去操作DOM，比如这样：\n\n```html\n<template>\n  <div>\n    <div ref=\"test\">{{test}}</div>\n    <button @click=\"handleClick\">tet</button>\n  </div>\n</template>\n\n```\n\n```javascript\nexport default {\n    data () {\n        return {\n            test: 'begin'\n        };\n    },\n    methods () {\n        handleClick () {\n            this.test = 'end';\n            console.log(this.$refs.test.innerText);//打印“begin”\n        }\n    }\n}\n```\n\n打印的结果是begin，为什么我们明明已经将test设置成了“end”，获取真实DOM节点的innerText却没有得到我们预期中的“end”，而是得到之前的值“begin”呢？\n\n## Watcher队列\n\n带着疑问，我们找到了Vue.js源码的Watch实现。当某个响应式数据发生变化的时候，它的setter函数会通知闭包中的Dep，Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。我们来看一下update的实现。\n\n```javascript\nupdate () {\n    /* istanbul ignore else */\n    if (this.lazy) {\n        this.dirty = true\n    } else if (this.sync) {\n        /*同步则执行run直接渲染视图*/\n        this.run()\n    } else {\n        /*异步推送到观察者队列中，下一个tick时调用。*/\n        queueWatcher(this)\n    }\n}\n```\n\n我们发现Vue.js默认是使用[异步执行DOM更新](https://cn.vuejs.org/v2/guide/reactivity.html#异步更新队列)。\n当异步执行update的时候，会调用queueWatcher函数。\n\n```javascript\n /*将一个观察者对象push进观察者队列，在队列中已经存在相同的id则该观察者对象将被跳过，除非它是在队列被刷新时推送*/\nexport function queueWatcher (watcher: Watcher) {\n  /*获取watcher的id*/\n  const id = watcher.id\n  /*检验id是否存在，已经存在则直接跳过，不存在则标记哈希表has，用于下次检验*/\n  if (has[id] == null) {\n    has[id] = true\n    if (!flushing) {\n      /*如果没有flush掉，直接push到队列中即可*/\n      queue.push(watcher)\n    } else {\n      // if already flushing, splice the watcher based on its id\n      // if already past its id, it will be run next immediately.\n      let i = queue.length - 1\n      while (i >= 0 && queue[i].id > watcher.id) {\n        i--\n      }\n      queue.splice(Math.max(i, index) + 1, 0, watcher)\n    }\n    // queue the flush\n    if (!waiting) {\n      waiting = true\n      nextTick(flushSchedulerQueue)\n    }\n  }\n}\n```\n\n查看queueWatcher的源码我们发现，Watch对象并不是立即更新视图，而是被push进了一个队列queue，此时状态处于waiting的状态，这时候会继续会有Watch对象被push进这个队列queue，等到下一个tick运行时，这些Watch对象才会被遍历取出，更新视图。同时，id重复的Watcher不会被多次加入到queue中去，因为在最终渲染时，我们只需要关心数据的最终结果。\n\n那么，什么是下一个tick？\n\n## nextTick\n\nvue.js提供了一个[nextTick](https://cn.vuejs.org/v2/api/#Vue-nextTick)函数，其实也就是上面调用的nextTick。\n\nnextTick的实现比较简单，执行的目的是在microtask或者task中推入一个function，在当前栈执行完毕（也许还会有一些排在前面的需要执行的任务）以后执行nextTick传入的function，看一下源码：\n\n```javascript\n/**\n * Defer a task to execute it asynchronously.\n */\n /*\n    延迟一个任务使其异步执行，在下一个tick时执行，一个立即执行函数，返回一个function\n    这个函数的作用是在task或者microtask中推入一个timerFunc，在当前调用栈执行完以后以此执行直到执行到timerFunc\n    目的是延迟到当前调用栈执行完以后执行\n*/\nexport const nextTick = (function () {\n  /*存放异步执行的回调*/\n  const callbacks = []\n  /*一个标记位，如果已经有timerFunc被推送到任务队列中去则不需要重复推送*/\n  let pending = false\n  /*一个函数指针，指向函数将被推送到任务队列中，等到主线程任务执行完时，任务队列中的timerFunc被调用*/\n  let timerFunc\n\n  /*下一个tick时的回调*/\n  function nextTickHandler () {\n    /*一个标记位，标记等待状态（即函数已经被推入任务队列或者主线程，已经在等待当前栈执行完毕去执行），这样就不需要在push多个回调到callbacks时将timerFunc多次推入任务队列或者主线程*/\n    pending = false\n    /*执行所有callback*/\n    const copies = callbacks.slice(0)\n    callbacks.length = 0\n    for (let i = 0; i < copies.length; i++) {\n      copies[i]()\n    }\n  }\n\n  // the nextTick behavior leverages the microtask queue, which can be accessed\n  // via either native Promise.then or MutationObserver.\n  // MutationObserver has wider support, however it is seriously bugged in\n  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It\n  // completely stops working after triggering a few times... so, if native\n  // Promise is available, we will use it:\n  /* istanbul ignore if */\n\n  /*\n    这里解释一下，一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法\n    优先使用Promise，在Promise不存在的情况下使用MutationObserver，这两个方法都会在microtask中执行，会比setTimeout更早执行，所以优先使用。\n    如果上述两种方法都不支持的环境则会使用setTimeout，在task尾部推入这个函数，等待调用执行。\n    参考：https://www.zhihu.com/question/55364497\n  */\n  if (typeof Promise !== 'undefined' && isNative(Promise)) {\n    /*使用Promise*/\n    var p = Promise.resolve()\n    var logError = err => { console.error(err) }\n    timerFunc = () => {\n      p.then(nextTickHandler).catch(logError)\n      // in problematic UIWebViews, Promise.then doesn't completely break, but\n      // it can get stuck in a weird state where callbacks are pushed into the\n      // microtask queue but the queue isn't being flushed, until the browser\n      // needs to do some other work, e.g. handle a timer. Therefore we can\n      // \"force\" the microtask queue to be flushed by adding an empty timer.\n      if (isIOS) setTimeout(noop)\n    }\n  } else if (typeof MutationObserver !== 'undefined' && (\n    isNative(MutationObserver) ||\n    // PhantomJS and iOS 7.x\n    MutationObserver.toString() === '[object MutationObserverConstructor]'\n  )) {\n    // use MutationObserver where native Promise is not available,\n    // e.g. PhantomJS IE11, iOS7, Android 4.4\n    /*新建一个textNode的DOM对象，用MutationObserver绑定该DOM并指定回调函数，在DOM变化的时候则会触发回调,该回调会进入主线程（比任务队列优先执行），即textNode.data = String(counter)时便会触发回调*/\n    var counter = 1\n    var observer = new MutationObserver(nextTickHandler)\n    var textNode = document.createTextNode(String(counter))\n    observer.observe(textNode, {\n      characterData: true\n    })\n    timerFunc = () => {\n      counter = (counter + 1) % 2\n      textNode.data = String(counter)\n    }\n  } else {\n    // fallback to setTimeout\n    /* istanbul ignore next */\n    /*使用setTimeout将回调推入任务队列尾部*/\n    timerFunc = () => {\n      setTimeout(nextTickHandler, 0)\n    }\n  }\n\n  /*\n    推送到队列中下一个tick时执行\n    cb 回调函数\n    ctx 上下文\n  */\n  return function queueNextTick (cb?: Function, ctx?: Object) {\n    let _resolve\n    /*cb存到callbacks中*/\n    callbacks.push(() => {\n      if (cb) {\n        try {\n          cb.call(ctx)\n        } catch (e) {\n          handleError(e, ctx, 'nextTick')\n        }\n      } else if (_resolve) {\n        _resolve(ctx)\n      }\n    })\n    if (!pending) {\n      pending = true\n      timerFunc()\n    }\n    if (!cb && typeof Promise !== 'undefined') {\n      return new Promise((resolve, reject) => {\n        _resolve = resolve\n      })\n    }\n  }\n})()\n```\n\n它是一个立即执行函数,返回一个queueNextTick接口。\n\n传入的cb会被push进callbacks中存放起来，然后执行timerFunc（pending是一个状态标记，保证timerFunc在下一个tick之前只执行一次）。\n\ntimerFunc是什么？\n\n看了源码发现timerFunc会检测当前环境而不同实现，其实就是按照Promise，MutationObserver，setTimeout优先级，哪个存在使用哪个，最不济的环境下使用setTimeout。\n\n这里解释一下，一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法。\n优先使用Promise，在Promise不存在的情况下使用MutationObserver，这两个方法的回调函数都会在microtask中执行，它们会比setTimeout更早执行，所以优先使用。\n如果上述两种方法都不支持的环境则会使用setTimeout，在task尾部推入这个函数，等待调用执行。\n\n为什么要优先使用microtask？我在顾轶灵在知乎的回答中学习到：\n\n```\nJS 的 event loop 执行时会区分 task 和 microtask，引擎在每个 task 执行完毕，从队列中取下一个 task 来执行之前，会先执行完所有 microtask 队列中的 microtask。\nsetTimeout 回调会被分配到一个新的 task 中执行，而 Promise 的 resolver、MutationObserver 的回调都会被安排到一个新的 microtask 中执行，会比 setTimeout 产生的 task 先执行。\n要创建一个新的 microtask，优先使用 Promise，如果浏览器不支持，再尝试 MutationObserver。\n实在不行，只能用 setTimeout 创建 task 了。\n为啥要用 microtask？\n根据 HTML Standard，在每个 task 运行完以后，UI 都会重渲染，那么在 microtask 中就完成数据更新，当前 task 结束就可以得到最新的 UI 了。\n反之如果新建一个 task 来做数据更新，那么渲染就会进行两次。\n\n参考顾轶灵知乎的回答：https://www.zhihu.com/question/55364497/answer/144215284\n```\n\n首先是Promise，Promise.resolve().then()可以在microtask中加入它的回调，\n\nMutationObserver新建一个textNode的DOM对象，用MutationObserver绑定该DOM并指定回调函数，在DOM变化的时候则会触发回调,该回调会进入microtask，即textNode.data = String(counter)时便会加入该回调。\n\nsetTimeout是最后的一种备选方案，它会将回调函数加入task中，等到执行。\n\n综上，nextTick的目的就是产生一个回调函数加入task或者microtask中，当前栈执行完以后（可能中间还有别的排在前面的函数）调用该回调函数，起到了异步触发（即下一个tick时触发）的目的。\n\n## flushSchedulerQueue\n\n```javascript\n/*Github:https://github.com/answershuto*/\n/**\n * Flush both queues and run the watchers.\n */\n /*nextTick的回调函数，在下一个tick时flush掉两个队列同时运行watchers*/\nfunction flushSchedulerQueue () {\n  flushing = true\n  let watcher, id\n\n  // Sort queue before flush.\n  // This ensures that:\n  // 1. Components are updated from parent to child. (because parent is always\n  //    created before the child)\n  // 2. A component's user watchers are run before its render watcher (because\n  //    user watchers are created before the render watcher)\n  // 3. If a component is destroyed during a parent component's watcher run,\n  //    its watchers can be skipped.\n  /*\n    给queue排序，这样做可以保证：\n    1.组件更新的顺序是从父组件到子组件的顺序，因为父组件总是比子组件先创建。\n    2.一个组件的user watchers比render watcher先运行，因为user watchers往往比render watcher更早创建\n    3.如果一个组件在父组件watcher运行期间被销毁，它的watcher执行将被跳过。\n  */\n  queue.sort((a, b) => a.id - b.id)\n\n  // do not cache length because more watchers might be pushed\n  // as we run existing watchers\n  /*这里不用index = queue.length;index > 0; index--的方式写是因为不要将length进行缓存，因为在执行处理现有watcher对象期间，更多的watcher对象可能会被push进queue*/\n  for (index = 0; index < queue.length; index++) {\n    watcher = queue[index]\n    id = watcher.id\n    /*将has的标记删除*/\n    has[id] = null\n    /*执行watcher*/\n    watcher.run()\n    // in dev build, check and stop circular updates.\n    /*\n      在测试环境中，检测watch是否在死循环中\n      比如这样一种情况\n      watch: {\n        test () {\n          this.test++;\n        }\n      }\n      持续执行了一百次watch代表可能存在死循环\n    */\n    if (process.env.NODE_ENV !== 'production' && has[id] != null) {\n      circular[id] = (circular[id] || 0) + 1\n      if (circular[id] > MAX_UPDATE_COUNT) {\n        warn(\n          'You may have an infinite update loop ' + (\n            watcher.user\n              ? `in watcher with expression \"${watcher.expression}\"`\n              : `in a component render function.`\n          ),\n          watcher.vm\n        )\n        break\n      }\n    }\n  }\n\n  // keep copies of post queues before resetting state\n  /**/\n  /*得到队列的拷贝*/\n  const activatedQueue = activatedChildren.slice()\n  const updatedQueue = queue.slice()\n\n  /*重置调度者的状态*/\n  resetSchedulerState()\n\n  // call component updated and activated hooks\n  /*使子组件状态都改编成active同时调用activated钩子*/\n  callActivatedHooks(activatedQueue)\n  /*调用updated钩子*/\n  callUpdateHooks(updatedQueue)\n\n  // devtool hook\n  /* istanbul ignore if */\n  if (devtools && config.devtools) {\n    devtools.emit('flush')\n  }\n}\n```\n\nflushSchedulerQueue是下一个tick时的回调函数，主要目的是执行Watcher的run函数，用来更新视图 \n\n## 为什么要异步更新视图\n\n来看一下下面这一段代码\n\n```html\n<template>\n  <div>\n    <div>{{test}}</div>\n  </div>\n</template>\n\n```\n\n```javascript\nexport default {\n    data () {\n        return {\n            test: 0\n        };\n    },\n    mounted () {\n      for(let i = 0; i < 1000; i++) {\n        this.test++;\n      }\n    }\n}\n```\n\n现在有这样的一种情况，mounted的时候test的值会被++循环执行1000次。\n每次++时，都会根据响应式触发setter->Dep->Watcher->update->patch。\n如果这时候没有异步更新视图，那么每次++都会直接操作DOM更新视图，这是非常消耗性能的。\n所以Vue.js实现了一个queue队列，在下一个tick的时候会统一执行queue中Watcher的run。同时，拥有相同id的Watcher不会被重复加入到该queue中去，所以不会执行1000次Watcher的run。最终更新视图只会直接将test对应的DOM的0变成1000。\n保证更新视图操作DOM的动作是在当前栈执行完以后下一个tick的时候调用，大大优化了性能。\n\n## 访问真实DOM节点更新后的数据\n\n所以我们需要在修改data中的数据后访问真实的DOM节点更新后的数据，只需要这样，我们把文章第一个例子进行修改。\n\n```html\n<template>\n  <div>\n    <div ref=\"test\">{{test}}</div>\n    <button @click=\"handleClick\">tet</button>\n  </div>\n</template>\n\n```\n\n```javascript\nexport default {\n    data () {\n        return {\n            test: 'begin'\n        };\n    },\n    methods () {\n        handleClick () {\n            this.test = 'end';\n            this.$nextTick(() => {\n                console.log(this.$refs.test.innerText);//打印\"end\"\n            });\n            console.log(this.$refs.test.innerText);//打印“begin”\n        }\n    }\n}\n```\n\n使用Vue.js的global API的$nextTick方法，即可在回调中获取已经更新好的DOM实例了。\n"
  },
  {
    "path": "docs/Vuex源码解析.MarkDown",
    "content": "## Vuex\n\n我们在使用Vue.js开发复杂的应用时，经常会遇到多个组件共享同一个状态，亦或是多个组件会去更新同一个状态，在应用代码量较少的时候，我们可以组件间通信去维护修改数据，或者是通过事件总线来进行数据的传递以及修改。但是当应用逐渐庞大以后，代码就会变得难以维护，从父组件开始通过prop传递多层嵌套的数据由于层级过深而显得异常脆弱，而事件总线也会因为组件的增多、代码量的增大而显得交互错综复杂，难以捋清其中的传递关系。\n\n那么为什么我们不能将数据层与组件层抽离开来呢？把数据层放到全局形成一个单一的Store，组件层变得更薄，专门用来进行数据的展示及操作。所有数据的变更都需要经过全局的Store来进行，形成一个单向数据流，使数据变化变得“可预测”。\n\nVuex是一个专门为Vue.js框架设计的、用于对Vue.js应用程序进行状态管理的库，它借鉴了Flux、redux的基本思想，将共享的数据抽离到全局，以一个单例存放，同时利用Vue.js的响应式机制来进行高效的状态管理与更新。正是因为Vuex使用了Vue.js内部的“响应式机制”，所以Vuex是一个专门为Vue.js设计并与之高度契合的框架（优点是更加简洁高效，缺点是只能跟Vue.js搭配使用）。具体使用方法及API可以参考[Vuex的官网](https://vuex.vuejs.org/zh-cn/intro.html)。\n\n先来看一下这张Vuex的数据流程图，熟悉Vuex使用的同学应该已经有所了解。\n\n![](https://vuex.vuejs.org/vuex.png)\n\nVuex实现了一个单向数据流，在全局拥有一个State存放数据，所有修改State的操作必须通过Mutation进行，Mutation的同时提供了订阅者模式供外部插件调用获取State数据的更新。所有异步接口需要走Action，常见于调用后端接口异步获取更新数据，而Action也是无法直接修改State的，还是需要通过Mutation来修改State的数据。最后，根据State的变化，渲染到视图上。Vuex运行依赖Vue内部数据双向绑定机制，需要new一个Vue对象来实现“响应式化”，所以Vuex是一个专门为Vue.js设计的状态管理库。\n\n## 安装\n\n使用过Vuex的朋友一定知道，Vuex的安装十分简单，只需要提供一个store，然后执行下面两句代码即完成的Vuex的引入。\n\n```javascript\nVue.use(Vuex);\n\n/*将store放入Vue创建时的option中*/\nnew Vue({\n    el: '#app',\n    store\n});\n```\n\n那么问题来了，Vuex是怎样把store注入到Vue实例中去的呢？\n\nVue.js提供了[Vue.use](https://cn.vuejs.org/v2/api/#Vue-use)方法用来给Vue.js安装插件，内部通过调用插件的install方法(当插件是一个对象的时候)来进行插件的安装。\n\n我们来看一下Vuex的install实现。\n\n```javascript\n/*暴露给外部的插件install方法，供Vue.use调用安装插件*/\nexport function install (_Vue) {\n  if (Vue) {\n    /*避免重复安装（Vue.use内部也会检测一次是否重复安装同一个插件）*/\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(\n        '[vuex] already installed. Vue.use(Vuex) should be called only once.'\n      )\n    }\n    return\n  }\n  /*保存Vue，同时用于检测是否重复安装*/\n  Vue = _Vue\n  /*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/\n  applyMixin(Vue)\n}\n```\n\n这段install代码做了两件事情，一件是防止Vuex被重复安装，另一件是执行applyMixin，目的是执行vuexInit方法初始化Vuex。Vuex针对Vue1.0与2.0分别进行了不同的处理，如果是Vue1.0，Vuex会将vuexInit方法放入Vue的_init方法中，而对于Vue2.0，则会将vuexinit混淆进Vue的beforeCreate钩子中。来看一下vuexInit的代码。\n\n```javascript\n /*Vuex的init钩子，会存入每一个Vue实例等钩子列表*/\n  function vuexInit () {\n    const options = this.$options\n    // store injection\n    if (options.store) {\n      /*存在store其实代表的就是Root节点，直接执行store（function时）或者使用store（非function）*/\n      this.$store = typeof options.store === 'function'\n        ? options.store()\n        : options.store\n    } else if (options.parent && options.parent.$store) {\n      /*子组件直接从父组件中获取$store，这样就保证了所有组件都公用了全局的同一份store*/\n      this.$store = options.parent.$store\n    }\n  }\n```\n\nvuexInit会尝试从options中获取store，如果当前组件是根组件（Root节点），则options中会存在store，直接获取赋值给$store即可。如果当前组件非根组件，则通过options中的parent获取父组件的$store引用。这样一来，所有的组件都获取到了同一份内存地址的Store实例，于是我们可以在每一个组件中通过this.$store愉快地访问全局的Store实例了。\n\n那么，什么是Store实例？\n\n## Store\n\n我们传入到根组件的store，就是Store实例，用Vuex提供的Store方法构造。\n\n```javascript\nexport default new Vuex.Store({\n    strict: true,\n    modules: {\n        moduleA,\n        moduleB\n    }\n});\n```\n\n我们来看一下Store的实现。首先是构造函数。\n\n```javascript\nconstructor (options = {}) {\n    // Auto install if it is not done yet and `window` has `Vue`.\n    // To allow users to avoid auto-installation in some cases,\n    // this code should be placed here. See #731\n    /*\n      在浏览器环境下，如果插件还未安装（!Vue即判断是否未安装），则它会自动安装。\n      它允许用户在某些情况下避免自动安装。\n    */\n    if (!Vue && typeof window !== 'undefined' && window.Vue) {\n      install(window.Vue)\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)\n      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)\n      assert(this instanceof Store, `Store must be called with the new operator.`)\n    }\n\n    const {\n      /*一个数组，包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数，可以监听 mutation（用于外部地数据持久化、记录或调试）或者提交 mutation （用于内部数据，例如 websocket 或 某些观察者）*/\n      plugins = [],\n      /*使 Vuex store 进入严格模式，在严格模式下，任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。*/\n      strict = false\n    } = options\n\n    /*从option中取出state，如果state是function则执行，最终得到一个对象*/\n    let {\n      state = {}\n    } = options\n    if (typeof state === 'function') {\n      state = state()\n    }\n\n    // store internal state\n    /* 用来判断严格模式下是否是用mutation修改state的 */\n    this._committing = false\n    /* 存放action */\n    this._actions = Object.create(null)\n    /* 存放mutation */\n    this._mutations = Object.create(null)\n    /* 存放getter */\n    this._wrappedGetters = Object.create(null)\n    /* module收集器 */\n    this._modules = new ModuleCollection(options)\n    /* 根据namespace存放module */\n    this._modulesNamespaceMap = Object.create(null)\n    /* 存放订阅者 */\n    this._subscribers = []\n    /* 用以实现Watch的Vue实例 */\n    this._watcherVM = new Vue()\n\n    // bind commit and dispatch to self\n    /*将dispatch与commit调用的this绑定为store对象本身，否则在组件内部this.dispatch时的this会指向组件的vm*/\n    const store = this\n    const { dispatch, commit } = this\n    /* 为dispatch与commit绑定this（Store实例本身） */\n    this.dispatch = function boundDispatch (type, payload) {\n      return dispatch.call(store, type, payload)\n    }\n    this.commit = function boundCommit (type, payload, options) {\n      return commit.call(store, type, payload, options)\n    }\n\n    // strict mode\n    /*严格模式(使 Vuex store 进入严格模式，在严格模式下，任何 mutation 处理函数以外修改 Vuex state 都会抛出错误)*/\n    this.strict = strict\n\n    // init root module.\n    // this also recursively registers all sub-modules\n    // and collects all module getters inside this._wrappedGetters\n    /*初始化根module，这也同时递归注册了所有子module，收集所有module的getter到_wrappedGetters中去，this._modules.root代表根module才独有保存的Module对象*/\n    installModule(this, state, [], this._modules.root)\n\n    // initialize the store vm, which is responsible for the reactivity\n    // (also registers _wrappedGetters as computed properties)\n    /* 通过vm重设store，新建Vue对象使用Vue内部的响应式实现注册state以及computed */\n    resetStoreVM(this, state)\n\n    // apply plugins\n    /* 调用插件 */\n    plugins.forEach(plugin => plugin(this))\n\n    /* devtool插件 */\n    if (Vue.config.devtools) {\n      devtoolPlugin(this)\n    }\n  }\n```\n\nStore的构造类除了初始化一些内部变量以外，主要执行了installModule（初始化module）以及resetStoreVM（通过VM使store“响应式”）。\n\n### installModule\n\ninstallModule的作用主要是为module加上namespace名字空间（如果有）后，注册mutation、action以及getter，同时递归安装所有子module。\n\n```javascript\n/*初始化module*/\nfunction installModule (store, rootState, path, module, hot) {\n  /* 是否是根module */\n  const isRoot = !path.length\n  /* 获取module的namespace */\n  const namespace = store._modules.getNamespace(path)\n\n  // register in namespace map\n  /* 如果有namespace则在_modulesNamespaceMap中注册 */\n  if (module.namespaced) {\n    store._modulesNamespaceMap[namespace] = module\n  }\n\n  // set state\n  if (!isRoot && !hot) {\n    /* 获取父级的state */\n    const parentState = getNestedState(rootState, path.slice(0, -1))\n    /* module的name */\n    const moduleName = path[path.length - 1]\n    store.`_withCommit`(() => {\n      /* 将子module设成响应式的 */\n      Vue.set(parentState, moduleName, module.state)\n    })\n  }\n\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  /* 遍历注册mutation */\n  module.forEachMutation((mutation, key) => {\n    const namespacedType = namespace + key\n    registerMutation(store, namespacedType, mutation, local)\n  })\n\n  /* 遍历注册action */\n  module.forEachAction((action, key) => {\n    const namespacedType = namespace + key\n    registerAction(store, namespacedType, action, local)\n  })\n\n  /* 遍历注册getter */\n  module.forEachGetter((getter, key) => {\n    const namespacedType = namespace + key\n    registerGetter(store, namespacedType, getter, local)\n  })\n\n  /* 递归安装mudule */\n  module.forEachChild((child, key) => {\n    installModule(store, rootState, path.concat(key), child, hot)\n  })\n}\n```\n\n### resetStoreVM\n\n在说resetStoreVM之前，先来看一个小demo。\n\n```javascript\nlet globalData = {\n    d: 'hello world'\n};\nnew Vue({\n    data () {\n        return {\n            $$state: {\n                globalData\n            }\n        }\n    }\n});\n\n/* modify */\nsetTimeout(() => {\n    globalData.d = 'hi~';\n}, 1000);\n\nVue.prototype.globalData = globalData;\n\n/* 任意模板中 */\n<div>{{globalData.d}}</div>\n```\n\n上述代码在全局有一个globalData，它被传入一个Vue对象的data中，之后在任意Vue模板中对该变量进行展示，因为此时globalData已经在Vue的prototype上了所以直接通过this.prototype访问，也就是在模板中的{{prototype.d}}。此时，setTimeout在1s之后将globalData.d进行修改，我们发现模板中的globalData.d发生了变化。其实上述部分就是Vuex依赖Vue核心实现数据的“响应式化”。\n\n不熟悉Vue.js响应式原理的同学可以通过笔者另一篇文章[响应式原理](https://github.com/answershuto/learnVue/blob/master/docs/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86.MarkDown)了解Vue.js是如何进行数据双向绑定的。\n\n接着来看代码。\n\n```javascript\n/* 通过vm重设store，新建Vue对象使用Vue内部的响应式实现注册state以及computed */\nfunction resetStoreVM (store, state, hot) {\n  /* 存放之前的vm对象 */\n  const oldVm = store._vm \n\n  // bind store public getters\n  store.getters = {}\n  const wrappedGetters = store._wrappedGetters\n  const computed = {}\n\n  /* 通过Object.defineProperty为每一个getter方法设置get方法，比如获取this.$store.getters.test的时候获取的是store._vm.test，也就是Vue对象的computed属性 */\n  forEachValue(wrappedGetters, (fn, key) => {\n    // use computed to leverage its lazy-caching mechanism\n    computed[key] = () => fn(store)\n    Object.defineProperty(store.getters, key, {\n      get: () => store._vm[key],\n      enumerable: true // for local getters\n    })\n  })\n\n  // use a Vue instance to store the state tree\n  // suppress warnings just in case the user has added\n  // some funky global mixins\n  const silent = Vue.config.silent\n  /* Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程中不会报出一切警告 */\n  Vue.config.silent = true\n  /*  这里new了一个Vue对象，运用Vue内部的响应式实现注册state以及computed*/\n  store._vm = new Vue({\n    data: {\n      $$state: state\n    },\n    computed\n  })\n  Vue.config.silent = silent\n\n  // enable strict mode for new vm\n  /* 使能严格模式，保证修改store只能通过mutation */\n  if (store.strict) {\n    enableStrictMode(store)\n  }\n\n  if (oldVm) {\n    /* 解除旧vm的state的引用，以及销毁旧的Vue对象 */\n    if (hot) {\n      // dispatch changes in all subscribed watchers\n      // to force getter re-evaluation for hot reloading.\n      store._withCommit(() => {\n        oldVm._data.$$state = null\n      })\n    }\n    Vue.nextTick(() => oldVm.$destroy())\n  }\n}\n```\n\nresetStoreVM首先会遍历wrappedGetters，使用Object.defineProperty方法为每一个getter绑定上get方法，这样我们就可以在组件里访问this.$store.getters.test就等同于访问store._vm.test。\n\n```javascript\nforEachValue(wrappedGetters, (fn, key) => {\n  // use computed to leverage its lazy-caching mechanism\n  computed[key] = () => fn(store)\n  Object.defineProperty(store.getters, key, {\n    get: () => store._vm[key],\n    enumerable: true // for local getters\n  })\n})\n```\n\n之后Vuex采用了new一个Vue对象来实现数据的“响应式化”，运用Vue.js内部提供的数据双向绑定功能来实现store的数据与视图的同步更新。\n\n```javascript\nstore._vm = new Vue({\n  data: {\n    $$state: state\n  },\n  computed\n})\n```\n\n这时候我们访问store._vm.test也就访问了Vue实例中的属性。\n\n这两步执行完以后，我们就可以通过this.$store.getter.test访问vm中的test属性了。\n\n### 严格模式\n\nVuex的Store构造类的option有一个strict的参数，可以控制Vuex执行严格模式，严格模式下，所有修改state的操作必须通过mutation实现，否则会抛出错误。\n\n```javascript\n/* 使能严格模式 */\nfunction enableStrictMode (store) {\n  store._vm.$watch(function () { return this._data.$$state }, () => {\n    if (process.env.NODE_ENV !== 'production') {\n      /* 检测store中的_committing的值，如果是false代表不是通过mutation的方法修改的 */\n      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)\n    }\n  }, { deep: true, sync: true })\n}\n```\n\n首先，在严格模式下，Vuex会利用vm的$watch方法来观察$$state，也就是Store的state，在它被修改的时候进入回调。我们发现，回调中只有一句话，用assert断言来检测store._committing，当store._committing为false的时候会触发断言，抛出异常。\n\n我们发现，Store的commit方法中，执行mutation的语句是这样的。\n\n```javascript\nthis._withCommit(() => {\n  entry.forEach(function commitIterator (handler) {\n    handler(payload)\n  })\n})\n```\n\n再来看看_withCommit的实现。\n\n```javascript\n_withCommit (fn) {\n  /* 调用withCommit修改state的值时会将store的committing值置为true，内部会有断言检查该值，在严格模式下只允许使用mutation来修改store中的值，而不允许直接修改store的数值 */\n  const committing = this._committing\n  this._committing = true\n  fn()\n  this._committing = committing\n}\n```\n\n我们发现，通过commit（mutation）修改state数据的时候，会在调用mutation方法之前将committing置为true，接下来再通过mutation函数修改state中的数据，这时候触发$watch中的回调断言committing是不会抛出异常的（此时committing为true）。而当我们直接修改state的数据时，触发$watch的回调执行断言，这时committing为false，则会抛出异常。这就是Vuex的严格模式的实现。\n\n接下来我们来看看Store提供的一些API。\n\n### commit（[mutation](https://vuex.vuejs.org/zh-cn/mutations.html)）\n\n```javascript\n/* 调用mutation的commit方法 */\ncommit (_type, _payload, _options) {\n  // check object-style commit\n  /* 校验参数 */\n  const {\n    type,\n    payload,\n    options\n  } = unifyObjectStyle(_type, _payload, _options)\n\n  const mutation = { type, payload }\n  /* 取出type对应的mutation的方法 */\n  const entry = this._mutations[type]\n  if (!entry) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] unknown mutation type: ${type}`)\n    }\n    return\n  }\n  /* 执行mutation中的所有方法 */\n  this._withCommit(() => {\n    entry.forEach(function commitIterator (handler) {\n      handler(payload)\n    })\n  })\n  /* 通知所有订阅者 */\n  this._subscribers.forEach(sub => sub(mutation, this.state))\n\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    options && options.silent\n  ) {\n    console.warn(\n      `[vuex] mutation type: ${type}. Silent option has been removed. ` +\n      'Use the filter functionality in the vue-devtools'\n    )\n  }\n}\n```\n\ncommit方法会根据type找到并调用_mutations中的所有type对应的mutation方法，所以当没有namespace的时候，commit方法会触发所有module中的mutation方法。再执行完所有的mutation之后会执行_subscribers中的所有订阅者。我们来看一下_subscribers是什么。\n\nStore给外部提供了一个subscribe方法，用以注册一个订阅函数，会push到Store实例的_subscribers中，同时返回一个从_subscribers中注销该订阅者的方法。\n\n```javascript\n/* 注册一个订阅函数，返回取消订阅的函数 */\nsubscribe (fn) {\n  const subs = this._subscribers\n  if (subs.indexOf(fn) < 0) {\n    subs.push(fn)\n  }\n  return () => {\n    const i = subs.indexOf(fn)\n    if (i > -1) {\n      subs.splice(i, 1)\n    }\n  }\n}\n```\n\n在commit结束以后则会调用这些_subscribers中的订阅者，这个订阅者模式提供给外部一个监视state变化的可能。state通过mutation改变时，可以有效补获这些变化。\n\n### dispatch（[action](https://vuex.vuejs.org/zh-cn/actions.html)）\n\n来看一下dispatch的实现。\n\n```javascript\n/* 调用action的dispatch方法 */\ndispatch (_type, _payload) {\n  // check object-style dispatch\n  const {\n    type,\n    payload\n  } = unifyObjectStyle(_type, _payload)\n\n  /* actions中取出type对应的action */\n  const entry = this._actions[type]\n  if (!entry) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] unknown action type: ${type}`)\n    }\n    return\n  }\n\n  /* 是数组则包装Promise形成一个新的Promise，只有一个则直接返回第0个 */\n  return entry.length > 1\n    ? Promise.all(entry.map(handler => handler(payload)))\n    : entry[0](payload)\n}\n```\n\n以及registerAction时候做的事情。\n\n```javascript\n/* 遍历注册action */\nfunction registerAction (store, type, handler, local) {\n  /* 取出type对应的action */\n  const entry = store._actions[type] || (store._actions[type] = [])\n  entry.push(function wrappedActionHandler (payload, cb) {\n    let res = handler.call(store, {\n      dispatch: local.dispatch,\n      commit: local.commit,\n      getters: local.getters,\n      state: local.state,\n      rootGetters: store.getters,\n      rootState: store.state\n    }, payload, cb)\n    /* 判断是否是Promise */\n    if (!isPromise(res)) {\n      /* 不是Promise对象的时候转化称Promise对象 */\n      res = Promise.resolve(res)\n    }\n    if (store._devtoolHook) {\n      /* 存在devtool插件的时候触发vuex的error给devtool */\n      return res.catch(err => {\n        store._devtoolHook.emit('vuex:error', err)\n        throw err\n      })\n    } else {\n      return res\n    }\n  })\n}\n```\n\n因为registerAction的时候将push进_actions的action进行了一层封装（wrappedActionHandler），所以我们在进行dispatch的第一个参数中获取state、commit等方法。之后，执行结果res会被进行判断是否是Promise，不是则会进行一层封装，将其转化成Promise对象。dispatch时则从_actions中取出，只有一个的时候直接返回，否则用Promise.all处理再返回。\n\n### watch\n\n```javascript\n/* 观察一个getter方法 */\nwatch (getter, cb, options) {\n  if (process.env.NODE_ENV !== 'production') {\n    assert(typeof getter === 'function', `store.watch only accepts a function.`)\n  }\n  return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)\n}\n```\n\n熟悉Vue的朋友应该很熟悉watch这个方法。这里采用了比较巧妙的设计，_watcherVM是一个Vue的实例，所以watch就可以直接采用了Vue内部的watch特性提供了一种观察数据getter变动的方法。\n\n### registerModule\n\n```javascript\n/* 注册一个动态module，当业务进行异步加载的时候，可以通过该接口进行注册动态module */\nregisterModule (path, rawModule) {\n  /* 转化称Array */\n  if (typeof path === 'string') path = [path]\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(Array.isArray(path), `module path must be a string or an Array.`)\n    assert(path.length > 0, 'cannot register the root module by using registerModule.')\n  }\n\n  /*注册*/\n  this._modules.register(path, rawModule)\n  /*初始化module*/\n  installModule(this, this.state, path, this._modules.get(path))\n  // reset store to update getters...\n  /* 通过vm重设store，新建Vue对象使用Vue内部的响应式实现注册state以及computed */\n  resetStoreVM(this, this.state)\n}\n```\n\nregisterModule用以注册一个动态模块，也就是在store创建以后再注册模块的时候用该接口。内部实现实际上也只有installModule与resetStoreVM两个步骤，前面已经讲过，这里不再累述。\n\n### unregisterModule\n\n```javascript\n /* 注销一个动态module */\nunregisterModule (path) {\n  /* 转化称Array */\n  if (typeof path === 'string') path = [path]\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(Array.isArray(path), `module path must be a string or an Array.`)\n  }\n\n  /*注销*/\n  this._modules.unregister(path)\n  this._withCommit(() => {\n    /* 获取父级的state */\n    const parentState = getNestedState(this.state, path.slice(0, -1))\n    /* 从父级中删除 */\n    Vue.delete(parentState, path[path.length - 1])\n  })\n  /* 重制store */\n  resetStore(this)\n}\n```\n\n同样，与registerModule对应的方法unregisterModule，动态注销模块。实现方法是先从state中删除模块，然后用resetStore来重制store。\n\n### resetStore\n\n```javascript\n/* 重制store */\nfunction resetStore (store, hot) {\n  store._actions = Object.create(null)\n  store._mutations = Object.create(null)\n  store._wrappedGetters = Object.create(null)\n  store._modulesNamespaceMap = Object.create(null)\n  const state = store.state\n  // init all modules\n  installModule(store, state, [], store._modules.root, true)\n  // reset vm\n  resetStoreVM(store, state, hot)\n}\n```\n\n这里的resetStore其实也就是将store中的_actions等进行初始化以后，重新执行installModule与resetStoreVM来初始化module以及用Vue特性使其“响应式化”，这跟构造函数中的是一致的。\n\n## 插件\n\nVue提供了一个非常好用的插件[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)\n\n```javascript\n/* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件 */\nconst devtoolHook =\n  typeof window !== 'undefined' &&\n  window.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\nexport default function devtoolPlugin (store) {\n  if (!devtoolHook) return\n\n  /* devtoll插件实例存储在store的_devtoolHook上 */\n  store._devtoolHook = devtoolHook\n\n  /* 出发vuex的初始化事件，并将store的引用地址传给deltool插件，使插件获取store的实例 */\n  devtoolHook.emit('vuex:init', store)\n\n  /* 监听travel-to-state事件 */\n  devtoolHook.on('vuex:travel-to-state', targetState => {\n    /* 重制state */\n    store.replaceState(targetState)\n  })\n\n  /* 订阅store的变化 */\n  store.subscribe((mutation, state) => {\n    devtoolHook.emit('vuex:mutation', mutation, state)\n  })\n}\n```\n\n如果已经安装了该插件，则会在windows对象上暴露一个__VUE_DEVTOOLS_GLOBAL_HOOK__。devtoolHook用在初始化的时候会触发“vuex:init”事件通知插件，然后通过on方法监听“vuex:travel-to-state”事件来重置state。最后通过Store的subscribe方法来添加一个订阅者，在触发commit方法修改mutation数据以后，该订阅者会被通知，从而触发“vuex:mutation”事件。\n\n## 最后\n\nVuex是一个非常优秀的库，代码量不多且结构清晰，非常适合研究学习其内部实现。最近的一系列源码阅读也使我自己受益匪浅，写这篇文章也希望可以帮助到更多想要学习探索Vuex内部实现原理的同学。"
  },
  {
    "path": "docs/Vue事件机制.MarkDown",
    "content": "## Vue事件API\n\n众所周知，Vue.js为我们提供了四个事件API，分别是[$on](https://cn.vuejs.org/v2/api/#vm-on-event-callback)，[$once](https://cn.vuejs.org/v2/api/#vm-once-event-callback)，[$off](https://cn.vuejs.org/v2/api/#vm-off-event-callback)，[$emit](https://cn.vuejs.org/v2/api/#vm-emit-event-…args)。\n\n## 初始化事件\n\n初始化事件在vm上创建一个_events对象，用来存放事件。_events的内容如下：\n```javascript\n{\n    eventName: [func1, func2, func3]\n}\n```\n存放事件名以及对应执行方法。\n\n\n```javascript\n/*初始化事件*/\nexport function initEvents (vm: Component) {\n  /*在vm上创建一个_events对象，用来存放事件。*/\n  vm._events = Object.create(null)\n  /*这个bool标志位来表明是否存在钩子，而不需要通过哈希表的方法来查找是否有钩子，这样做可以减少不必要的开销，优化性能。*/\n  vm._hasHookEvent = false\n  // init parent attached events\n  /*初始化父组件attach的事件*/\n  const listeners = vm.$options._parentListeners\n  if (listeners) {\n    updateComponentListeners(vm, listeners)\n  }\n}\n```\n\n## $on\n\n$on方法用来在vm实例上监听一个自定义事件，该事件可用$emit触发。\n\n```javascript\n  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {\n    const vm: Component = this\n\n    /*如果是数组的时候，则递归$on，为每一个成员都绑定上方法*/\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$on(event[i], fn)\n      }\n    } else {\n      (vm._events[event] || (vm._events[event] = [])).push(fn)\n      // optimize hook:event cost by using a boolean flag marked at registration\n      // instead of a hash lookup\n      /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子，而不需要通过哈希表的方法来查找是否有钩子，这样做可以减少不必要的开销，优化性能。*/\n      if (hookRE.test(event)) {\n        vm._hasHookEvent = true\n      }\n    }\n    return vm\n  }\n```\n\n## $once\n\n$once监听一个只能触发一次的事件，在触发以后会自动移除该事件。\n\n```javascript\n  Vue.prototype.$once = function (event: string, fn: Function): Component {\n    const vm: Component = this\n    function on () {\n      /*在第一次执行的时候将该事件销毁*/\n      vm.$off(event, on)\n      /*执行注册的方法*/\n      fn.apply(vm, arguments)\n    }\n    on.fn = fn\n    vm.$on(event, on)\n    return vm\n  }\n```\n\n## $off\n\n$off用来移除自定义事件\n\n```javascript\nVue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {\n    const vm: Component = this\n    // all\n    /*如果不传参数则注销所有事件*/\n    if (!arguments.length) {\n      vm._events = Object.create(null)\n      return vm\n    }\n    // array of events\n    /*如果event是数组则递归注销事件*/\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$off(event[i], fn)\n      }\n      return vm\n    }\n    // specific event\n    const cbs = vm._events[event]\n    /*Github:https://github.com/answershuto*/\n    /*本身不存在该事件则直接返回*/\n    if (!cbs) {\n      return vm\n    }\n    /*如果只传了event参数则注销该event方法下的所有方法*/\n    if (arguments.length === 1) {\n      vm._events[event] = null\n      return vm\n    }\n    // specific handler\n    /*遍历寻找对应方法并删除*/\n    let cb\n    let i = cbs.length\n    while (i--) {\n      cb = cbs[i]\n      if (cb === fn || cb.fn === fn) {\n        cbs.splice(i, 1)\n        break\n      }\n    }\n    return vm\n  }\n```\n\n## $emit\n\n$emit用来触发指定的自定义事件。\n\n```javascript\nVue.prototype.$emit = function (event: string): Component {\n    const vm: Component = this\n    if (process.env.NODE_ENV !== 'production') {\n      const lowerCaseEvent = event.toLowerCase()\n      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {\n        tip(\n          `Event \"${lowerCaseEvent}\" is emitted in component ` +\n          `${formatComponentName(vm)} but the handler is registered for \"${event}\". ` +\n          `Note that HTML attributes are case-insensitive and you cannot use ` +\n          `v-on to listen to camelCase events when using in-DOM templates. ` +\n          `You should probably use \"${hyphenate(event)}\" instead of \"${event}\".`\n        )\n      }\n    }\n    let cbs = vm._events[event]\n    if (cbs) {\n      /*将类数组的对象转换成数组*/\n      cbs = cbs.length > 1 ? toArray(cbs) : cbs\n      const args = toArray(arguments, 1)\n      /*遍历执行*/\n      for (let i = 0, l = cbs.length; i < l; i++) {\n        cbs[i].apply(vm, args)\n      }\n    }\n    return vm\n  }\n```"
  },
  {
    "path": "docs/Vue组件间通信.MarkDown",
    "content": "## 什么是Vue组件？\n\n[组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素，封装可重用的代码。在较高层面上，组件是自定义元素，Vue.js 的编译器为它添加特殊功能。在有些情况下，组件也可以是原生 HTML 元素的形式，以 is 特性扩展。](https://cn.vuejs.org/v2/guide/components.html)\n\n<br />\n<br />\n\n## Vue组件间通信\n\n### 父组件向子组件通信\n\n#### 方法一：props\n\n使用[props](https://cn.vuejs.org/v2/guide/components.html#Prop)，父组件可以使用props向子组件传递数据。\n\n父组件vue模板father.vue\n\n```\n<template>\n    <child :msg=\"message\"></child>\n</template>\n\n<script>\n\nimport child from './child.vue';\n\nexport default {\n    components: {\n        child\n    },\n    data () {\n        return {\n            message: 'father message';\n        }\n    }\n}\n</script>\n```\n\n子组件vue模板child.vue\n\n```\n<template>\n    <div>{{msg}}</div>\n</template>\n\n<script>\nexport default {\n    props: {\n        msg: {\n            type: String,\n            required: true\n        }\n    }\n}\n</script>\n```\n\n<br />\n\n#### 方法二 使用$children\n\n使用[$children](https://cn.vuejs.org/v2/api/#vm-children)可以在父组件中访问子组件。\n\n<br /> \n<br /> \n\n### 子组件向父组件通信\n\n<br />\n\n#### 方法一:使用[vue事件](https://cn.vuejs.org/v2/guide/components.html#使用-v-on-绑定自定义事件)\n\n父组件向子组件传递事件方法，子组件通过$emit触发事件，回调给父组件。\n\n父组件vue模板father.vue\n\n```\n<template>\n    <child @msgFunc=\"func\"></child>\n</template>\n\n<script>\n\nimport child from './child.vue';\n\nexport default {\n    components: {\n        child\n    },\n    methods: {\n        func (msg) {\n            console.log(msg);\n        }\n    }\n}\n</script>\n```\n\n子组件vue模板child.vue\n\n```\n<template>\n    <button @click=\"handleClick\">点我</button>\n</template>\n\n<script>\nexport default {\n    props: {\n        msg: {\n            type: String,\n            required: true\n        }\n    },\n    methods () {\n        handleClick () {\n            //........\n            this.$emit('msgFunc');\n        }\n    }\n}\n</script>\n```\n\n<br />\n\n#### 方法二： 通过修改父组件传递的props来修改父组件数据\n\n这种方法只能在父组件传递一个引用变量时可以使用，字面变量无法达到相应效果。因为引用变量最终无论是父组件中的数据还是子组件得到的props中的数据都是指向同一块内存地址，所以修改了子组件中props的数据即修改了父组件的数据。\n\n但是并不推荐这么做，并不建议直接修改props的值，如果数据是用于显示修改的，在实际开发中我经常会将其放入data中，在需要回传给父组件的时候再用事件回传数据。这样做保持了组件独立以及解耦，不会因为使用同一份数据而导致数据流异常混乱，只通过特定的接口传递数据来达到修改数据的目的，而内部数据状态由专门的data负责管理。\n\n<br />\n\n#### 方法三：使用$parent\n\n使用[$parent](https://cn.vuejs.org/v2/api/#vm-parent)可以访问父组件的数据。\n\n<br />\n<br />\n\n### 非父子组件、兄弟组件之间的数据传递\n\n非父子组件通信，Vue官方推荐[使用一个Vue实例作为中央事件总线](https://cn.vuejs.org/v2/guide/components.html#非父子组件通信)。\n\nVue内部有一个事件机制，可以参考[源码](https://github.com/vuejs/vue/blob/dev/src/core/instance/events.js)。\n\n$on方法用来监听一个事件。\n\n$emit用来触发一个事件。\n\n```javascript\n/*新建一个Vue实例作为中央事件总线*/\nlet event = new Vue();\n\n/*监听事件*/\nevent.$on('eventName', (val) => {\n    //......do something\n});\n\n/*触发事件*/\nevent.$emit('eventName', 'this is a message.');\n```\n\n<br />\n<br />\n\n### 多层级父子组件通信：\n\n在Vue1.0中实现了$broadcast与$dispatch两个方法用来向子组件（或父组件）广播（或派发），当子组件（或父组件）上监听了事件并返回true的时候会向爷孙级组件继续广播（或派发）事件。但是这个方法在Vue2.0里面已经被移除了。\n\n之前在学习饿了么的开源组件库[element](https://github.com/ElemeFE/element)的时候发现他们重新实现了broadcast以及dispatch的方法，以mixin的方式引入，具体可以参考[《说说element组件库broadcast与dispatch》](https://github.com/answershuto/learnVue/blob/master/docs/%E8%AF%B4%E8%AF%B4element%E7%BB%84%E4%BB%B6%E5%BA%93broadcast%E4%B8%8Edispatch.MarkDown)。但是跟Vue1.0的两个方法实现有略微的不同。这两个方法实现了向子孙组件事件广播以及向多层级父组件事件派发的功能。但是并非广义上的事件广播，它需要指定一个commentName进行向指定组件名组件定向广播（派发）事件。\n\n其实这两个方法内部实现还是用到的还是$parent以及$children，用以遍历子节点或是逐级向上查询父节点，访问到指定组件名的时候，调用$emit触发指定事件。\n\n<br />\n<br />\n\n### 复杂的单页应用数据管理\n\n当应用足够复杂情况下，请使用[vuex](https://cn.vuejs.org/v2/guide/state-management.html)进行数据管理。\n"
  },
  {
    "path": "docs/从template到DOM(Vue.js源码角度看内部运行机制).MarkDown",
    "content": "## 从new一个Vue对象开始\n\n```javascript\nlet vm = new Vue({\n    el: '#app',\n    /*some options*/\n});\n```\n\n很多同学好奇，在new一个Vue对象的时候，内部究竟发生了什么？\n\n究竟Vue.js是如何将data中的数据渲染到真实的宿主环境中的？\n\n又是如何通过“响应式”修改数据的？\n\ntemplate是如何被编译成真实环境中可用的HTML的？\n\nVue指令又是如何执行的？\n\n带着这些疑问，我们从Vue的构造类开始看起。\n\n## Vue构造类\n\n```javascript\nfunction Vue (options) {\n  if (process.env.NODE_ENV !== 'production' &&\n    !(this instanceof Vue)) {\n    warn('Vue is a constructor and should be called with the `new` keyword')\n  }\n  /*初始化*/\n  this._init(options)\n}\n```\n\nVue的构造类只做了一件事情，就是调用_init函数进行初始化\n\n来看一下init的代码\n\n```javascript\nVue.prototype._init = function (options?: Object) {\n    const vm: Component = this\n    // a uid\n    vm._uid = uid++\n\n    let startTag, endTag\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      startTag = `vue-perf-init:${vm._uid}`\n      endTag = `vue-perf-end:${vm._uid}`\n      mark(startTag)\n    }\n\n    // a flag to avoid this being observed\n    /*一个防止vm实例自身被观察的标志位*/\n    vm._isVue = true\n    // merge options\n    if (options && options._isComponent) {\n      // optimize internal component instantiation\n      // since dynamic options merging is pretty slow, and none of the\n      // internal component options needs special treatment.\n      initInternalComponent(vm, options)\n    } else {\n      vm.$options = mergeOptions(\n        resolveConstructorOptions(vm.constructor),\n        options || {},\n        vm\n      )\n    }\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      initProxy(vm)\n    } else {\n      vm._renderProxy = vm\n    }\n    // expose real self\n    vm._self = vm\n    /*初始化生命周期*/\n    initLifecycle(vm)\n    /*初始化事件*/\n    initEvents(vm)\n    /*初始化render*/\n    initRender(vm)\n    /*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/\n    callHook(vm, 'beforeCreate')\n    initInjections(vm) // resolve injections before data/props\n    /*初始化props、methods、data、computed与watch*/\n    initState(vm)\n    initProvide(vm) // resolve provide after data/props\n    /*调用created钩子函数并且触发created钩子事件*/\n    callHook(vm, 'created')\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      /*格式化组件名*/\n      vm._name = formatComponentName(vm, false)\n      mark(endTag)\n      measure(`${vm._name} init`, startTag, endTag)\n    }\n\n    if (vm.$options.el) {\n      /*挂载组件*/\n      vm.$mount(vm.$options.el)\n    }\n  }\n```\n\n_init主要做了这两件事：\n\n1.初始化（包括生命周期、事件、render函数、state等）。\n\n2.$mount组件。\n\n在生命钩子beforeCreate与created之间会初始化state，在此过程中，会依次初始化props、methods、data、computed与watch，这也就是Vue.js对options中的数据进行“响应式化”（即双向绑定）的过程。对于Vue.js响应式原理不了解的同学可以先看一下笔者的另一片文章[《Vue.js响应式原理》](https://github.com/answershuto/learnVue/blob/master/docs/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86.MarkDown)。\n\n```javascript\n/*初始化props、methods、data、computed与watch*/\nexport function initState (vm: Component) {\n  vm._watchers = []\n  const opts = vm.$options\n  /*初始化props*/\n  if (opts.props) initProps(vm, opts.props)\n  /*初始化方法*/\n  if (opts.methods) initMethods(vm, opts.methods)\n  /*初始化data*/\n  if (opts.data) {\n    initData(vm)\n  } else {\n    /*该组件没有data的时候绑定一个空对象*/\n    observe(vm._data = {}, true /* asRootData */)\n  }\n  /*初始化computed*/\n  if (opts.computed) initComputed(vm, opts.computed)\n  /*初始化watchers*/\n  if (opts.watch) initWatch(vm, opts.watch)\n}\n\n```\n\n## 双向绑定\n\n以initData为例，对option的data的数据进行双向绑定Oberver，其他option参数双向绑定的核心原理是一致的。\n\n```javascript\nfunction initData (vm: Component) {\n\n  /*得到data数据*/\n  let data = vm.$options.data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n\n  /*判断是否是对象*/\n  if (!isPlainObject(data)) {\n    data = {}\n    process.env.NODE_ENV !== 'production' && warn(\n      'data functions should return an object:\\n' +\n      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',\n      vm\n    )\n  }\n\n  // proxy data on instance\n  /*遍历data对象*/\n  const keys = Object.keys(data)\n  const props = vm.$options.props\n  let i = keys.length\n\n  //遍历data中的数据\n  while (i--) {\n    /*保证data中的key不与props中的key重复，props优先，如果有冲突会产生warning*/\n    if (props && hasOwn(props, keys[i])) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `The data property \"${keys[i]}\" is already declared as a prop. ` +\n        `Use prop default value instead.`,\n        vm\n      )\n    } else if (!isReserved(keys[i])) {\n      /*判断是否是保留字段*/\n\n      /*这里是我们前面讲过的代理，将data上面的属性代理到了vm实例上*/\n      proxy(vm, `_data`, keys[i])\n    }\n  }\n  /*Github:https://github.com/answershuto*/\n  // observe data\n  /*从这里开始我们要observe了，开始对数据进行绑定，这里有尤大大的注释asRootData，这步作为根数据，下面会进行递归observe进行对深层对象的绑定。*/\n  observe(data, true /* asRootData */)\n}\n```\n\nobserve会通过defineReactive对data中的对象进行双向绑定，最终通过Object.defineProperty对对象设置setter以及getter的方法。getter的方法主要用来进行依赖收集，对于依赖收集不了解的同学可以参考笔者的另一篇文章[《依赖收集》](https://github.com/answershuto/learnVue/blob/master/docs/%E4%BE%9D%E8%B5%96%E6%94%B6%E9%9B%86.MarkDown)。setter方法会在对象被修改的时候触发（不存在添加属性的情况，添加属性请用Vue.set），这时候setter会通知闭包中的Dep，Dep中有一些订阅了这个对象改变的Watcher观察者对象，Dep会通知Watcher对象更新视图。\n\n如果是修改一个数组的成员，该成员是一个对象，那只需要递归对数组的成员进行双向绑定即可。但这时候出现了一个问题，如果我们进行pop、push等操作的时候，push进去的对象根本没有进行过双向绑定，更别说pop了，那么我们如何监听数组的这些变化呢？\nVue.js提供的方法是重写push、pop、shift、unshift、splice、sort、reverse这七个[数组方法](http://v1-cn.vuejs.org/guide/list.html#变异方法)。修改数组原型方法的代码可以参考[observer/array.js](https://github.com/vuejs/vue/blob/dev/src/core/observer/array.js)以及[observer/index.js](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L45)。\n\n```javascript\nexport class Observer {\n  value: any;\n  dep: Dep;\n  vmCount: number; // number of vms that has this object as root $data\n\n  constructor (value: any) {\n    //.......\n\n    if (Array.isArray(value)) {\n      /*\n          如果是数组，将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法，达到监听数组数据变化响应的效果。\n          这里如果当前浏览器支持__proto__属性，则直接覆盖当前数组对象原型上的原生数组方法，如果不支持该属性，则直接覆盖数组对象的原型。\n      */\n      const augment = hasProto\n        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/\n        : copyAugment   /*定义（覆盖）目标对象或数组的某一个方法*/\n      augment(value, arrayMethods, arrayKeys)\n\n      /*如果是数组则需要遍历数组的每一个成员进行observe*/\n      this.observeArray(value)\n    } else {\n      /*如果是对象则直接walk进行绑定*/\n      this.walk(value)\n    }\n  }\n}\n\n/**\n * Augment an target Object or Array by intercepting\n * the prototype chain using __proto__\n */\n /*直接覆盖原型的方法来修改目标对象或数组*/\nfunction protoAugment (target, src: Object) {\n  /* eslint-disable no-proto */\n  target.__proto__ = src\n  /* eslint-enable no-proto */\n}\n\n/**\n * Augment an target Object or Array by defining\n * hidden properties.\n */\n/* istanbul ignore next */\n/*定义（覆盖）目标对象或数组的某一个方法*/\nfunction copyAugment (target: Object, src: Object, keys: Array<string>) {\n  for (let i = 0, l = keys.length; i < l; i++) {\n    const key = keys[i]\n    def(target, key, src[key])\n  }\n}\n```\n\n```javascript\n/*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototype\n */\n\nimport { def } from '../util/index'\n\n/*取得原生数组的原型*/\nconst arrayProto = Array.prototype\n/*创建一个新的数组对象，修改该对象上的数组的七个方法，防止污染原生数组方法*/\nexport const arrayMethods = Object.create(arrayProto)\n\n/**\n * Intercept mutating methods and emit events\n */\n /*这里重写了数组的这些方法，在保证不污染原生数组原型的情况下重写数组的这些方法，截获数组的成员发生的变化，执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/\n[\n  'push',\n  'pop',\n  'shift',\n  'unshift',\n  'splice',\n  'sort',\n  'reverse'\n]\n.forEach(function (method) {\n  // cache original method\n  /*将数组的原生方法缓存起来，后面要调用*/\n  const original = arrayProto[method]\n  def(arrayMethods, method, function mutator () {\n    // avoid leaking arguments:\n    // http://jsperf.com/closure-with-arguments\n    let i = arguments.length\n    const args = new Array(i)\n    while (i--) {\n      args[i] = arguments[i]\n    }\n    /*调用原生的数组方法*/\n    const result = original.apply(this, args)\n\n    /*数组新插入的元素需要重新进行observe才能响应式*/\n    const ob = this.__ob__\n    let inserted\n    switch (method) {\n      case 'push':\n        inserted = args\n        break\n      case 'unshift':\n        inserted = args\n        break\n      case 'splice':\n        inserted = args.slice(2)\n        break\n    }\n    if (inserted) ob.observeArray(inserted)\n      \n    // notify change\n    /*dep通知所有注册的观察者进行响应式处理*/\n    ob.dep.notify()\n    return result\n  })\n})\n\n```\n\n从数组的原型新建一个Object.create(arrayProto)对象，通过修改此原型可以保证原生数组方法不被污染。如果当前浏览器支持__proto__这个属性的话就可以直接覆盖该属性使数组对象具有了重写后的数组方法。如果浏览器没有该属性，则必须通过遍历def所有需要重写的数组方法，这种方法效率较低，所以优先使用第一种。\n\n在保证不污染不覆盖数组原生方法添加监听，主要做了两个操作，第一是通知所有注册的观察者进行响应式处理，第二是如果是添加成员的操作，需要对新成员进行observe。\n\n但是修改了数组的原生方法以后我们还是没法像原生数组一样直接通过数组的下标或者设置length来修改数组，可以通过[Vue.set以及splice方法](https://cn.vuejs.org/v2/guide/list.html#%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84)。\n\n\n对于更具体的讲解数据双向绑定以及Dep、Watcher的实现可以参考笔者的文章[《从源码角度再看数据绑定》](https://github.com/answershuto/learnVue/blob/master/docs/%E4%BB%8E%E6%BA%90%E7%A0%81%E8%A7%92%E5%BA%A6%E5%86%8D%E7%9C%8B%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A.MarkDown)。\n\n## template编译\n\n在$mount过程中，如果是使用独立构建，则会在此过程中将template编译成render function。当然，你也可以采用运行时构建。具体参考[运行时-编译器-vs-只包含运行时](https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时)。\n\ntemplate是如何被编译成render function的呢？\n\n```javascript\nfunction baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  /*parse解析得到ast树*/\n  const ast = parse(template.trim(), options)\n  /*\n    将AST树进行优化\n    优化的目标：生成模板AST树，检测不需要进行DOM改变的静态子树。\n    一旦检测到这些静态树，我们就能做以下这些事情：\n    1.把它们变成常数，这样我们就再也不需要每次重新渲染时创建新的节点了。\n    2.在patch的过程中直接跳过。\n */\n  optimize(ast, options)\n  /*根据ast树生成所需的code（内部包含render与staticRenderFns）*/\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n}\n```\n\nbaseCompile首先会将模板template进行parse得到一个AST语法树，再通过optimize做一些优化，最后通过generate得到render以及staticRenderFns。\n\n### parse\n\nparse的源码可以参见[https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53](https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53)。\n\nparse会用正则等方式解析template模板中的指令、class、style等数据，形成AST语法树。\n\n### optimize\n\noptimize的主要作用是标记static静态节点，这是Vue在编译过程中的一处优化，后面当update更新界面时，会有一个patch的过程，diff算法会直接跳过静态节点，从而减少了比较的过程，优化了patch的性能。\n\n### generate\n\ngenerate是将AST语法树转化成render funtion字符串的过程，得到结果是render的字符串以及staticRenderFns字符串。\n\n具体的template编译实现请参考[《聊聊Vue.js的template编译》](https://github.com/answershuto/learnVue/blob/master/docs/%E8%81%8A%E8%81%8AVue%E7%9A%84template%E7%BC%96%E8%AF%91.MarkDown)。\n\n\n## Watcher到视图\n\nWatcher对象会通过调用updateComponent方法来达到更新视图的目的。这里提一下，其实Watcher并不是实时更新视图的，Vue.js默认会将Watcher对象存在一个队列中，在下一个tick时更新异步更新视图，完成了性能优化。关于nextTick感兴趣的小伙伴可以参考[《Vue.js异步更新DOM策略及nextTick》](https://github.com/answershuto/learnVue/blob/master/docs/Vue.js%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0DOM%E7%AD%96%E7%95%A5%E5%8F%8AnextTick.MarkDown)。\n\n```javascript\nupdateComponent = () => {\n    vm._update(vm._render(), hydrating)\n}\n```\n\nupdateComponent就执行一句话，_render函数会返回一个新的Vnode节点，传入_update中与旧的VNode对象进行对比，经过一个patch的过程得到两个VNode节点的差异，最后将这些差异渲染到真实环境形成视图。\n\n什么是VNode？\n\n## VNode\n\n在刀耕火种的年代，我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。\n\n那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树，在修改抽象树数据后将抽象树转化成真实DOM重绘到页面上呢？于是虚拟DOM出现了，它是真实DOM的一层抽象，用属性描述真实DOM的各个特性。当它发生变化的时候，就会去修改视图。\n\n可以想象，最简单粗暴的方法就是将整个DOM结构用innerHTML修改到页面上，但是这样进行重绘整个视图层是相当消耗性能的，我们是不是可以每次只更新它的修改呢？所以Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树，以VNode节点模拟真实DOM，可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作，在这过程中都不需要操作真实DOM，只需要操作JavaScript对象后只对差异修改，相对于整块的innerHTML的粗暴式修改，大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位，再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作，大大提高了性能。\n\nVue就使用了这样的抽象节点VNode，它是对真实DOM的一层抽象，而不依赖某个平台，它可以是浏览器平台，也可以是weex，甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作，这也为前后端同构提供了可能。\n\n先来看一下Vue.js源码中对VNode类的定义。\n\n```javascript\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  functionalContext: Component | void; // only for functional component root nodes\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n\n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions\n  ) {\n    /*当前节点的标签名*/\n    this.tag = tag\n    /*当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息*/\n    this.data = data\n    /*当前节点的子节点，是一个数组*/\n    this.children = children\n    /*当前节点的文本*/\n    this.text = text\n    /*当前虚拟节点对应的真实dom节点*/\n    this.elm = elm\n    /*当前节点的名字空间*/\n    this.ns = undefined\n    /*编译作用域*/\n    this.context = context\n    /*函数化组件作用域*/\n    this.functionalContext = undefined\n    /*节点的key属性，被当作节点的标志，用以优化*/\n    this.key = data && data.key\n    /*组件的option选项*/\n    this.componentOptions = componentOptions\n    /*当前节点对应的组件的实例*/\n    this.componentInstance = undefined\n    /*当前节点的父节点*/\n    this.parent = undefined\n    /*简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false*/\n    this.raw = false\n    /*静态节点标志*/\n    this.isStatic = false\n    /*是否作为跟节点插入*/\n    this.isRootInsert = true\n    /*是否为注释节点*/\n    this.isComment = false\n    /*是否为克隆节点*/\n    this.isCloned = false\n    /*是否有v-once指令*/\n    this.isOnce = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n```\n\n这是一个最基础的VNode节点，作为其他派生VNode类的基类，里面定义了下面这些数据。\n\ntag: 当前节点的标签名\n\ndata: 当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息\n\nchildren: 当前节点的子节点，是一个数组\n\ntext: 当前节点的文本\n\nelm: 当前虚拟节点对应的真实dom节点\n\nns: 当前节点的名字空间\n\ncontext: 当前节点的编译作用域\n\nfunctionalContext: 函数化组件作用域\n\nkey: 节点的key属性，被当作节点的标志，用以优化\n\ncomponentOptions: 组件的option选项\n\ncomponentInstance: 当前节点对应的组件的实例\n\nparent: 当前节点的父节点\n\nraw: 简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false\n\nisStatic: 是否为静态节点\n\nisRootInsert: 是否作为跟节点插入\n\nisComment: 是否为注释节点\n\nisCloned: 是否为克隆节点\n\nisOnce: 是否有v-once指令\n\n---\n\n打个比方，比如说我现在有这么一个VNode树\n\n```JavaScript\n{\n    tag: 'div'\n    data: {\n        class: 'test'\n    },\n    children: [\n        {\n            tag: 'span',\n            data: {\n                class: 'demo'\n            }\n            text: 'hello,VNode'\n        }\n    ]\n}\n```\n\n渲染之后的结果就是这样的\n\n```html\n<div class=\"test\">\n    <span class=\"demo\">hello,VNode</span>\n</div>\n```\n\n更多操作VNode的方法，请参考[《VNode节点》](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)。\n\n## patch\n\n最后_update会将新旧两个VNode进行一次patch的过程，得出两个VNode最小的差异，然后将这些差异渲染到视图上。\n\n首先说一下patch的核心diff算法，diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式，所以时间复杂度只有O(n)，是一种相当高效的算法。\n\n![img](https://i.loli.net/2017/08/27/59a23cfca50f3.png)\n\n![img](https://i.loli.net/2017/08/27/59a2419a3c617.png)\n\n这两张图代表旧的VNode与新VNode进行patch的过程，他们只是在同层级的VNode之间进行比较得到变化（第二张图中相同颜色的方块代表互相进行比较的VNode节点），然后修改变化的视图，所以十分高效。\n\n在patch的过程中，如果两个VNode被认为是同一个VNode（sameVnode），则会进行深度的比较，得出最小差异，否则直接删除旧有DOM节点，创建新的DOM节点。\n\n什么是sameVnode？\n\n我们来看一下sameVnode的实现。\n\n```JavaScript\n/*\n  判断两个VNode节点是否是同一个节点，需要满足以下条件\n  key相同\n  tag（当前节点的标签名）相同\n  isComment（是否为注释节点）相同\n  是否data（当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息）都有定义\n  当标签是<input>的时候，type必须相同\n*/\nfunction sameVnode (a, b) {\n  return (\n    a.key === b.key &&\n    a.tag === b.tag &&\n    a.isComment === b.isComment &&\n    isDef(a.data) === isDef(b.data) &&\n    sameInputType(a, b)\n  )\n}\n\n// Some browsers do not support dynamically changing type for <input>\n// so they need to be treated as different nodes\n/*\n  判断当标签是<input>的时候，type是否相同\n  某些浏览器不支持动态修改<input>类型，所以他们被视为不同类型\n*/\nfunction sameInputType (a, b) {\n  if (a.tag !== 'input') return true\n  let i\n  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type\n  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type\n  return typeA === typeB\n}\n```\n\n当两个VNode的tag、key、isComment都相同，并且同时定义或未定义data的时候，且如果标签为input则type必须相同。这时候这两个VNode则算sameVnode，可以直接进行patchVnode操作。\n\npatchVnode的规则是这样的：\n\n1.如果新旧VNode都是静态的，同时它们的key相同（代表同一节点），并且新的VNode是clone或者是标记了once（标记v-once属性，只渲染一次），那么只需要替换elm以及componentInstance即可。\n\n2.新老节点均有children子节点，则对子节点进行diff操作，调用updateChildren，这个updateChildren也是diff的核心。\n\n3.如果老节点没有子节点而新节点存在子节点，先清空老节点DOM的文本内容，然后为当前DOM节点加入子节点。\n\n4.当新节点没有子节点而老节点有子节点的时候，则移除该DOM节点的所有子节点。\n\n5.当新老节点都无子节点的时候，只是文本的替换。\n\n## updateChildren\n\n```JavaScript\n  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n    let oldStartIdx = 0\n    let newStartIdx = 0\n    let oldEndIdx = oldCh.length - 1\n    let oldStartVnode = oldCh[0]\n    let oldEndVnode = oldCh[oldEndIdx]\n    let newEndIdx = newCh.length - 1\n    let newStartVnode = newCh[0]\n    let newEndVnode = newCh[newEndIdx]\n    let oldKeyToIdx, idxInOld, elmToMove, refElm\n\n    // removeOnly is a special flag used only by <transition-group>\n    // to ensure removed elements stay in correct relative positions\n    // during leaving transitions\n    const canMove = !removeOnly\n\n    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n      if (isUndef(oldStartVnode)) {\n        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left\n      } else if (isUndef(oldEndVnode)) {\n        oldEndVnode = oldCh[--oldEndIdx]\n      } else if (sameVnode(oldStartVnode, newStartVnode)) {\n        /*前四种情况其实是指定key的时候，判定为同一个VNode，则直接patchVnode即可，分别比较oldCh以及newCh的两头节点2*2=4种情况*/\n        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)\n        oldStartVnode = oldCh[++oldStartIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else if (sameVnode(oldEndVnode, newEndVnode)) {\n        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))\n        oldStartVnode = oldCh[++oldStartIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else {\n        /*\n          生成一个key与旧VNode的key对应的哈希表（只有第一次进来undefined的时候会生成，也为后面检测重复的key值做铺垫）\n          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  \n          结果生成{key0: 0, key1: 1, key2: 2}\n        */\n        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld（即第几个节点，下标）*/\n        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null\n        if (isUndef(idxInOld)) { // New element\n          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/\n          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n          newStartVnode = newCh[++newStartIdx]\n        } else {\n          /*获取同key的老节点*/\n          elmToMove = oldCh[idxInOld]\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !elmToMove) {\n            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的DOM中，提示可能存在重复的key，确保v-for的时候item有唯一的key值*/\n            warn(\n              'It seems there are duplicate keys that is causing an update error. ' +\n              'Make sure each v-for item has a unique key.'\n            )\n          }\n          if (sameVnode(elmToMove, newStartVnode)) {\n            /*Github:https://github.com/answershuto*/\n            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/\n            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)\n            /*因为已经patchVnode进去了，所以将这个老节点赋值undefined，之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/\n            oldCh[idxInOld] = undefined\n            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实DOM节点前面*/\n            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)\n            newStartVnode = newCh[++newStartIdx]\n          } else {\n            // same key but different element. treat as new element\n            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候（比如说tag不一样或者是有不一样type的input标签），创建一个新的节点*/\n            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n            newStartVnode = newCh[++newStartIdx]\n          }\n        }\n      }\n    }\n    if (oldStartIdx > oldEndIdx) {\n      /*全部比较完成以后，发现oldStartIdx > oldEndIdx的话，说明老节点已经遍历完了，新节点比老节点多，所以这时候多出来的新节点需要一个一个创建出来加入到真实DOM中*/\n      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm\n      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)\n    } else if (newStartIdx > newEndIdx) {\n      /*如果全部比较完成以后发现newStartIdx > newEndIdx，则说明新节点已经遍历完了，老节点多余新节点，这个时候需要将多余的老节点从真实DOM中移除*/\n      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)\n    }\n  }\n```\n\n直接看源码可能比较难以捋清其中的关系，我们通过图来看一下。\n\n![img](https://i.loli.net/2017/08/28/59a4015bb2765.png)\n\n首先，在新老两个VNode节点的左右头尾两侧都有一个变量标记，在遍历过程中这几个变量都会向中间靠拢。当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环。\n\n索引与VNode节点的对应关系：\noldStartIdx => oldStartVnode\noldEndIdx => oldEndVnode\nnewStartIdx => newStartVnode\nnewEndIdx => newEndVnode\n\n在遍历中，如果存在key，并且满足sameVnode，会将该DOM节点进行复用，否则则会创建一个新的DOM节点。\n\n首先，oldStartVnode、oldEndVnode与newStartVnode、newEndVnode两两比较一共有2*2=4种比较方法。\n\n当新老VNode节点的start或者end满足sameVnode时，也就是sameVnode(oldStartVnode, newStartVnode)或者sameVnode(oldEndVnode, newEndVnode)，直接将该VNode节点进行patchVnode即可。\n\n![img](https://i.loli.net/2017/08/28/59a40c12c1655.png)\n\n如果oldStartVnode与newEndVnode满足sameVnode，即sameVnode(oldStartVnode, newEndVnode)。\n\n这时候说明oldStartVnode已经跑到了oldEndVnode后面去了，进行patchVnode的同时还需要将真实DOM节点移动到oldEndVnode的后面。\n\n![img](https://ooo.0o0.ooo/2017/08/28/59a4214784979.png)\n\n如果oldEndVnode与newStartVnode满足sameVnode，即sameVnode(oldEndVnode, newStartVnode)。\n\n这说明oldEndVnode跑到了oldStartVnode的前面，进行patchVnode的同时真实的DOM节点移动到了oldStartVnode的前面。\n\n![img](https://i.loli.net/2017/08/29/59a4c70685d12.png)\n\n如果以上情况均不符合，则通过createKeyToOldIdx会得到一个oldKeyToIdx，里面存放了一个key为旧的VNode，value为对应index序列的哈希表。从这个哈希表中可以找到是否有与newStartVnode一致key的旧的VNode节点，如果同时满足sameVnode，patchVnode的同时会将这个真实DOM（elmToMove）移动到oldStartVnode对应的真实DOM的前面。\n\n![img](https://i.loli.net/2017/08/29/59a4d7552d299.png)\n\n当然也有可能newStartVnode在旧的VNode节点找不到一致的key，或者是即便key相同却不是sameVnode，这个时候会调用createElm创建一个新的DOM节点。\n\n![img](https://i.loli.net/2017/08/29/59a4de0fa4dba.png)\n\n到这里循环已经结束了，那么剩下我们还需要处理多余或者不够的真实DOM节点。\n\n1.当结束时oldStartIdx > oldEndIdx，这个时候老的VNode节点已经遍历完了，但是新的节点还没有。说明了新的VNode节点实际上比老的VNode节点多，也就是比真实DOM多，需要将剩下的（也就是新增的）VNode节点插入到真实DOM节点中去，此时调用addVnodes（批量调用createElm的接口将这些节点加入到真实DOM中去）。\n\n![img](https://i.loli.net/2017/08/29/59a509f0d1788.png)\n\n2。同理，当newStartIdx > newEndIdx时，新的VNode节点已经遍历完了，但是老的节点还有剩余，说明真实DOM节点多余了，需要从文档中删除，这时候调用removeVnodes将这些多余的真实DOM删除。\n\n![img](https://i.loli.net/2017/08/29/59a4f389b98cb.png)\n\n更详细的diff实现参考笔者的文章[VirtualDOM与diff(Vue.js实现)](https://github.com/answershuto/learnVue/blob/master/docs/VirtualDOM%E4%B8%8Ediff(Vue%E5%AE%9E%E7%8E%B0).MarkDown)。\n\n## 映射到真实DOM\n\n由于Vue使用了虚拟DOM，所以虚拟DOM可以在任何支持JavaScript语言的平台上操作，譬如说目前Vue支持的浏览器平台或是weex，在虚拟DOM的实现上是一致的。那么最后虚拟DOM如何映射到真实的DOM节点上呢？\n\nVue为平台做了一层适配层，浏览器平台见[/platforms/web/runtime/node-ops.js](https://github.com/answershuto/learnVue/blob/master/vue-src/platforms/web/runtime/node-ops.js)以及weex平台见[/platforms/weex/runtime/node-ops.js](https://github.com/answershuto/learnVue/blob/master/vue-src/platforms/weex/runtime/node-ops.js)。不同平台之间通过适配层对外提供相同的接口，虚拟DOM进行操作真实DOM节点的时候，只需要调用这些适配层的接口即可，而内部实现则不需要关心，它会根据平台的改变而改变。\n\n现在又出现了一个问题，我们只是将虚拟DOM映射成了真实的DOM。那如何给这些DOM加入attr、class、style等DOM属性呢？\n\n这要依赖于虚拟DOM的生命钩子。虚拟DOM提供了如下的钩子函数，分别在不同的时期会进行调用。\n\n```JavaScript\nconst hooks = ['create', 'activate', 'update', 'remove', 'destroy']\n\n/*构建cbs回调函数，web平台上见/platforms/web/runtime/modules*/\n  for (i = 0; i < hooks.length; ++i) {\n    cbs[hooks[i]] = []\n    for (j = 0; j < modules.length; ++j) {\n      if (isDef(modules[j][hooks[i]])) {\n        cbs[hooks[i]].push(modules[j][hooks[i]])\n      }\n    }\n  }\n```\n\n同理，也会根据不同平台有自己不同的实现，我们这里以Web平台为例。Web平台的钩子函数见[/platforms/web/runtime/modules](https://github.com/answershuto/learnVue/tree/master/vue-src/platforms/web/runtime/modules)。里面有对attr、class、props、events、style以及transition（过渡状态）的DOM属性进行操作。\n\n以attr为例，代码很简单。\n\n```JavaScript\n/* @flow */\n\nimport { isIE9 } from 'core/util/env'\n\nimport {\n  extend,\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  isXlink,\n  xlinkNS,\n  getXlinkProp,\n  isBooleanAttr,\n  isEnumeratedAttr,\n  isFalsyAttrValue\n} from 'web/util/index'\n\n/*更新attr*/\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  /*如果旧的以及新的VNode节点均没有attr属性，则直接返回*/\n  if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {\n    return\n  }\n  let key, cur, old\n  /*VNode节点对应的Dom实例*/\n  const elm = vnode.elm\n  /*旧VNode节点的attr*/\n  const oldAttrs = oldVnode.data.attrs || {}\n  /*新VNode节点的attr*/\n  let attrs: any = vnode.data.attrs || {}\n  // clone observed objects, as the user probably wants to mutate it\n  /*如果新的VNode的attr已经有__ob__（代表已经被Observe处理过了）， 进行深拷贝*/\n  if (isDef(attrs.__ob__)) {\n    attrs = vnode.data.attrs = extend({}, attrs)\n  }\n\n  /*遍历attr，不一致则替换*/\n  for (key in attrs) {\n    cur = attrs[key]\n    old = oldAttrs[key]\n    if (old !== cur) {\n      setAttr(elm, key, cur)\n    }\n  }\n  // #4391: in IE9, setting type can reset value for input[type=radio]\n  /* istanbul ignore if */\n  if (isIE9 && attrs.value !== oldAttrs.value) {\n    setAttr(elm, 'value', attrs.value)\n  }\n  for (key in oldAttrs) {\n    if (isUndef(attrs[key])) {\n      if (isXlink(key)) {\n        elm.removeAttributeNS(xlinkNS, getXlinkProp(key))\n      } else if (!isEnumeratedAttr(key)) {\n        elm.removeAttribute(key)\n      }\n    }\n  }\n}\n\n/*设置attr*/\nfunction setAttr (el: Element, key: string, value: any) {\n  if (isBooleanAttr(key)) {\n    // set attribute for blank value\n    // e.g. <option disabled>Select one</option>\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      el.setAttribute(key, key)\n    }\n  } else if (isEnumeratedAttr(key)) {\n    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')\n  } else if (isXlink(key)) {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttributeNS(xlinkNS, getXlinkProp(key))\n    } else {\n      el.setAttributeNS(xlinkNS, key, value)\n    }\n  } else {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      el.setAttribute(key, value)\n    }\n  }\n}\n\nexport default {\n  create: updateAttrs,\n  update: updateAttrs\n}\n\n```\n\nattr只需要在create以及update钩子被调用时更新DOM的attr属性即可。\n\n## 最后\n\n至此，我们已经从template到真实DOM的整个过程梳理完了。现在再去看这张图，是不是更清晰了呢？\n\n![](https://cn.vuejs.org/images/data.png)\n"
  },
  {
    "path": "docs/从源码角度再看数据绑定.MarkDown",
    "content": "## 数据绑定原理\n\n前面已经讲过Vue数据绑定的原理了，现在从源码来看一下数据绑定在Vue中是如何实现的。\n\n首先看一下Vue.js官网介绍响应式原理的这张图。\n\n![](https://cn.vuejs.org/images/data.png)\n\n这张图比较清晰地展示了整个流程，首先通过一次渲染操作触发Data的getter（这里保证只有视图中需要被用到的data才会触发getter）进行依赖收集，这时候其实Watcher与data可以看成一种被绑定的状态（实际上是data的闭包中有一个Deps订阅者，在修改的时候会通知所有的Watcher观察者），在data发生变化的时候会触发它的setter，setter通知Watcher，Watcher进行回调通知组件重新渲染的函数，之后根据diff算法来决定是否发生视图的更新。\n\nVue在初始化组件数据时，在生命周期的[beforeCreate](https://github.com/vuejs/vue/blob/dev/src/core/instance/init.js#L55)与[created](https://github.com/vuejs/vue/blob/dev/src/core/instance/init.js#L59)钩子函数之间实现了对[data、props、computed、methods、events以及watch](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L43)的处理。\n\n## initData\n\n这里来讲一下[initData](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L107)，可以参考源码instance下的state.js文件，下面所有的中文注释都是我加的，英文注释是尤大加的，请不要忽略英文注释，英文注释都讲到了比较关键或者晦涩难懂的点。\n\n加注释版的vue源码也可以直接通过[传送门](https://github.com/answershuto/learnVue/tree/master/vue-src)查看，这些是我在阅读Vue源码过程中加的注释，持续更新中。\n\ninitData主要是初始化data中的数据，将数据进行Observer，监听数据的变化，其他的监视原理一致，这里以data为例。\n\n```javascript\nfunction initData (vm: Component) {\n\n  /*得到data数据*/\n  let data = vm.$options.data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n\n  /*判断是否是对象*/\n  if (!isPlainObject(data)) {\n    data = {}\n    process.env.NODE_ENV !== 'production' && warn(\n      'data functions should return an object:\\n' +\n      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',\n      vm\n    )\n  }\n\n  // proxy data on instance\n  /*遍历data对象*/\n  const keys = Object.keys(data)\n  const props = vm.$options.props\n  let i = keys.length\n\n  //遍历data中的数据\n  while (i--) {\n    /*保证data中的key不与props中的key重复，props优先，如果有冲突会产生warning*/\n    if (props && hasOwn(props, keys[i])) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `The data property \"${keys[i]}\" is already declared as a prop. ` +\n        `Use prop default value instead.`,\n        vm\n      )\n    } else if (!isReserved(keys[i])) {\n      /*判断是否是保留字段*/\n\n      /*这里是我们前面讲过的代理，将data上面的属性代理到了vm实例上*/\n      proxy(vm, `_data`, keys[i])\n    }\n  }\n  /*Github:https://github.com/answershuto*/\n  // observe data\n  /*从这里开始我们要observe了，开始对数据进行绑定，这里有尤大大的注释asRootData，这步作为根数据，下面会进行递归observe进行对深层对象的绑定。*/\n  observe(data, true /* asRootData */)\n}\n```\n\n其实这段代码主要做了两件事，一是将_data上面的数据代理到vm上，另一件事通过observe将所有数据变成observable。\n\n## proxy\n\n接下来看一下proxy代理。\n\n```javascript\n/*添加代理*/\nexport function proxy (target: Object, sourceKey: string, key: string) {\n  sharedPropertyDefinition.get = function proxyGetter () {\n    return this[sourceKey][key]\n  }\n  sharedPropertyDefinition.set = function proxySetter (val) {\n    this[sourceKey][key] = val\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n```\n\n这里比较好理解，通过proxy函数将data上面的数据代理到vm上，这样就可以用app.text代替app._data.text了。\n\n## observe\n\n接下来是[observe](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L106)，这个函数定义在core文件下observer的index.js文件中。\n\n```javascript\n/**\n * Attempt to create an observer instance for a value,\n * returns the new observer if successfully observed,\n * or the existing observer if the value already has one.\n */\n /*\n 尝试创建一个Observer实例（__ob__），如果成功创建Observer实例则返回新的Observer实例，如果已有Observer实例则返回现有的Observer实例。\n */\nexport function observe (value: any, asRootData: ?boolean): Observer | void {\n  /*判断是否是一个对象*/\n  if (!isObject(value)) {\n    return\n  }\n  let ob: Observer | void\n\n  /*这里用__ob__这个属性来判断是否已经有Observer实例，如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性，如果已有Observer实例则直接返回该Observer实例*/\n  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {\n    ob = value.__ob__\n  } else if (\n\n    /*这里的判断是为了确保value是单纯的对象，而不是函数或者是Regexp等情况。*/\n    observerState.shouldConvert &&\n    !isServerRendering() &&\n    (Array.isArray(value) || isPlainObject(value)) &&\n    Object.isExtensible(value) &&\n    !value._isVue\n  ) {\n    ob = new Observer(value)\n  }\n  if (asRootData && ob) {\n\n    /*如果是根数据则计数，后面Observer中的observe的asRootData非true*/\n    ob.vmCount++\n  }\n  return ob\n}\n\n```\n\nVue的响应式数据都会有一个__ob__的属性作为标记，里面存放了该属性的观察器，也就是Observer的实例，防止重复绑定。\n\n## Observer\n\n接下来看一下新建的[Observer](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L34)。Observer的作用就是遍历对象的所有属性将其进行双向绑定。\n\n```javascript\n/**\n * Observer class that are attached to each observed\n * object. Once attached, the observer converts target\n * object's property keys into getter/setters that\n * collect dependencies and dispatches updates.\n */\nexport class  {\n  value: any;\n  dep: Dep;\n  vmCount: number; // number of vms that has this object as root $data\n\n  constructor (value: any) {\n    this.value = value\n    this.dep = new Dep()\n    this.vmCount = 0\n\n    /*\n    将Observer实例绑定到data的__ob__属性上面去，之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了，def方法定义可以参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16\n    */\n    def(value, '__ob__', this)\n    if (Array.isArray(value)) {\n\n      /*\n          如果是数组，将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法，达到监听数组数据变化响应的效果。\n          这里如果当前浏览器支持__proto__属性，则直接覆盖当前数组对象原型上的原生数组方法，如果不支持该属性，则直接覆盖数组对象的原型。\n      */\n      const augment = hasProto\n        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/\n        : copyAugment   /*定义（覆盖）目标对象或数组的某一个方法*/\n      augment(value, arrayMethods, arrayKeys)\n      /*Github:https://github.com/answershuto*/\n      /*如果是数组则需要遍历数组的每一个成员进行observe*/\n      this.observeArray(value)\n    } else {\n\n      /*如果是对象则直接walk进行绑定*/\n      this.walk(value)\n    }\n  }\n\n  /**\n   * Walk through each property and convert them into\n   * getter/setters. This method should only be called when\n   * value type is Object.\n   */\n  walk (obj: Object) {\n    const keys = Object.keys(obj)\n\n    /*walk方法会遍历对象的每一个属性进行defineReactive绑定*/\n    for (let i = 0; i < keys.length; i++) {\n      defineReactive(obj, keys[i], obj[keys[i]])\n    }\n  }\n\n  /**\n   * Observe a list of Array items.\n   */\n  observeArray (items: Array<any>) {\n\n    /*数组需要遍历每一个成员进行observe*/\n    for (let i = 0, l = items.length; i < l; i++) {\n      observe(items[i])\n    }\n  }\n}\n```\n\nObserver为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历，为每一个子对象都绑定上方法，如果是数组则为每一个成员都绑定上方法。\n\n如果是修改一个数组的成员，该成员是一个对象，那只需要递归对数组的成员进行双向绑定即可。但这时候出现了一个问题：如果我们进行pop、push等操作的时候，push进去的对象根本没有进行过双向绑定，更别说pop了，那么我们如何监听数组的这些变化呢？\nVue.js提供的方法是重写push、pop、shift、unshift、splice、sort、reverse这七个[数组方法](http://v1-cn.vuejs.org/guide/list.html#变异方法)。修改数组原型方法的代码可以参考[observer/array.js](https://github.com/vuejs/vue/blob/dev/src/core/observer/array.js)以及[observer/index.js](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L45)。\n\n```javascript\nexport class Observer {\n  value: any;\n  dep: Dep;\n  vmCount: number; // number of vms that has this object as root $data\n\n  constructor (value: any) {\n    //.......\n\n    if (Array.isArray(value)) {\n      /*\n          如果是数组，将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法，达到监听数组数据变化响应的效果。\n          这里如果当前浏览器支持__proto__属性，则直接覆盖当前数组对象原型上的原生数组方法，如果不支持该属性，则直接覆盖数组对象的原型。\n      */\n      const augment = hasProto\n        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/\n        : copyAugment   /*定义（覆盖）目标对象或数组的某一个方法*/\n      augment(value, arrayMethods, arrayKeys)\n\n      /*如果是数组则需要遍历数组的每一个成员进行observe*/\n      this.observeArray(value)\n    } else {\n      /*如果是对象则直接walk进行绑定*/\n      this.walk(value)\n    }\n  }\n}\n\n/**\n * Augment an target Object or Array by intercepting\n * the prototype chain using __proto__\n */\n /*直接覆盖原型的方法来修改目标对象或数组*/\nfunction protoAugment (target, src: Object) {\n  /* eslint-disable no-proto */\n  target.__proto__ = src\n  /* eslint-enable no-proto */\n}\n\n/**\n * Augment an target Object or Array by defining\n * hidden properties.\n */\n/* istanbul ignore next */\n/*定义（覆盖）目标对象或数组的某一个方法*/\nfunction copyAugment (target: Object, src: Object, keys: Array<string>) {\n  for (let i = 0, l = keys.length; i < l; i++) {\n    const key = keys[i]\n    def(target, key, src[key])\n  }\n}\n```\n\n```javascript\n/*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototype\n */\n\nimport { def } from '../util/index'\n\n/*取得原生数组的原型*/\nconst arrayProto = Array.prototype\n/*创建一个新的数组对象，修改该对象上的数组的七个方法，防止污染原生数组方法*/\nexport const arrayMethods = Object.create(arrayProto)\n\n/**\n * Intercept mutating methods and emit events\n */\n /*这里重写了数组的这些方法，在保证不污染原生数组原型的情况下重写数组的这些方法，截获数组的成员发生的变化，执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/\n[\n  'push',\n  'pop',\n  'shift',\n  'unshift',\n  'splice',\n  'sort',\n  'reverse'\n]\n.forEach(function (method) {\n  // cache original method\n  /*将数组的原生方法缓存起来，后面要调用*/\n  const original = arrayProto[method]\n  def(arrayMethods, method, function mutator () {\n    // avoid leaking arguments:\n    // http://jsperf.com/closure-with-arguments\n    let i = arguments.length\n    const args = new Array(i)\n    while (i--) {\n      args[i] = arguments[i]\n    }\n    /*调用原生的数组方法*/\n    const result = original.apply(this, args)\n\n    /*数组新插入的元素需要重新进行observe才能响应式*/\n    const ob = this.__ob__\n    let inserted\n    switch (method) {\n      case 'push':\n        inserted = args\n        break\n      case 'unshift':\n        inserted = args\n        break\n      case 'splice':\n        inserted = args.slice(2)\n        break\n    }\n    if (inserted) ob.observeArray(inserted)\n\n    // notify change\n    /*dep通知所有注册的观察者进行响应式处理*/\n    ob.dep.notify()\n    return result\n  })\n})\n\n```\n\n从数组的原型新建一个Object.create(arrayProto)对象，通过修改此原型可以保证原生数组方法不被污染。如果当前浏览器支持__proto__这个属性的话就可以直接覆盖该属性则使数组对象具有了重写后的数组方法。如果没有该属性的浏览器，则必须通过遍历def所有需要重写的数组方法，这种方法效率较低，所以优先使用第一种。\n\n在保证不污染不覆盖数组原生方法添加监听，主要做了两个操作，第一是通知所有注册的观察者进行响应式处理，第二是如果是添加成员的操作，需要对新成员进行observe。\n\n但是修改了数组的原生方法以后我们还是没法像原生数组一样直接通过数组的下标或者设置length来修改数组，可以通过[Vue.set以及splice方法](https://cn.vuejs.org/v2/guide/list.html#%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84)。\n\n## Watcher\n\n[Watcher](https://github.com/vuejs/vue/blob/dev/src/core/observer/watcher.js#L24)是一个观察者对象。依赖收集以后Watcher对象会被保存在Deps中，数据变动的时候会由Deps通知Watcher实例，然后由Watcher实例回调cb进行视图的更新。\n\n```javascript\nexport default class Watcher {\n  vm: Component;\n  expression: string;\n  cb: Function;\n  id: number;\n  deep: boolean;\n  user: boolean;\n  lazy: boolean;\n  sync: boolean;\n  dirty: boolean;\n  active: boolean;\n  deps: Array<Dep>;\n  newDeps: Array<Dep>;\n  depIds: ISet;\n  newDepIds: ISet;\n  getter: Function;\n  value: any;\n\n  constructor (\n    vm: Component,\n    expOrFn: string | Function,\n    cb: Function,\n    options?: Object\n  ) {\n    this.vm = vm\n    /*_watchers存放订阅者实例*/\n    vm._watchers.push(this)\n    // options\n    if (options) {\n      this.deep = !!options.deep\n      this.user = !!options.user\n      this.lazy = !!options.lazy\n      this.sync = !!options.sync\n    } else {\n      this.deep = this.user = this.lazy = this.sync = false\n    }\n    this.cb = cb\n    this.id = ++uid // uid for batching\n    this.active = true\n    this.dirty = this.lazy // for lazy watchers\n    this.deps = []\n    this.newDeps = []\n    this.depIds = new Set()\n    this.newDepIds = new Set()\n    this.expression = process.env.NODE_ENV !== 'production'\n      ? expOrFn.toString()\n      : ''\n    // parse expression for getter\n    /*把表达式expOrFn解析成getter*/\n    if (typeof expOrFn === 'function') {\n      this.getter = expOrFn\n    } else {\n      this.getter = parsePath(expOrFn)\n      if (!this.getter) {\n        this.getter = function () {}\n        process.env.NODE_ENV !== 'production' && warn(\n          `Failed watching path: \"${expOrFn}\" ` +\n          'Watcher only accepts simple dot-delimited paths. ' +\n          'For full control, use a function instead.',\n          vm\n        )\n      }\n    }\n    this.value = this.lazy\n      ? undefined\n      : this.get()\n  }\n\n  /**\n   * Evaluate the getter, and re-collect dependencies.\n   */\n   /*获得getter的值并且重新进行依赖收集*/\n  get () {\n    /*将自身watcher观察者实例设置给Dep.target，用以依赖收集。*/\n    pushTarget(this)\n    let value\n    const vm = this.vm\n\n    /*\n      执行了getter操作，看似执行了渲染操作，其实是执行了依赖收集。\n      在将Dep.target设置为自身观察者实例以后，执行getter操作。\n      譬如说现在的的data中可能有a、b、c三个数据，getter渲染需要依赖a跟c，\n      那么在执行getter的时候就会触发a跟c两个数据的getter函数，\n      在getter函数中即可判断Dep.target是否存在然后完成依赖收集，\n      将该观察者对象放入闭包中的Dep的subs中去。\n    */\n    if (this.user) {\n      try {\n        value = this.getter.call(vm, vm)\n      } catch (e) {\n        handleError(e, vm, `getter for watcher \"${this.expression}\"`)\n      }\n    } else {\n      value = this.getter.call(vm, vm)\n    }\n    // \"touch\" every property so they are all tracked as\n    // dependencies for deep watching\n    /*如果存在deep，则触发每个深层对象的依赖，追踪其变化*/\n    if (this.deep) {\n      /*递归每一个对象或者数组，触发它们的getter，使得对象或数组的每一个成员都被依赖收集，形成一个“深（deep）”依赖关系*/\n      traverse(value)\n    }\n\n    /*将观察者实例从target栈中取出并设置给Dep.target*/\n    popTarget()\n    this.cleanupDeps()\n    return value\n  }\n\n  /**\n   * Add a dependency to this directive.\n   */\n   /*添加一个依赖关系到Deps集合中*/\n  addDep (dep: Dep) {\n    const id = dep.id\n    if (!this.newDepIds.has(id)) {\n      this.newDepIds.add(id)\n      this.newDeps.push(dep)\n      if (!this.depIds.has(id)) {\n        dep.addSub(this)\n      }\n    }\n  }\n\n  /**\n   * Clean up for dependency collection.\n   */\n   /*清理依赖收集*/\n  cleanupDeps () {\n    /*移除所有观察者对象*/\n    let i = this.deps.length\n    while (i--) {\n      const dep = this.deps[i]\n      if (!this.newDepIds.has(dep.id)) {\n        dep.removeSub(this)\n      }\n    }\n    let tmp = this.depIds\n    this.depIds = this.newDepIds\n    this.newDepIds = tmp\n    this.newDepIds.clear()\n    tmp = this.deps\n    this.deps = this.newDeps\n    this.newDeps = tmp\n    this.newDeps.length = 0\n  }\n\n  /**\n   * Subscriber interface.\n   * Will be called when a dependency changes.\n   */\n   /*\n      调度者接口，当依赖发生改变的时候进行回调。\n   */\n  update () {\n    /* istanbul ignore else */\n    if (this.lazy) {\n      this.dirty = true\n    } else if (this.sync) {\n      /*同步则执行run直接渲染视图*/\n      this.run()\n    } else {\n      /*异步推送到观察者队列中，由调度者调用。*/\n      queueWatcher(this)\n    }\n  }\n\n  /**\n   * Scheduler job interface.\n   * Will be called by the scheduler.\n   */\n   /*\n      调度者工作接口，将被调度者回调。\n    */\n  run () {\n    if (this.active) {\n      const value = this.get()\n      if (\n        value !== this.value ||\n        // Deep watchers and watchers on Object/Arrays should fire even\n        // when the value is the same, because the value may\n        // have mutated.\n        /*\n            即便值相同，拥有Deep属性的观察者以及在对象／数组上的观察者应该被触发更新，因为它们的值可能发生改变。\n        */\n        isObject(value) ||\n        this.deep\n      ) {\n        // set new value\n        const oldValue = this.value\n        /*设置新的值*/\n        this.value = value\n\n        /*触发回调渲染视图*/\n        if (this.user) {\n          try {\n            this.cb.call(this.vm, value, oldValue)\n          } catch (e) {\n            handleError(e, this.vm, `callback for watcher \"${this.expression}\"`)\n          }\n        } else {\n          this.cb.call(this.vm, value, oldValue)\n        }\n      }\n    }\n  }\n\n  /**\n   * Evaluate the value of the watcher.\n   * This only gets called for lazy watchers.\n   */\n   /*获取观察者的值*/\n  evaluate () {\n    this.value = this.get()\n    this.dirty = false\n  }\n\n  /**\n   * Depend on all deps collected by this watcher.\n   */\n   /*收集该watcher的所有deps依赖*/\n  depend () {\n    let i = this.deps.length\n    while (i--) {\n      this.deps[i].depend()\n    }\n  }\n\n  /**\n   * Remove self from all dependencies' subscriber list.\n   */\n   /*将自身从所有依赖收集订阅列表删除*/\n  teardown () {\n    if (this.active) {\n      // remove self from vm's watcher list\n      // this is a somewhat expensive operation so we skip it\n      // if the vm is being destroyed.\n      /*从vm实例的观察者列表中将自身移除，由于该操作比较耗费资源，所以如果vm实例正在被销毁则跳过该步骤。*/\n      if (!this.vm._isBeingDestroyed) {\n        remove(this.vm._watchers, this)\n      }\n      let i = this.deps.length\n      while (i--) {\n        this.deps[i].removeSub(this)\n      }\n      this.active = false\n    }\n  }\n}\n```\n\n## Dep\n\n来看看[Dep](https://github.com/vuejs/vue/blob/dev/src/core/observer/dep.js#L12)类。其实Dep就是一个发布者，可以订阅多个观察者，依赖收集之后Deps中会存在一个或多个Watcher对象，在数据变更的时候通知所有的Watcher。\n\n```javascript\n/**\n * A dep is an observable that can have multiple\n * directives subscribing to it.\n */\nexport default class Dep {\n  static target: ?Watcher;\n  id: number;\n  subs: Array<Watcher>;\n\n  constructor () {\n    this.id = uid++\n    this.subs = []\n  }\n\n  /*添加一个观察者对象*/\n  addSub (sub: Watcher) {\n    this.subs.push(sub)\n  }\n\n  /*移除一个观察者对象*/\n  removeSub (sub: Watcher) {\n    remove(this.subs, sub)\n  }\n\n  /*依赖收集，当存在Dep.target的时候添加观察者对象*/\n  depend () {\n    if (Dep.target) {\n      Dep.target.addDep(this)\n    }\n  }\n\n  /*通知所有订阅者*/\n  notify () {\n    // stabilize the subscriber list first\n    const subs = this.subs.slice()\n    for (let i = 0, l = subs.length; i < l; i++) {\n      subs[i].update()\n    }\n  }\n}\n\n// the current target watcher being evaluated.\n// this is globally unique because there could be only one\n// watcher being evaluated at any time.\nDep.target = null\n/*依赖收集完需要将Dep.target设为null，防止后面重复添加依赖。*/\n```\n\n## defineReactive\n\n接下来是[defineReactive](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L131)。defineReactive的作用是通过Object.defineProperty为数据定义上getter\\setter方法，进行依赖收集后闭包中的Deps会存放Watcher对象。触发setter改变数据的时候会通知Deps订阅者通知所有的Watcher观察者对象进行试图的更新。\n\n```javascript\n/**\n * Define a reactive property on an Object.\n */\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: Function\n) {\n  /*在闭包中定义一个dep对象*/\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  /*如果之前该对象已经预设了getter以及setter函数则将其取出来，新定义的getter/setter中会将其执行，保证不会覆盖之前已经定义的getter/setter。*/\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n\n  /*对象的子对象递归进行observe并返回子节点的Observer对象*/\n  let childOb = observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n\n      /*如果原本对象拥有getter方法则执行*/\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n\n        /*进行依赖收集*/\n        dep.depend()\n        if (childOb) {\n\n          /*子对象进行依赖收集，其实就是将同一个watcher观察者实例放进了两个depend中，一个是正在本身闭包中的depend，另一个是子元素的depend*/\n          childOb.dep.depend()\n        }\n        if (Array.isArray(value)) {\n\n          /*是数组则需要对每一个成员都进行依赖收集，如果数组的成员还是数组，则递归。*/\n          dependArray(value)\n        }\n      }\n      return value\n    },\n    set: function reactiveSetter (newVal) {\n\n      /*通过getter方法获取当前值，与新值进行比较，一致则不需要执行下面的操作*/\n      const value = getter ? getter.call(obj) : val\n      /* eslint-disable no-self-compare */\n      if (newVal === value || (newVal !== newVal && value !== value)) {\n        return\n      }\n      /* eslint-enable no-self-compare */\n      if (process.env.NODE_ENV !== 'production' && customSetter) {\n        customSetter()\n      }\n      if (setter) {\n\n        /*如果原本对象拥有setter方法则执行setter*/\n        setter.call(obj, newVal)\n      } else {\n        val = newVal\n      }\n\n      /*新的值需要重新进行observe，保证数据响应式*/\n      childOb = observe(newVal)\n\n      /*dep对象通知所有的观察者*/\n      dep.notify()\n    }\n  })\n}\n```\n\n现在再来看这张图是不是更清晰了呢？\n\n![](https://cn.vuejs.org/images/data.png)\n"
  },
  {
    "path": "docs/依赖收集.MarkDown",
    "content": "## 为什么要依赖收集\n\n先看下面这段代码\n\n```javascript\nnew Vue({\n    template: \n        `<div>\n            <span>text1:</span> {{text1}}\n            <span>text2:</span> {{text2}}\n        <div>`,\n    data: {\n        text1: 'text1',\n        text2: 'text2',\n        text3: 'text3'\n    }\n});\n```\n\n按照之前[《响应式原理》](https://github.com/answershuto/learnVue/blob/master/docs/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86.MarkDown)中的方法进行绑定则会出现一个问题——text3在实际模板中并没有被用到，然而当text3的数据被修改（this.text3 = 'test'）的时候，同样会触发text3的setter导致重新执行渲染，这显然不正确。\n\n## 先说说Dep\n\n当对data上的对象进行修改值的时候会触发它的setter，那么取值的时候自然就会触发getter事件，所以我们只要在最开始进行一次render，那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。\n\n定义一个依赖收集类Dep。\n\n```javascript\nclass Dep {\n    constructor () {\n        this.subs = [];\n    }\n\n    addSub (sub: Watcher) {\n        this.subs.push(sub)\n    }\n\n    removeSub (sub: Watcher) {\n        remove(this.subs, sub)\n    }\n    /*Github:https://github.com/answershuto*/\n    notify () {\n        // stabilize the subscriber list first\n        const subs = this.subs.slice()\n        for (let i = 0, l = subs.length; i < l; i++) {\n            subs[i].update()\n        }\n    }\n}\nfunction remove (arr, item) {\n    if (arr.length) {\n        const index = arr.indexOf(item)\n        if (index > -1) {\n            return arr.splice(index, 1)\n        }\n    }\n}\n```\n\n## Watcher\n\n订阅者，当依赖收集的时候会addSub到sub中，在修改data中数据的时候会触发dep对象的notify，通知所有Watcher对象去修改对应视图。\n\n```javascript\nclass Watcher {\n    constructor (vm, expOrFn, cb, options) {\n        this.cb = cb;\n        this.vm = vm;\n\n        /*在这里将观察者本身赋值给全局的target，只有被target标记过的才会进行依赖收集*/\n        Dep.target = this;\n        /*Github:https://github.com/answershuto*/\n        /*触发渲染操作进行依赖收集*/\n        this.cb.call(this.vm);\n    }\n\n    update () {\n        this.cb.call(this.vm);\n    }\n}\n```\n\n## 开始依赖收集\n\n```javascript\nclass Vue {\n    constructor(options) {\n        this._data = options.data;\n        observer(this._data, options.render);\n        let watcher = new Watcher(this, );\n    }\n}\n\nfunction defineReactive (obj, key, val, cb) {\n    /*在闭包内存储一个Dep对象*/\n    const dep = new Dep();\n\n    Object.defineProperty(obj, key, {\n        enumerable: true,\n        configurable: true,\n        get: ()=>{\n            if (Dep.target) {\n                /*Watcher对象存在全局的Dep.target中*/\n                dep.addSub(Dep.target);\n            }\n        },\n        set:newVal=> {\n            /*只有之前addSub中的函数才会触发*/\n            dep.notify();\n        }\n    })\n}\n\nDep.target = null;\n```\n\n将观察者Watcher实例赋值给全局的Dep.target，然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中，在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。\n"
  },
  {
    "path": "docs/响应式原理.MarkDown",
    "content": "## 关于Vue.js\n\nVue.js是一款MVVM框架，上手快速简单易用，通过响应式在修改数据的时候更新视图。Vue.js的响应式原理依赖于[Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)，尤大大在[Vue.js文档](https://cn.vuejs.org/v2/guide/reactivity.html#如何追踪变化)中就已经提到过，这也是Vue.js不支持IE8 以及更低版本浏览器的原因。Vue通过设定对象属性的 setter/getter 方法来监听数据的变化，通过getter进行依赖收集，而每个setter方法就是一个观察者，在数据变更的时候通知订阅者更新视图。\n\n\n## 将数据data变成可观察（observable）的\n\n那么Vue是如何将所有data下面的所有属性变成可观察的（observable）呢？\n\n```javascript\nfunction observe(value, cb) {\n    Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))\n}\n\nfunction defineReactive (obj, key, val, cb) {\n    Object.defineProperty(obj, key, {\n        enumerable: true,\n        configurable: true,\n        get: ()=>{\n            /*....依赖收集等....*/\n            /*Github:https://github.com/answershuto*/\n            return val\n        },\n        set:newVal=> {\n            val = newVal;\n            cb();/*订阅者收到消息的回调*/\n        }\n    })\n}\n\nclass Vue {\n    constructor(options) {\n        this._data = options.data;\n        observe(this._data, options.render)\n    }\n}\n\nlet app = new Vue({\n    el: '#app',\n    data: {\n        text: 'text',\n        text2: 'text2'\n    },\n    render(){\n        console.log(\"render\");\n    }\n})\n```\n\n为了便于理解，首先考虑一种最简单的情况，不考虑数组等情况，代码如上所示。在[initData](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L107)中会调用[observe](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L106)这个函数将Vue的数据设置成observable的。当_data数据发生改变的时候就会触发set，对订阅者进行回调（在这里是render）。\n\n那么问题来了，需要对app._data.text操作才会触发set。为了偷懒，我们需要一种方便的方法通过app.text直接设置就能触发set对视图进行重绘。那么就需要用到代理。\n\n## 代理\n\n我们可以在Vue的构造函数constructor中为data执行一个代理[proxy](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L33)。这样我们就把data上面的属性代理到了vm实例上。\n\n```javascript\n_proxy.call(this, options.data);/*构造函数中*/\n\n/*代理*/\nfunction _proxy (data) {\n    const that = this;\n    Object.keys(data).forEach(key => {\n        Object.defineProperty(that, key, {\n            configurable: true,\n            enumerable: true,\n            get: function proxyGetter () {\n                return that._data[key];\n            },\n            set: function proxySetter (val) {\n                that._data[key] = val;\n            }\n        })\n    });\n}\n```\n\n我们就可以用app.text代替app._data.text了。\n"
  },
  {
    "path": "docs/聊聊Vue的template编译.MarkDown",
    "content": "## $mount\n\n首先看一下mount的代码\n\n```javascript\n/*把原本不带编译的$mount方法保存下来，在最后会调用。*/\nconst mount = Vue.prototype.$mount\n/*挂载组件，带模板编译*/\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  /*处理模板templete，编译成render函数，render不存在的时候才会编译template，否则优先使用render*/\n  if (!options.render) {\n    let template = options.template\n    /*template存在的时候取template，不存在的时候取el的outerHTML*/\n    if (template) {\n      /*当template是字符串的时候*/\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        /*当template为DOM节点的时候*/\n        template = template.innerHTML\n      } else {\n        /*报错*/\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      /*获取element的outerHTML*/\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n\n      /*将template编译成render函数，这里会有render以及staticRenderFns两个返回，这是vue的编译时优化，static静态不需要在VNode更新时进行patch，优化性能*/\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        shouldDecodeNewlines,\n        delimiters: options.delimiters\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  /*Github:https://github.com/answershuto*/\n  /*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/\n  return mount.call(this, el, hydrating)\n}\n```\n\n通过mount代码我们可以看到，在mount的过程中，如果render函数不存在（render函数存在会优先使用render）会将template进行compileToFunctions得到render以及staticRenderFns。譬如说手写组件时加入了template的情况都会在运行时进行编译。而render function在运行后会返回VNode节点，供页面的渲染以及在update的时候patch。接下来我们来看一下template是如何编译的。\n\n## 一些基础\n\n首先，template会被编译成AST，那么AST是什么？\n\n在计算机科学中，抽象语法树（abstract syntax tree或者缩写为AST），或者语法树（syntax tree），是源代码的抽象语法结构的树状表现形式，这里特指编程语言的源代码。具体可以查看[抽象语法树](https://zh.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E8%AA%9E%E6%B3%95%E6%A8%B9)。\n\nAST会经过generate得到render函数，render的返回值是VNode，VNode是Vue的虚拟DOM节点，具体定义如下：\n\n```javascript\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  functionalContext: Component | void; // only for functional component root nodes\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n  /*Github:https://github.com/answershuto*/\n  \n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions\n  ) {\n    /*当前节点的标签名*/\n    this.tag = tag\n    /*当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息*/\n    this.data = data\n    /*当前节点的子节点，是一个数组*/\n    this.children = children\n    /*当前节点的文本*/\n    this.text = text\n    /*当前虚拟节点对应的真实dom节点*/\n    this.elm = elm\n    /*当前节点的名字空间*/\n    this.ns = undefined\n    /*编译作用域*/\n    this.context = context\n    /*函数化组件作用域*/\n    this.functionalContext = undefined\n    /*节点的key属性，被当作节点的标志，用以优化*/\n    this.key = data && data.key\n    /*组件的option选项*/\n    this.componentOptions = componentOptions\n    /*当前节点对应的组件的实例*/\n    this.componentInstance = undefined\n    /*当前节点的父节点*/\n    this.parent = undefined\n    /*简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false*/\n    this.raw = false\n    /*静态节点标志*/\n    this.isStatic = false\n    /*是否作为跟节点插入*/\n    this.isRootInsert = true\n    /*是否为注释节点*/\n    this.isComment = false\n    /*是否为克隆节点*/\n    this.isCloned = false\n    /*是否有v-once指令*/\n    this.isOnce = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n```\n\n关于VNode的一些细节，请参考[VNode节点](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)。\n\n## createCompiler\n\ncreateCompiler用以创建编译器，返回值是compile以及compileToFunctions。compile是一个编译器，它会将传入的template转换成对应的AST、render函数以及staticRenderFns函数。而compileToFunctions则是带缓存的编译器，同时staticRenderFns以及render函数会被转换成Funtion对象。\n\n因为不同平台有一些不同的options，所以createCompiler会根据平台区分传入一个baseOptions，会与compile本身传入的options合并得到最终的finalOptions。\n\n## compileToFunctions\n\n首先还是贴一下compileToFunctions的代码。\n\n```javascript\n  /*带缓存的编译器，同时staticRenderFns以及render函数会被转换成Funtion对象*/\n  function compileToFunctions (\n    template: string,\n    options?: CompilerOptions,\n    vm?: Component\n  ): CompiledFunctionResult {\n    options = options || {}\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      // detect possible CSP restriction\n      try {\n        new Function('return 1')\n      } catch (e) {\n        if (e.toString().match(/unsafe-eval|CSP/)) {\n          warn(\n            'It seems you are using the standalone build of Vue.js in an ' +\n            'environment with Content Security Policy that prohibits unsafe-eval. ' +\n            'The template compiler cannot work in this environment. Consider ' +\n            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +\n            'templates into render functions.'\n          )\n        }\n      }\n    }\n    /*Github:https://github.com/answershuto*/\n    // check cache\n    /*有缓存的时候直接取出缓存中的结果即可*/\n    const key = options.delimiters\n      ? String(options.delimiters) + template\n      : template\n    if (functionCompileCache[key]) {\n      return functionCompileCache[key]\n    }\n\n    // compile\n    /*编译*/\n    const compiled = compile(template, options)\n\n    // check compilation errors/tips\n    if (process.env.NODE_ENV !== 'production') {\n      if (compiled.errors && compiled.errors.length) {\n        warn(\n          `Error compiling template:\\n\\n${template}\\n\\n` +\n          compiled.errors.map(e => `- ${e}`).join('\\n') + '\\n',\n          vm\n        )\n      }\n      if (compiled.tips && compiled.tips.length) {\n        compiled.tips.forEach(msg => tip(msg, vm))\n      }\n    }\n\n    // turn code into functions\n    const res = {}\n    const fnGenErrors = []\n    /*将render转换成Funtion对象*/\n    res.render = makeFunction(compiled.render, fnGenErrors)\n    /*将staticRenderFns全部转化成Funtion对象 */\n    const l = compiled.staticRenderFns.length\n    res.staticRenderFns = new Array(l)\n    for (let i = 0; i < l; i++) {\n      res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)\n    }\n\n    // check function generation errors.\n    // this should only happen if there is a bug in the compiler itself.\n    // mostly for codegen development use\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {\n        warn(\n          `Failed to generate render function:\\n\\n` +\n          fnGenErrors.map(({ err, code }) => `${err.toString()} in\\n\\n${code}\\n`).join('\\n'),\n          vm\n        )\n      }\n    }\n\n    /*存放在缓存中，以免每次都重新编译*/\n    return (functionCompileCache[key] = res) \n  }\n```\n\n我们可以发现，在闭包中，会有一个functionCompileCache对象作为缓存器。\n\n```javascript\n  /*作为缓存，防止每次都重新编译*/\n  const functionCompileCache: {\n    [key: string]: CompiledFunctionResult;\n  } = Object.create(null)\n```\n\n在进入compileToFunctions以后，会先检查缓存中是否有已经编译好的结果，如果有结果则直接从缓存中读取。这样做防止每次同样的模板都要进行重复的编译工作。\n\n```javascript\n    // check cache\n    /*有缓存的时候直接取出缓存中的结果即可*/\n    const key = options.delimiters\n      ? String(options.delimiters) + template\n      : template\n    if (functionCompileCache[key]) {\n      return functionCompileCache[key]\n    }\n```\n在compileToFunctions的末尾会将编译结果进行缓存\n\n```javascript\n  /*存放在缓存中，以免每次都重新编译*/\n  return (functionCompileCache[key] = res) \n```\n\n## compile\n\n```javascript\n  /*编译，将模板template编译成AST、render函数以及staticRenderFns函数*/\n  function compile (\n    template: string,\n    options?: CompilerOptions\n  ): CompiledResult {\n    const finalOptions = Object.create(baseOptions)\n    const errors = []\n    const tips = []\n    finalOptions.warn = (msg, tip) => {\n      (tip ? tips : errors).push(msg)\n    }\n\n    /*做下面这些merge的目的因为不同平台可以提供自己本身平台的一个baseOptions，内部封装了平台自己的实现，然后把共同的部分抽离开来放在这层compiler中，所以在这里需要merge一下*/\n    if (options) {\n      // merge custom modules\n      /*合并modules*/\n      if (options.modules) {\n        finalOptions.modules = (baseOptions.modules || []).concat(options.modules)\n      }\n      // merge custom directives\n      if (options.directives) {\n        /*合并directives*/\n        finalOptions.directives = extend(\n          Object.create(baseOptions.directives),\n          options.directives\n        )\n      }\n      // copy other options\n      for (const key in options) {\n        /*合并其余的options，modules与directives已经在上面做了特殊处理了*/\n        if (key !== 'modules' && key !== 'directives') {\n          finalOptions[key] = options[key]\n        }\n      }\n    }\n\n    /*基础模板编译，得到编译结果*/\n    const compiled = baseCompile(template, finalOptions)\n    if (process.env.NODE_ENV !== 'production') {\n      errors.push.apply(errors, detectErrors(compiled.ast))\n    }\n    compiled.errors = errors\n    compiled.tips = tips\n    return compiled\n  }\n```\n\ncompile主要做了两件事，一件是合并option（前面说的将平台自有的option与传入的option进行合并），另一件是baseCompile，进行模板template的编译。\n\n来看一下baseCompile\n\n## baseCompile\n\n```javascript\nfunction baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  /*parse解析得到AST*/\n  const ast = parse(template.trim(), options)\n  /*\n    将AST进行优化\n    优化的目标：生成模板AST，检测不需要进行DOM改变的静态子树。\n    一旦检测到这些静态树，我们就能做以下这些事情：\n    1.把它们变成常数，这样我们就再也不需要每次重新渲染时创建新的节点了。\n    2.在patch的过程中直接跳过。\n */\n  optimize(ast, options)\n  /*根据AST生成所需的code（内部包含render与staticRenderFns）*/\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n}\n```\n\nbaseCompile首先会将模板template进行parse得到一个AST，再通过optimize做一些优化，最后通过generate得到render以及staticRenderFns。\n\n### parse\n\nparse的源码可以参见[https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53](https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53)。\n\nparse会用正则等方式解析template模板中的指令、class、style等数据，形成AST。\n\n### optimize\n\noptimize的主要作用是标记static静态节点，这是Vue在编译过程中的一处优化，后面当update更新界面时，会有一个patch的过程，diff算法会直接跳过静态节点，从而减少了比较的过程，优化了patch的性能。\n\n### generate\n\ngenerate是将AST转化成render funtion字符串的过程，得到结果是render的字符串以及staticRenderFns字符串。\n\n---\n\n至此，我们的template模板已经被转化成了我们所需的AST、render function字符串以及staticRenderFns字符串。\n\n## 举个例子\n\n来看一下这段代码的编译结果\n\n```html\n<div class=\"main\" :class=\"bindClass\">\n    <div>{{text}}</div>\n    <div>hello world</div>\n    <div v-for=\"(item, index) in arr\">\n        <p>{{item.name}}</p>\n        <p>{{item.value}}</p>\n        <p>{{index}}</p>\n        <p>---</p>\n    </div>\n    <div v-if=\"text\">\n        {{text}}\n    </div>\n    <div v-else></div>\n</div>\n```\n\n转化后得到AST，如下图：\n\n![img](https://i.loli.net/2017/09/07/59b135001cbfa.png)\n\n我们可以看到最外层的div是这颗AST的根节点，节点上有许多数据代表这个节点的形态，比如static表示是否是静态节点，staticClass表示静态class属性（非bind:class）。children代表该节点的子节点，可以看到children是一个长度为4的数组，里面包含的是该节点下的四个div子节点。children里面的节点与父节点的结构类似，层层往下形成一棵AST。\n\n再来看看由AST得到的render函数\n\n```javascript\nwith(this){\n    return _c(  'div',\n                {\n                    /*static class*/\n                    staticClass:\"main\",\n                    /*bind class*/\n                    class:bindClass\n                },\n                [\n                    _c( 'div', [_v(_s(text))]),\n                    _c('div',[_v(\"hello world\")]),\n                    /*这是一个v-for循环*/\n                    _l(\n                        (arr),\n                        function(item,index){\n                            return _c(  'div',\n                                        [_c('p',[_v(_s(item.name))]),\n                                        _c('p',[_v(_s(item.value))]),\n                                        _c('p',[_v(_s(index))]),\n                                        _c('p',[_v(\"---\")])]\n                                    )\n                        }\n                    ),\n                    /*这是v-if*/\n                    (text)?_c('div',[_v(_s(text))]):_c('div',[_v(\"no text\")])],\n                    2\n            )\n}\n```\n\n\n## \\_c，\\_v，\\_s，\\_q\n\n看了render function字符串，发现有大量的_c，_v，_s，_q，这些函数究竟是什么？\n\n带着问题，我们来看一下[core/instance/render](https://github.com/answershuto/learnVue/blob/master/vue-src/core/instance/render.js#L124)。\n\n```javascript\n/*处理v-once的渲染函数*/\n  Vue.prototype._o = markOnce\n  /*将字符串转化为数字，如果转换失败会返回原字符串*/\n  Vue.prototype._n = toNumber\n  /*将val转化成字符串*/\n  Vue.prototype._s = toString\n  /*处理v-for列表渲染*/\n  Vue.prototype._l = renderList\n  /*处理slot的渲染*/\n  Vue.prototype._t = renderSlot\n  /*检测两个变量是否相等*/\n  Vue.prototype._q = looseEqual\n  /*检测arr数组中是否包含与val变量相等的项*/\n  Vue.prototype._i = looseIndexOf\n  /*处理static树的渲染*/\n  Vue.prototype._m = renderStatic\n  /*处理filters*/\n  Vue.prototype._f = resolveFilter\n  /*从config配置中检查eventKeyCode是否存在*/\n  Vue.prototype._k = checkKeyCodes\n  /*合并v-bind指令到VNode中*/\n  Vue.prototype._b = bindObjectProps\n  /*创建一个文本节点*/\n  Vue.prototype._v = createTextVNode\n  /*创建一个空VNode节点*/\n  Vue.prototype._e = createEmptyVNode\n  /*处理ScopedSlots*/\n  Vue.prototype._u = resolveScopedSlots\n\n  /*创建VNode节点*/\n  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)\n```\n\n通过这些函数，render函数最后会返回一个VNode节点，在_update的时候，经过patch与之前的VNode节点进行比较，得出差异后将这些差异渲染到真实的DOM上。"
  },
  {
    "path": "docs/聊聊keep-alive组件的使用及其实现原理.MarkDown",
    "content": "## keep-alive\n\nkeep-alive是Vue.js的一个内置组件。它能够不活动的组件实例保存在内存中，而不是直接将其销毁，它是一个抽象组件，不会被渲染到真实DOM中，也不会出现在父组件链中。\n\n它提供了include与exclude两个属性，允许组件有条件地进行缓存。\n\n具体内容可以参考[官网](https://cn.vuejs.org/v2/api/#keep-alive)。\n\n## 使用\n\n### 用法\n\n```html\n<keep-alive>\n    <component></component>\n</keep-alive>\n```\n\n这里的component组件会被缓存起来。\n\n### 举个栗子\n\n```html\n<keep-alive>\n    <coma v-if=\"test\"></coma>\n    <comb v-else></comb>\n</keep-alive>\n<button @click=\"test=handleClick\">请点击</button>\n```\n\n```javascript\nexport default {\n    data () {\n        return {\n            test: true\n        }\n    },\n    methods: {\n        handleClick () {\n            this.test = !this.test;\n        }\n    }\n}\n```\n\n在点击button时候，coma与comb两个组件会发生切换，但是这时候这两个组件的状态会被缓存起来，比如说coma与comb组件中都有一个input标签，那么input标签中的内容不会因为组件的切换而消失。\n\n### props\n\nkeep-alive组件提供了include与exclude两个属性来允许组件有条件地进行缓存，二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。\n\n```html\n<keep-alive include=\"a\">\n  <component></component>\n</keep-alive>\n```\n\n将缓存name为a的组件。\n\n```html\n<keep-alive exclude=\"a\">\n  <component></component>\n</keep-alive>\n```\n\nname为a的组件将不会被缓存。\n\n### 生命钩子\n\nkeep-alive提供了两个生命钩子，分别是activated与deactivated。\n\n因为keep-alive会将组件保存在内存中，并不会销毁以及重新创建，所以不会重新调用组件的created等方法，需要用activated与deactivated这两个生命钩子来得知当前组件是否处于活动状态。\n\n---\n\n## 深入keep-alive组件实现\n\n说完了keep-alive组件的使用，我们从源码角度看一下keep-alive组件究竟是如何实现组件的缓存的呢？\n\n### created与destroyed钩子\n\ncreated钩子会创建一个cache对象，用来作为缓存容器，保存vnode节点。\n\n```javascript\ncreated () {\n    /* 缓存对象 */\n    this.cache = Object.create(null)\n},\n```\n\ndestroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。\n\n```javascript\n/* destroyed钩子中销毁所有cache中的组件实例 */\ndestroyed () {\n    for (const key in this.cache) {\n        pruneCacheEntry(this.cache[key])\n    }\n},\n```\n\n### render\n\n接下来是render函数。\n\n```javascript\nrender () {\n    /* 得到slot插槽中的第一个组件 */\n    const vnode: VNode = getFirstComponentChild(this.$slots.default)\n\n    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n    if (componentOptions) {\n        // check pattern\n        /* 获取组件名称，优先获取组件的name字段，否则是组件的tag */\n        const name: ?string = getComponentName(componentOptions)\n        /* name不在inlcude中或者在exlude中则直接返回vnode（没有取缓存） */\n        if (name && (\n        (this.include && !matches(this.include, name)) ||\n        (this.exclude && matches(this.exclude, name))\n        )) {\n            return vnode\n        }\n        const key: ?string = vnode.key == null\n        // same constructor may get registered as different local components\n        // so cid alone is not enough (#3269)\n        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n        : vnode.key\n        /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode，还未缓存过则进行缓存 */\n        if (this.cache[key]) {\n            vnode.componentInstance = this.cache[key].componentInstance\n        } else {\n            this.cache[key] = vnode\n        }\n        /* keepAlive标记位 */\n        vnode.data.keepAlive = true\n    }\n    return vnode\n}\n```\n\n首先通过getFirstComponentChild获取第一个子组件，获取该组件的name（存在组件名则直接使用组件名，否则会使用tag）。接下来会将这个name通过include与exclude属性进行匹配，匹配不成功（说明不需要进行缓存）则不进行任何操作直接返回vnode，vnode是一个VNode类型的对象，不了解VNode的同学可以参考笔者的另一篇文章[《VNode节点》](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown) .\n\n```javascript\n/* 检测name是否匹配 */\nfunction matches (pattern: string | RegExp, name: string): boolean {\n  if (typeof pattern === 'string') {\n    /* 字符串情况，如a,b,c */\n    return pattern.split(',').indexOf(name) > -1\n  } else if (isRegExp(pattern)) {\n    /* 正则 */\n    return pattern.test(name)\n  }\n  /* istanbul ignore next */\n  return false\n}\n```\n\n检测include与exclude属性匹配的函数很简单，include与exclude属性支持字符串如\"a,b,c\"这样组件名以逗号隔开的情况以及正则表达式。matches通过这两种方式分别检测是否匹配当前组件。\n\n```javascript\nif (this.cache[key]) {\n    vnode.componentInstance = this.cache[key].componentInstance\n} else {\n    this.cache[key] = vnode\n}\n```\n\n接下来的事情很简单，根据key在this.cache中查找，如果存在则说明之前已经缓存过了，直接将缓存的vnode的componentInstance（组件实例）覆盖到目前的vnode上面。否则将vnode存储在cache中。\n\n最后返回vnode（有缓存时该vnode的componentInstance已经被替换成缓存中的了）。\n\n### watch\n\n用watch来监听pruneCache与pruneCache这两个属性的改变，在改变的时候修改cache缓存中的缓存数据。\n\n```javascript\nwatch: {\n    /* 监视include以及exclude，在被修改的时候对cache进行修正 */\n    include (val: string | RegExp) {\n        pruneCache(this.cache, this._vnode, name => matches(val, name))\n    },\n    exclude (val: string | RegExp) {\n        pruneCache(this.cache, this._vnode, name => !matches(val, name))\n    }\n},\n```\n\n来看一下pruneCache的实现。\n\n```javascript\n/* 修正cache */\nfunction pruneCache (cache: VNodeCache, current: VNode, filter: Function) {\n  for (const key in cache) {\n    /* 取出cache中的vnode */\n    const cachedNode: ?VNode = cache[key]\n    if (cachedNode) {\n      const name: ?string = getComponentName(cachedNode.componentOptions)\n      /* name不符合filter条件的，同时不是目前渲染的vnode时，销毁vnode对应的组件实例（Vue实例），并从cache中移除 */\n      if (name && !filter(name)) {\n        if (cachedNode !== current) {\n          pruneCacheEntry(cachedNode)\n        }\n        cache[key] = null\n      }\n    }\n  }\n} \n\n/* 销毁vnode对应的组件实例（Vue实例） */\nfunction pruneCacheEntry (vnode: ?VNode) {\n  if (vnode) {\n    vnode.componentInstance.$destroy()\n  }\n}\n```\n\n遍历cache中的所有项，如果不符合filter指定的规则的话，则会执行pruneCacheEntry。pruneCacheEntry则会调用组件实例的$destroy方法来将组件销毁。\n\n## 最后\n\nVue.js内部将DOM节点抽象成了一个个的[VNode节点](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)，keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件（pruneCache与pruneCache）的组件在cache对象中缓存起来，在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。"
  },
  {
    "path": "docs/说说element组件库broadcast与dispatch.MarkDown",
    "content": "众所周知，Vue 在 2.0 版本中去除了$broadcast方法以及$dispatch 方法，最近在学习饿了么的[Element](https://github.com/ElemeFE/element)时重新实现了这两种方法，并以 minix 的方式引入。\n\n看一下[源代码](https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js)\n\n```javascript\nfunction broadcast(componentName, eventName, params) {\n  /*遍历当前节点下的所有子组件*/\n  this.$children.forEach(child => {\n    /*获取子组件名称*/\n    var name = child.$options.componentName;\n\n    if (name === componentName) {\n      /*如果是我们需要广播到的子组件的时候调用$emit触发所需事件，在子组件中用$on监听*/\n      child.$emit.apply(child, [eventName].concat(params));\n    } else {\n      /*非所需子组件则递归遍历深层次子组件*/\n      broadcast.apply(child, [componentName, eventName].concat([params]));\n    }\n  });\n}\nexport default {\n  methods: {\n    /*对多级父组件进行事件派发*/\n    dispatch(componentName, eventName, params) {\n      /*获取父组件，如果以及是根组件，则是$root*/\n      var parent = this.$parent || this.$root;\n      /*获取父节点的组件名*/\n      var name = parent.$options.componentName;\n\n      while (parent && (!name || name !== componentName)) {\n        /*当父组件不是所需组件时继续向上寻找*/\n        parent = parent.$parent;\n\n        if (parent) {\n          name = parent.$options.componentName;\n        }\n      }\n      /*找到所需组件后调用$emit触发当前事件*/\n      if (parent) {\n        parent.$emit.apply(parent, [eventName].concat(params));\n      }\n    },\n    /*\n        向所有子组件进行事件广播。\n        这里包了一层，为了修改broadcast的this对象为当前Vue实例\n    */\n    broadcast(componentName, eventName, params) {\n      broadcast.call(this, componentName, eventName, params);\n    }\n  }\n};\n\n```\n\n其实这里的broadcast与dispatch实现了一个定向的多层级父子组件间的事件广播及事件派发功能。完成多层级分发对应事件的组件间通信功能。\n\nbroadcast通过递归遍历子组件找到所需组件广播事件，而dispatch则逐级向上查找对应父组件派发事件。\n\nbroadcast需要三个参数，componentName（组件名），eventName（事件名称）以及params（数据）。根据componentName深度遍历子组件找到对应组件emit事件eventName。\n\ndispatch同样道理，需要三个参数，componentName（组件名），eventName（事件名称）以及params（数据）。根据componentName向上级一直寻找对应父组件，找到以后emit事件eventName。\n"
  },
  {
    "path": "vue-router-src/components/link.js",
    "content": "/* @flow */\n\nimport { createRoute, isSameRoute, isIncludedRoute } from '../util/route'\nimport { _Vue } from '../install'\n\n// work around weird flow bug\nconst toTypes: Array<Function> = [String, Object]\nconst eventTypes: Array<Function> = [String, Array]\n\nexport default {\n  name: 'RouterLink',\n  props: {\n    to: {\n      type: toTypes,\n      required: true\n    },\n    tag: {\n      type: String,\n      default: 'a'\n    },\n    exact: Boolean,\n    append: Boolean,\n    replace: Boolean,\n    activeClass: String,\n    exactActiveClass: String,\n    event: {\n      type: eventTypes,\n      default: 'click'\n    }\n  },\n  render (h: Function) {\n    const router = this.$router\n    const current = this.$route\n    const { location, route, href } = router.resolve(this.to, current, this.append)\n\n    const classes = {}\n    const globalActiveClass = router.options.linkActiveClass\n    const globalExactActiveClass = router.options.linkExactActiveClass\n    // Support global empty active class\n    const activeClassFallback = globalActiveClass == null\n            ? 'router-link-active'\n            : globalActiveClass\n    const exactActiveClassFallback = globalExactActiveClass == null\n            ? 'router-link-exact-active'\n            : globalExactActiveClass\n    const activeClass = this.activeClass == null\n            ? activeClassFallback\n            : this.activeClass\n    const exactActiveClass = this.exactActiveClass == null\n            ? exactActiveClassFallback\n            : this.exactActiveClass\n    const compareTarget = location.path\n      ? createRoute(null, location, null, router)\n      : route\n\n    classes[exactActiveClass] = isSameRoute(current, compareTarget)\n    classes[activeClass] = this.exact\n      ? classes[exactActiveClass]\n      : isIncludedRoute(current, compareTarget)\n\n    const handler = e => {\n      if (guardEvent(e)) {\n        if (this.replace) {\n          router.replace(location)\n        } else {\n          router.push(location)\n        }\n      }\n    }\n\n    const on = { click: guardEvent }\n    if (Array.isArray(this.event)) {\n      this.event.forEach(e => { on[e] = handler })\n    } else {\n      on[this.event] = handler\n    }\n\n    const data: any = {\n      class: classes\n    }\n\n    if (this.tag === 'a') {\n      data.on = on\n      data.attrs = { href }\n    } else {\n      // find the first <a> child and apply listener and href\n      const a = findAnchor(this.$slots.default)\n      if (a) {\n        // in case the <a> is a static node\n        a.isStatic = false\n        const extend = _Vue.util.extend\n        const aData = a.data = extend({}, a.data)\n        aData.on = on\n        const aAttrs = a.data.attrs = extend({}, a.data.attrs)\n        aAttrs.href = href\n      } else {\n        // doesn't have <a> child, apply listener to self\n        data.on = on\n      }\n    }\n\n    return h(this.tag, data, this.$slots.default)\n  }\n}\n\nfunction guardEvent (e) {\n  // don't redirect with control keys\n  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return\n  // don't redirect when preventDefault called\n  if (e.defaultPrevented) return\n  // don't redirect on right click\n  if (e.button !== undefined && e.button !== 0) return\n  // don't redirect if `target=\"_blank\"`\n  if (e.currentTarget && e.currentTarget.getAttribute) {\n    const target = e.currentTarget.getAttribute('target')\n    if (/\\b_blank\\b/i.test(target)) return\n  }\n  // this may be a Weex event which doesn't have this method\n  if (e.preventDefault) {\n    e.preventDefault()\n  }\n  return true\n}\n\nfunction findAnchor (children) {\n  if (children) {\n    let child\n    for (let i = 0; i < children.length; i++) {\n      child = children[i]\n      if (child.tag === 'a') {\n        return child\n      }\n      if (child.children && (child = findAnchor(child.children))) {\n        return child\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-router-src/components/view.js",
    "content": "import { warn } from '../util/warn'\n\n/* router-view组件 */\nexport default {\n  name: 'RouterView',\n  /* \n    https://cn.vuejs.org/v2/api/#functional\n    使组件无状态 (没有 data ) 和无实例 (没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使他们更容易渲染。\n  */\n  functional: true,\n  props: {\n    name: {\n      type: String,\n      default: 'default'\n    }\n  },\n  render (_, { props, children, parent, data }) {\n    /* 标记位，标记是route-view组件 */\n    data.routerView = true\n\n    // directly use parent context's createElement() function\n    // so that components rendered by router-view can resolve named slots\n    /* 直接使用父组件的createElement函数 */\n    const h = parent.$createElement\n    /* props的name，默认'default' */\n    const name = props.name\n    /* option中的VueRouter对象 */\n    const route = parent.$route\n    /* 在parent上建立一个缓存对象 */\n    const cache = parent._routerViewCache || (parent._routerViewCache = {})\n\n    // determine current view depth, also check to see if the tree\n    // has been toggled inactive but kept-alive.\n    /* 记录组件深度 */\n    let depth = 0\n    /* 标记是否是待用（非alive状态）） */\n    let inactive = false\n    /* _routerRoot中中存放了根组件的势力，这边循环向上级访问，直到访问到根组件，得到depth深度 */\n    while (parent && parent._routerRoot !== parent) {\n      if (parent.$vnode && parent.$vnode.data.routerView) {\n        depth++\n      }\n      /* 如果_inactive为true，代表是在keep-alive中且是待用（非alive状态） */\n      if (parent._inactive) {\n        inactive = true\n      }\n      parent = parent.$parent\n    }\n    /* 存放route-view组件的深度 */\n    data.routerViewDepth = depth\n\n    // render previous view if the tree is inactive and kept-alive\n    /* 如果inactive为true说明在keep-alive组件中，直接从缓存中取 */\n    if (inactive) {\n      return h(cache[name], data, children)\n    }\n\n    const matched = route.matched[depth]\n    // render empty node if no matched route\n    /* 如果没有匹配到的路由，则渲染一个空节点 */\n    if (!matched) {\n      cache[name] = null\n      return h()\n    }\n\n    /* 从成功匹配到的路由中取出组件 */\n    const component = cache[name] = matched.components[name]\n\n    // attach instance registration hook\n    // this will be called in the instance's injected lifecycle hooks\n    /* 注册实例的registration钩子，这个函数将在实例被注入的加入到组件的生命钩子（beforeCreate与destroyed）中被调用 */\n    data.registerRouteInstance = (vm, val) => {  \n      /* 第二个值不存在的时候为注销 */\n      // val could be undefined for unregistration\n      /* 获取组件实例 */\n      const current = matched.instances[name]\n      if (\n        (val && current !== vm) ||\n        (!val && current === vm)\n      ) {\n        /* 这里有两种情况，一种是val存在，则用val替换当前组件实例，另一种则是val不存在，则直接将val（这个时候其实是一个undefined）赋给instances */\n        matched.instances[name] = val\n      }\n    }\n\n    // also register instance in prepatch hook\n    // in case the same component instance is reused across different routes\n    ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {\n      matched.instances[name] = vnode.componentInstance\n    }\n\n    // resolve props\n    let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name])\n    if (propsToPass) {\n      // clone to prevent mutation\n      propsToPass = data.props = extend({}, propsToPass)\n      // pass non-declared props as attrs\n      const attrs = data.attrs = data.attrs || {}\n      for (const key in propsToPass) {\n        if (!component.props || !(key in component.props)) {\n          attrs[key] = propsToPass[key]\n          delete propsToPass[key]\n        }\n      }\n    }\n\n    return h(component, data, children)\n  }\n}\n\nfunction resolveProps (route, config) {\n  switch (typeof config) {\n    case 'undefined':\n      return\n    case 'object':\n      return config\n    case 'function':\n      return config(route)\n    case 'boolean':\n      return config ? route.params : undefined\n    default:\n      if (process.env.NODE_ENV !== 'production') {\n        warn(\n          false,\n          `props in \"${route.path}\" is a ${typeof config}, ` +\n          `expecting an object, function or boolean.`\n        )\n      }\n  }\n}\n\nfunction extend (to, from) {\n  for (const key in from) {\n    to[key] = from[key]\n  }\n  return to\n}\n"
  },
  {
    "path": "vue-router-src/create-matcher.js",
    "content": "/* @flow */\n\nimport type VueRouter from './index'\nimport { resolvePath } from './util/path'\nimport { assert, warn } from './util/warn'\nimport { createRoute } from './util/route'\nimport { fillParams } from './util/params'\nimport { createRouteMap } from './create-route-map'\nimport { normalizeLocation } from './util/location'\n\nexport type Matcher = {\n  match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;\n  addRoutes: (routes: Array<RouteConfig>) => void;\n};\n\nexport function createMatcher (\n  routes: Array<RouteConfig>,\n  router: VueRouter\n): Matcher {\n  const { pathList, pathMap, nameMap } = createRouteMap(routes)\n\n  function addRoutes (routes) {\n    createRouteMap(routes, pathList, pathMap, nameMap)\n  }\n\n  function match (\n    raw: RawLocation,\n    currentRoute?: Route,\n    redirectedFrom?: Location\n  ): Route {\n    const location = normalizeLocation(raw, currentRoute, false, router)\n    const { name } = location\n\n    if (name) {\n      const record = nameMap[name]\n      if (process.env.NODE_ENV !== 'production') {\n        warn(record, `Route with name '${name}' does not exist`)\n      }\n      if (!record) return _createRoute(null, location)\n      const paramNames = record.regex.keys\n        .filter(key => !key.optional)\n        .map(key => key.name)\n\n      if (typeof location.params !== 'object') {\n        location.params = {}\n      }\n\n      if (currentRoute && typeof currentRoute.params === 'object') {\n        for (const key in currentRoute.params) {\n          if (!(key in location.params) && paramNames.indexOf(key) > -1) {\n            location.params[key] = currentRoute.params[key]\n          }\n        }\n      }\n\n      if (record) {\n        location.path = fillParams(record.path, location.params, `named route \"${name}\"`)\n        return _createRoute(record, location, redirectedFrom)\n      }\n    } else if (location.path) {\n      location.params = {}\n      for (let i = 0; i < pathList.length; i++) {\n        const path = pathList[i]\n        const record = pathMap[path]\n        if (matchRoute(record.regex, location.path, location.params)) {\n          return _createRoute(record, location, redirectedFrom)\n        }\n      }\n    }\n    // no match\n    return _createRoute(null, location)\n  }\n\n  function redirect (\n    record: RouteRecord,\n    location: Location\n  ): Route {\n    const originalRedirect = record.redirect\n    let redirect = typeof originalRedirect === 'function'\n        ? originalRedirect(createRoute(record, location, null, router))\n        : originalRedirect\n\n    if (typeof redirect === 'string') {\n      redirect = { path: redirect }\n    }\n\n    if (!redirect || typeof redirect !== 'object') {\n      if (process.env.NODE_ENV !== 'production') {\n        warn(\n          false, `invalid redirect option: ${JSON.stringify(redirect)}`\n        )\n      }\n      return _createRoute(null, location)\n    }\n\n    const re: Object = redirect\n    const { name, path } = re\n    let { query, hash, params } = location\n    query = re.hasOwnProperty('query') ? re.query : query\n    hash = re.hasOwnProperty('hash') ? re.hash : hash\n    params = re.hasOwnProperty('params') ? re.params : params\n\n    if (name) {\n      // resolved named direct\n      const targetRecord = nameMap[name]\n      if (process.env.NODE_ENV !== 'production') {\n        assert(targetRecord, `redirect failed: named route \"${name}\" not found.`)\n      }\n      return match({\n        _normalized: true,\n        name,\n        query,\n        hash,\n        params\n      }, undefined, location)\n    } else if (path) {\n      // 1. resolve relative redirect\n      const rawPath = resolveRecordPath(path, record)\n      // 2. resolve params\n      const resolvedPath = fillParams(rawPath, params, `redirect route with path \"${rawPath}\"`)\n      // 3. rematch with existing query and hash\n      return match({\n        _normalized: true,\n        path: resolvedPath,\n        query,\n        hash\n      }, undefined, location)\n    } else {\n      if (process.env.NODE_ENV !== 'production') {\n        warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)\n      }\n      return _createRoute(null, location)\n    }\n  }\n\n  function alias (\n    record: RouteRecord,\n    location: Location,\n    matchAs: string\n  ): Route {\n    const aliasedPath = fillParams(matchAs, location.params, `aliased route with path \"${matchAs}\"`)\n    const aliasedMatch = match({\n      _normalized: true,\n      path: aliasedPath\n    })\n    if (aliasedMatch) {\n      const matched = aliasedMatch.matched\n      const aliasedRecord = matched[matched.length - 1]\n      location.params = aliasedMatch.params\n      return _createRoute(aliasedRecord, location)\n    }\n    return _createRoute(null, location)\n  }\n\n  function _createRoute (\n    record: ?RouteRecord,\n    location: Location,\n    redirectedFrom?: Location\n  ): Route {\n    if (record && record.redirect) {\n      return redirect(record, redirectedFrom || location)\n    }\n    if (record && record.matchAs) {\n      return alias(record, location, record.matchAs)\n    }\n    return createRoute(record, location, redirectedFrom, router)\n  }\n\n  return {\n    match,\n    addRoutes\n  }\n}\n\nfunction matchRoute (\n  regex: RouteRegExp,\n  path: string,\n  params: Object\n): boolean {\n  const m = path.match(regex)\n\n  if (!m) {\n    return false\n  } else if (!params) {\n    return true\n  }\n\n  for (let i = 1, len = m.length; i < len; ++i) {\n    const key = regex.keys[i - 1]\n    const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]\n    if (key) {\n      params[key.name] = val\n    }\n  }\n\n  return true\n}\n\nfunction resolveRecordPath (path: string, record: RouteRecord): string {\n  return resolvePath(path, record.parent ? record.parent.path : '/', true)\n}\n"
  },
  {
    "path": "vue-router-src/create-route-map.js",
    "content": "/* @flow */\n\nimport Regexp from 'path-to-regexp'\nimport { cleanPath } from './util/path'\nimport { assert, warn } from './util/warn'\n\nexport function createRouteMap (\n  routes: Array<RouteConfig>,\n  oldPathList?: Array<string>,\n  oldPathMap?: Dictionary<RouteRecord>,\n  oldNameMap?: Dictionary<RouteRecord>\n): {\n  pathList: Array<string>;\n  pathMap: Dictionary<RouteRecord>;\n  nameMap: Dictionary<RouteRecord>;\n} {\n  // the path list is used to control path matching priority\n  const pathList: Array<string> = oldPathList || []\n  // $flow-disable-line\n  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)\n  // $flow-disable-line\n  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)\n\n  routes.forEach(route => {\n    addRouteRecord(pathList, pathMap, nameMap, route)\n  })\n\n  // ensure wildcard routes are always at the end\n  for (let i = 0, l = pathList.length; i < l; i++) {\n    if (pathList[i] === '*') {\n      pathList.push(pathList.splice(i, 1)[0])\n      l--\n      i--\n    }\n  }\n\n  return {\n    pathList,\n    pathMap,\n    nameMap\n  }\n}\n\nfunction addRouteRecord (\n  pathList: Array<string>,\n  pathMap: Dictionary<RouteRecord>,\n  nameMap: Dictionary<RouteRecord>,\n  route: RouteConfig,\n  parent?: RouteRecord,\n  matchAs?: string\n) {\n  const { path, name } = route\n  if (process.env.NODE_ENV !== 'production') {\n    assert(path != null, `\"path\" is required in a route configuration.`)\n    assert(\n      typeof route.component !== 'string',\n      `route config \"component\" for path: ${String(path || name)} cannot be a ` +\n      `string id. Use an actual component instead.`\n    )\n  }\n\n  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}\n  const normalizedPath = normalizePath(\n    path,\n    parent,\n    pathToRegexpOptions.strict\n  )\n\n  if (typeof route.caseSensitive === 'boolean') {\n    pathToRegexpOptions.sensitive = route.caseSensitive\n  }\n\n  const record: RouteRecord = {\n    path: normalizedPath,\n    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),\n    components: route.components || { default: route.component },\n    instances: {},\n    name,\n    parent,\n    matchAs,\n    redirect: route.redirect,\n    beforeEnter: route.beforeEnter,\n    meta: route.meta || {},\n    props: route.props == null\n      ? {}\n      : route.components\n        ? route.props\n        : { default: route.props }\n  }\n\n  if (route.children) {\n    // Warn if route is named, does not redirect and has a default child route.\n    // If users navigate to this route by name, the default child will\n    // not be rendered (GH Issue #629)\n    if (process.env.NODE_ENV !== 'production') {\n      if (route.name && !route.redirect && route.children.some(child => /^\\/?$/.test(child.path))) {\n        warn(\n          false,\n          `Named Route '${route.name}' has a default child route. ` +\n          `When navigating to this named route (:to=\"{name: '${route.name}'\"), ` +\n          `the default child route will not be rendered. Remove the name from ` +\n          `this route and use the name of the default child route for named ` +\n          `links instead.`\n        )\n      }\n    }\n    route.children.forEach(child => {\n      const childMatchAs = matchAs\n        ? cleanPath(`${matchAs}/${child.path}`)\n        : undefined\n      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)\n    })\n  }\n\n  if (route.alias !== undefined) {\n    const aliases = Array.isArray(route.alias)\n      ? route.alias\n      : [route.alias]\n\n    aliases.forEach(alias => {\n      const aliasRoute = {\n        path: alias,\n        children: route.children\n      }\n      addRouteRecord(\n        pathList,\n        pathMap,\n        nameMap,\n        aliasRoute,\n        parent,\n        record.path || '/' // matchAs\n      )\n    })\n  }\n\n  if (!pathMap[record.path]) {\n    pathList.push(record.path)\n    pathMap[record.path] = record\n  }\n\n  if (name) {\n    if (!nameMap[name]) {\n      nameMap[name] = record\n    } else if (process.env.NODE_ENV !== 'production' && !matchAs) {\n      warn(\n        false,\n        `Duplicate named routes definition: ` +\n        `{ name: \"${name}\", path: \"${record.path}\" }`\n      )\n    }\n  }\n}\n\nfunction compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp {\n  const regex = Regexp(path, [], pathToRegexpOptions)\n  if (process.env.NODE_ENV !== 'production') {\n    const keys: any = Object.create(null)\n    regex.keys.forEach(key => {\n      warn(!keys[key.name], `Duplicate param keys in route with path: \"${path}\"`)\n      keys[key.name] = true\n    })\n  }\n  return regex\n}\n\nfunction normalizePath (path: string, parent?: RouteRecord, strict?: boolean): string {\n  if (!strict) path = path.replace(/\\/$/, '')\n  if (path[0] === '/') return path\n  if (parent == null) return path\n  return cleanPath(`${parent.path}/${path}`)\n}\n"
  },
  {
    "path": "vue-router-src/history/abstract.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\n\nexport class AbstractHistory extends History {\n  index: number;\n  stack: Array<Route>;\n\n  constructor (router: Router, base: ?string) {\n    super(router, base)\n    this.stack = []\n    this.index = -1\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.transitionTo(location, route => {\n      this.stack = this.stack.slice(0, this.index + 1).concat(route)\n      this.index++\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.transitionTo(location, route => {\n      this.stack = this.stack.slice(0, this.index).concat(route)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  go (n: number) {\n    const targetIndex = this.index + n\n    if (targetIndex < 0 || targetIndex >= this.stack.length) {\n      return\n    }\n    const route = this.stack[targetIndex]\n    this.confirmTransition(route, () => {\n      this.index = targetIndex\n      this.updateRoute(route)\n    })\n  }\n\n  getCurrentLocation () {\n    const current = this.stack[this.stack.length - 1]\n    return current ? current.fullPath : '/'\n  }\n\n  ensureURL () {\n    // noop\n  }\n}\n"
  },
  {
    "path": "vue-router-src/history/base.js",
    "content": "/* @flow */\n\nimport { _Vue } from '../install'\nimport type Router from '../index'\nimport { inBrowser } from '../util/dom'\nimport { runQueue } from '../util/async'\nimport { warn, isError } from '../util/warn'\nimport { START, isSameRoute } from '../util/route'\nimport {\n  flatten,\n  flatMapComponents,\n  resolveAsyncComponents\n} from '../util/resolve-components'\n\nexport class History {\n  router: Router;\n  base: string;\n  current: Route;\n  pending: ?Route;\n  cb: (r: Route) => void;\n  ready: boolean;\n  readyCbs: Array<Function>;\n  readyErrorCbs: Array<Function>;\n  errorCbs: Array<Function>;\n\n  // implemented by sub-classes\n  +go: (n: number) => void;\n  +push: (loc: RawLocation) => void;\n  +replace: (loc: RawLocation) => void;\n  +ensureURL: (push?: boolean) => void;\n  +getCurrentLocation: () => string;\n\n  constructor (router: Router, base: ?string) {\n    this.router = router\n    this.base = normalizeBase(base)\n    // start with a route object that stands for \"nowhere\"\n    this.current = START\n    this.pending = null\n    this.ready = false\n    this.readyCbs = []\n    this.readyErrorCbs = []\n    this.errorCbs = []\n  }\n\n  listen (cb: Function) {\n    this.cb = cb\n  }\n\n  onReady (cb: Function, errorCb: ?Function) {\n    if (this.ready) {\n      cb()\n    } else {\n      this.readyCbs.push(cb)\n      if (errorCb) {\n        this.readyErrorCbs.push(errorCb)\n      }\n    }\n  }\n\n  onError (errorCb: Function) {\n    this.errorCbs.push(errorCb)\n  }\n\n  transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const route = this.router.match(location, this.current)\n    this.confirmTransition(route, () => {\n      this.updateRoute(route)\n      onComplete && onComplete(route)\n      this.ensureURL()\n\n      // fire ready cbs once\n      if (!this.ready) {\n        this.ready = true\n        this.readyCbs.forEach(cb => { cb(route) })\n      }\n    }, err => {\n      if (onAbort) {\n        onAbort(err)\n      }\n      if (err && !this.ready) {\n        this.ready = true\n        this.readyErrorCbs.forEach(cb => { cb(err) })\n      }\n    })\n  }\n\n  confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {\n    const current = this.current\n    const abort = err => {\n      if (isError(err)) {\n        if (this.errorCbs.length) {\n          this.errorCbs.forEach(cb => { cb(err) })\n        } else {\n          warn(false, 'uncaught error during route navigation:')\n          console.error(err)\n        }\n      }\n      onAbort && onAbort(err)\n    }\n    if (\n      isSameRoute(route, current) &&\n      // in the case the route map has been dynamically appended to\n      route.matched.length === current.matched.length\n    ) {\n      this.ensureURL()\n      return abort()\n    }\n\n    const {\n      updated,\n      deactivated,\n      activated\n    } = resolveQueue(this.current.matched, route.matched)\n\n    const queue: Array<?NavigationGuard> = [].concat(\n      // in-component leave guards\n      extractLeaveGuards(deactivated),\n      // global before hooks\n      this.router.beforeHooks,\n      // in-component update hooks\n      extractUpdateHooks(updated),\n      // in-config enter guards\n      activated.map(m => m.beforeEnter),\n      // async components\n      resolveAsyncComponents(activated)\n    )\n\n    this.pending = route\n    const iterator = (hook: NavigationGuard, next) => {\n      if (this.pending !== route) {\n        return abort()\n      }\n      try {\n        hook(route, current, (to: any) => {\n          if (to === false || isError(to)) {\n            // next(false) -> abort navigation, ensure current URL\n            this.ensureURL(true)\n            abort(to)\n          } else if (\n            typeof to === 'string' ||\n            (typeof to === 'object' && (\n              typeof to.path === 'string' ||\n              typeof to.name === 'string'\n            ))\n          ) {\n            // next('/') or next({ path: '/' }) -> redirect\n            abort()\n            if (typeof to === 'object' && to.replace) {\n              this.replace(to)\n            } else {\n              this.push(to)\n            }\n          } else {\n            // confirm transition and pass on the value\n            next(to)\n          }\n        })\n      } catch (e) {\n        abort(e)\n      }\n    }\n\n    runQueue(queue, iterator, () => {\n      const postEnterCbs = []\n      const isValid = () => this.current === route\n      // wait until async components are resolved before\n      // extracting in-component enter guards\n      const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)\n      const queue = enterGuards.concat(this.router.resolveHooks)\n      runQueue(queue, iterator, () => {\n        if (this.pending !== route) {\n          return abort()\n        }\n        this.pending = null\n        onComplete(route)\n        if (this.router.app) {\n          this.router.app.$nextTick(() => {\n            postEnterCbs.forEach(cb => { cb() })\n          })\n        }\n      })\n    })\n  }\n\n  updateRoute (route: Route) {\n    const prev = this.current\n    this.current = route\n    this.cb && this.cb(route)\n    this.router.afterHooks.forEach(hook => {\n      hook && hook(route, prev)\n    })\n  }\n}\n\nfunction normalizeBase (base: ?string): string {\n  if (!base) {\n    if (inBrowser) {\n      // respect <base> tag\n      const baseEl = document.querySelector('base')\n      base = (baseEl && baseEl.getAttribute('href')) || '/'\n      // strip full URL origin\n      base = base.replace(/^https?:\\/\\/[^\\/]+/, '')\n    } else {\n      base = '/'\n    }\n  }\n  // make sure there's the starting slash\n  if (base.charAt(0) !== '/') {\n    base = '/' + base\n  }\n  // remove trailing slash\n  return base.replace(/\\/$/, '')\n}\n\nfunction resolveQueue (\n  current: Array<RouteRecord>,\n  next: Array<RouteRecord>\n): {\n  updated: Array<RouteRecord>,\n  activated: Array<RouteRecord>,\n  deactivated: Array<RouteRecord>\n} {\n  let i\n  const max = Math.max(current.length, next.length)\n  for (i = 0; i < max; i++) {\n    if (current[i] !== next[i]) {\n      break\n    }\n  }\n  return {\n    updated: next.slice(0, i),\n    activated: next.slice(i),\n    deactivated: current.slice(i)\n  }\n}\n\nfunction extractGuards (\n  records: Array<RouteRecord>,\n  name: string,\n  bind: Function,\n  reverse?: boolean\n): Array<?Function> {\n  const guards = flatMapComponents(records, (def, instance, match, key) => {\n    const guard = extractGuard(def, name)\n    if (guard) {\n      return Array.isArray(guard)\n        ? guard.map(guard => bind(guard, instance, match, key))\n        : bind(guard, instance, match, key)\n    }\n  })\n  return flatten(reverse ? guards.reverse() : guards)\n}\n\nfunction extractGuard (\n  def: Object | Function,\n  key: string\n): NavigationGuard | Array<NavigationGuard> {\n  if (typeof def !== 'function') {\n    // extend now so that global mixins are applied.\n    def = _Vue.extend(def)\n  }\n  return def.options[key]\n}\n\nfunction extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {\n  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)\n}\n\nfunction extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {\n  return extractGuards(updated, 'beforeRouteUpdate', bindGuard)\n}\n\nfunction bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {\n  if (instance) {\n    return function boundRouteGuard () {\n      return guard.apply(instance, arguments)\n    }\n  }\n}\n\nfunction extractEnterGuards (\n  activated: Array<RouteRecord>,\n  cbs: Array<Function>,\n  isValid: () => boolean\n): Array<?Function> {\n  return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {\n    return bindEnterGuard(guard, match, key, cbs, isValid)\n  })\n}\n\nfunction bindEnterGuard (\n  guard: NavigationGuard,\n  match: RouteRecord,\n  key: string,\n  cbs: Array<Function>,\n  isValid: () => boolean\n): NavigationGuard {\n  return function routeEnterGuard (to, from, next) {\n    return guard(to, from, cb => {\n      next(cb)\n      if (typeof cb === 'function') {\n        cbs.push(() => {\n          // #750\n          // if a router-view is wrapped with an out-in transition,\n          // the instance may not have been registered at this time.\n          // we will need to poll for registration until current route\n          // is no longer valid.\n          poll(cb, match.instances, key, isValid)\n        })\n      }\n    })\n  }\n}\n\nfunction poll (\n  cb: any, // somehow flow cannot infer this is a function\n  instances: Object,\n  key: string,\n  isValid: () => boolean\n) {\n  if (instances[key]) {\n    cb(instances[key])\n  } else if (isValid()) {\n    setTimeout(() => {\n      poll(cb, instances, key, isValid)\n    }, 16)\n  }\n}\n"
  },
  {
    "path": "vue-router-src/history/hash.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\nimport { cleanPath } from '../util/path'\nimport { getLocation } from './html5'\nimport { setupScroll, handleScroll } from '../util/scroll'\nimport { pushState, replaceState, supportsPushState } from '../util/push-state'\n\nexport class HashHistory extends History {\n  constructor (router: Router, base: ?string, fallback: boolean) {\n    super(router, base)\n    // check history fallback deeplinking\n    if (fallback && checkFallback(this.base)) {\n      return\n    }\n    ensureSlash()\n  }\n\n  // this is delayed until the app mounts\n  // to avoid the hashchange listener being fired too early\n  setupListeners () {\n    const router = this.router\n    const expectScroll = router.options.scrollBehavior\n    const supportsScroll = supportsPushState && expectScroll\n\n    if (supportsScroll) {\n      setupScroll()\n    }\n\n    window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {\n      const current = this.current\n      if (!ensureSlash()) {\n        return\n      }\n      this.transitionTo(getHash(), route => {\n        if (supportsScroll) {\n          handleScroll(this.router, route, current, true)\n        }\n        if (!supportsPushState) {\n          replaceHash(route.fullPath)\n        }\n      })\n    })\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      pushHash(route.fullPath)\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      replaceHash(route.fullPath)\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  go (n: number) {\n    window.history.go(n)\n  }\n\n  ensureURL (push?: boolean) {\n    const current = this.current.fullPath\n    if (getHash() !== current) {\n      push ? pushHash(current) : replaceHash(current)\n    }\n  }\n\n  getCurrentLocation () {\n    return getHash()\n  }\n}\n\nfunction checkFallback (base) {\n  const location = getLocation(base)\n  if (!/^\\/#/.test(location)) {\n    window.location.replace(\n      cleanPath(base + '/#' + location)\n    )\n    return true\n  }\n}\n\nfunction ensureSlash (): boolean {\n  const path = getHash()\n  if (path.charAt(0) === '/') {\n    return true\n  }\n  replaceHash('/' + path)\n  return false\n}\n\nexport function getHash (): string {\n  // We can't use window.location.hash here because it's not\n  // consistent across browsers - Firefox will pre-decode it!\n  const href = window.location.href\n  const index = href.indexOf('#')\n  return index === -1 ? '' : href.slice(index + 1)\n}\n\nfunction getUrl (path) {\n  const href = window.location.href\n  const i = href.indexOf('#')\n  const base = i >= 0 ? href.slice(0, i) : href\n  return `${base}#${path}`\n}\n\nfunction pushHash (path) {\n  if (supportsPushState) {\n    pushState(getUrl(path))\n  } else {\n    window.location.hash = path\n  }\n}\n\nfunction replaceHash (path) {\n  if (supportsPushState) {\n    replaceState(getUrl(path))\n  } else {\n    window.location.replace(getUrl(path))\n  }\n}\n"
  },
  {
    "path": "vue-router-src/history/html5.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\nimport { cleanPath } from '../util/path'\nimport { START } from '../util/route'\nimport { setupScroll, handleScroll } from '../util/scroll'\nimport { pushState, replaceState, supportsPushState } from '../util/push-state'\n\nexport class HTML5History extends History {\n  constructor (router: Router, base: ?string) {\n    super(router, base)\n\n    const expectScroll = router.options.scrollBehavior\n    const supportsScroll = supportsPushState && expectScroll\n\n    if (supportsScroll) {\n      setupScroll()\n    }\n\n    const initLocation = getLocation(this.base)\n    window.addEventListener('popstate', e => {\n      const current = this.current\n\n      // Avoiding first `popstate` event dispatched in some browsers but first\n      // history route not updated since async guard at the same time.\n      const location = getLocation(this.base)\n      if (this.current === START && location === initLocation) {\n        return\n      }\n\n      this.transitionTo(location, route => {\n        if (supportsScroll) {\n          handleScroll(router, route, current, true)\n        }\n      })\n    })\n  }\n\n  go (n: number) {\n    window.history.go(n)\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      pushState(cleanPath(this.base + route.fullPath))\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      replaceState(cleanPath(this.base + route.fullPath))\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  ensureURL (push?: boolean) {\n    if (getLocation(this.base) !== this.current.fullPath) {\n      const current = cleanPath(this.base + this.current.fullPath)\n      push ? pushState(current) : replaceState(current)\n    }\n  }\n\n  getCurrentLocation (): string {\n    return getLocation(this.base)\n  }\n}\n\nexport function getLocation (base: string): string {\n  let path = window.location.pathname\n  if (base && path.indexOf(base) === 0) {\n    path = path.slice(base.length)\n  }\n  return (path || '/') + window.location.search + window.location.hash\n}\n"
  },
  {
    "path": "vue-router-src/index.js",
    "content": "/* @flow */\n\nimport { install } from './install'\nimport { START } from './util/route'\nimport { assert } from './util/warn'\nimport { inBrowser } from './util/dom'\nimport { cleanPath } from './util/path'\nimport { createMatcher } from './create-matcher'\nimport { normalizeLocation } from './util/location'\nimport { supportsPushState } from './util/push-state'\n\nimport { HashHistory } from './history/hash'\nimport { HTML5History } from './history/html5'\nimport { AbstractHistory } from './history/abstract'\n\nimport type { Matcher } from './create-matcher'\n\n/* 导出的VueRouter对象，用来包装store */\nexport default class VueRouter {\n  static install: () => void;\n  static version: string;\n\n  app: any;\n  apps: Array<any>;\n  ready: boolean;\n  readyCbs: Array<Function>;\n  options: RouterOptions;\n  mode: string;\n  history: HashHistory | HTML5History | AbstractHistory;\n  matcher: Matcher;\n  fallback: boolean;\n  beforeHooks: Array<?NavigationGuard>;\n  resolveHooks: Array<?NavigationGuard>;\n  afterHooks: Array<?AfterNavigationHook>;\n\n  constructor (options: RouterOptions = {}) {\n    this.app = null\n    /* 保存vm实例 */\n    this.apps = []\n    this.options = options\n    this.beforeHooks = []\n    this.resolveHooks = []\n    this.afterHooks = []\n    this.matcher = createMatcher(options.routes || [], this)\n\n    let mode = options.mode || 'hash'\n    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false\n    if (this.fallback) {\n      mode = 'hash'\n    }\n    if (!inBrowser) {\n      mode = 'abstract'\n    }\n    this.mode = mode\n\n    switch (mode) {\n      case 'history':\n        this.history = new HTML5History(this, options.base)\n        break\n      case 'hash':\n        this.history = new HashHistory(this, options.base, this.fallback)\n        break\n      case 'abstract':\n        this.history = new AbstractHistory(this, options.base)\n        break\n      default:\n        if (process.env.NODE_ENV !== 'production') {\n          assert(false, `invalid mode: ${mode}`)\n        }\n    }\n  }\n\n  match (\n    raw: RawLocation,\n    current?: Route,\n    redirectedFrom?: Location\n  ): Route {\n    return this.matcher.match(raw, current, redirectedFrom)\n  }\n\n  get currentRoute (): ?Route {\n    return this.history && this.history.current\n  }\n\n  /* 初始化 */\n  init (app: any /* Vue component instance */) {\n    /* 未安装就调用init会抛出异常 */\n    process.env.NODE_ENV !== 'production' && assert(\n      install.installed,\n      `not installed. Make sure to call \\`Vue.use(VueRouter)\\` ` +\n      `before creating root instance.`\n    )\n\n    /* 将当前vm实例保存在app中 */\n    this.apps.push(app)\n\n    // main app already initialized.\n    /* 已存在说明已经被init过了，直接返回 */\n    if (this.app) {\n      return\n    }\n\n    /* this.app保存当前vm实例 */\n    this.app = app\n\n    const history = this.history\n\n    if (history instanceof HTML5History) {\n      history.transitionTo(history.getCurrentLocation())\n    } else if (history instanceof HashHistory) {\n      const setupHashListener = () => {\n        history.setupListeners()\n      }\n      history.transitionTo(\n        history.getCurrentLocation(),\n        setupHashListener,\n        setupHashListener\n      )\n    }\n\n    history.listen(route => {\n      this.apps.forEach((app) => {\n        app._route = route\n      })\n    })\n  }\n\n  beforeEach (fn: Function): Function {\n    return registerHook(this.beforeHooks, fn)\n  }\n\n  beforeResolve (fn: Function): Function {\n    return registerHook(this.resolveHooks, fn)\n  }\n\n  afterEach (fn: Function): Function {\n    return registerHook(this.afterHooks, fn)\n  }\n\n  onReady (cb: Function, errorCb?: Function) {\n    this.history.onReady(cb, errorCb)\n  }\n\n  onError (errorCb: Function) {\n    this.history.onError(errorCb)\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.history.push(location, onComplete, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.history.replace(location, onComplete, onAbort)\n  }\n\n  go (n: number) {\n    this.history.go(n)\n  }\n\n  back () {\n    this.go(-1)\n  }\n\n  forward () {\n    this.go(1)\n  }\n\n  getMatchedComponents (to?: RawLocation | Route): Array<any> {\n    const route: any = to\n      ? to.matched\n        ? to\n        : this.resolve(to).route\n      : this.currentRoute\n    if (!route) {\n      return []\n    }\n    return [].concat.apply([], route.matched.map(m => {\n      return Object.keys(m.components).map(key => {\n        return m.components[key]\n      })\n    }))\n  }\n\n  resolve (\n    to: RawLocation,\n    current?: Route,\n    append?: boolean\n  ): {\n    location: Location,\n    route: Route,\n    href: string,\n    // for backwards compat\n    normalizedTo: Location,\n    resolved: Route\n  } {\n    const location = normalizeLocation(\n      to,\n      current || this.history.current,\n      append,\n      this\n    )\n    const route = this.match(location, current)\n    const fullPath = route.redirectedFrom || route.fullPath\n    const base = this.history.base\n    const href = createHref(base, fullPath, this.mode)\n    return {\n      location,\n      route,\n      href,\n      // for backwards compat\n      normalizedTo: location,\n      resolved: route\n    }\n  }\n\n  addRoutes (routes: Array<RouteConfig>) {\n    this.matcher.addRoutes(routes)\n    if (this.history.current !== START) {\n      this.history.transitionTo(this.history.getCurrentLocation())\n    }\n  }\n}\n\nfunction registerHook (list: Array<any>, fn: Function): Function {\n  list.push(fn)\n  return () => {\n    const i = list.indexOf(fn)\n    if (i > -1) list.splice(i, 1)\n  }\n}\n\nfunction createHref (base: string, fullPath: string, mode) {\n  var path = mode === 'hash' ? '#' + fullPath : fullPath\n  return base ? cleanPath(base + '/' + path) : path\n}\n\n/* Vue.use安装插件时候需要暴露的install方法 */\nVueRouter.install = install\nVueRouter.version = '__VERSION__'\n\n/* 兼容用script标签引用的方法 */\nif (inBrowser && window.Vue) {\n  window.Vue.use(VueRouter)\n}\n"
  },
  {
    "path": "vue-router-src/install.js",
    "content": "import View from './components/view'\nimport Link from './components/link'\n\nexport let _Vue\n\n/* Vue.use安装插件时候需要暴露的install方法 */\nexport function install (Vue) {\n  \n  /* 判断是否已安装过 */\n  if (install.installed && _Vue === Vue) return\n  install.installed = true\n\n  /* 保存Vue实例 */\n  _Vue = Vue\n\n  /* 判断是否已定义 */\n  const isDef = v => v !== undefined\n\n  /* 通过registerRouteInstance方法注册router实例 */\n  const registerInstance = (vm, callVal) => {\n    let i = vm.$options._parentVnode\n    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {\n      i(vm, callVal)\n    }\n  }\n\n  /* 混淆进Vue实例，在boforeCreate与destroyed钩子上混淆 */\n  Vue.mixin({\n    /* boforeCreate钩子 */\n    beforeCreate () {\n      if (isDef(this.$options.router)) {\n        /* 在option上面存在router则代表是根组件 */\n        /* 保存跟组件vm */\n        this._routerRoot = this\n        /* 保存router */\n        this._router = this.$options.router\n        /* VueRouter对象的init方法 */\n        this._router.init(this)\n        /* Vue内部方法，为对象defineProperty上在变化时通知的属性 */\n        Vue.util.defineReactive(this, '_route', this._router.history.current)\n      } else {\n        /* 非根组件则直接从父组件中获取 */\n        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this\n      }\n      /* 通过registerRouteInstance方法注册router实例 */\n      registerInstance(this, this)\n    },\n    destroyed () {\n      registerInstance(this)\n    }\n  })\n\n  /* 在Vue的prototype上面绑定$router，这样可以在任意Vue对象中使用this.$router访问，同时经过Object.defineProperty，访问this.$router即访问this._routerRoot._router */\n  Object.defineProperty(Vue.prototype, '$router', {\n    get () { return this._routerRoot._router }\n  })\n\n  /* 以上同理，访问this.$route即访问this._routerRoot._route */\n  Object.defineProperty(Vue.prototype, '$route', {\n    get () { return this._routerRoot._route }\n  })\n\n  /* 注册touter-view以及router-link组件 */\n  Vue.component('RouterView', View)\n  Vue.component('RouterLink', Link)\n\n  /* 该对象保存了两个option合并的规则 */\n  const strats = Vue.config.optionMergeStrategies\n  // use the same hook merging strategy for route hooks\n  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created\n}\n"
  },
  {
    "path": "vue-router-src/util/async.js",
    "content": "/* @flow */\n\nexport function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {\n  const step = index => {\n    if (index >= queue.length) {\n      cb()\n    } else {\n      if (queue[index]) {\n        fn(queue[index], () => {\n          step(index + 1)\n        })\n      } else {\n        step(index + 1)\n      }\n    }\n  }\n  step(0)\n}\n"
  },
  {
    "path": "vue-router-src/util/dom.js",
    "content": "/* @flow */\n\nexport const inBrowser = typeof window !== 'undefined'\n"
  },
  {
    "path": "vue-router-src/util/location.js",
    "content": "/* @flow */\n\nimport type VueRouter from '../index'\nimport { parsePath, resolvePath } from './path'\nimport { resolveQuery } from './query'\nimport { fillParams } from './params'\nimport { warn } from './warn'\n\nexport function normalizeLocation (\n  raw: RawLocation,\n  current: ?Route,\n  append: ?boolean,\n  router: ?VueRouter\n): Location {\n  let next: Location = typeof raw === 'string' ? { path: raw } : raw\n  // named target\n  if (next.name || next._normalized) {\n    return next\n  }\n\n  // relative params\n  if (!next.path && next.params && current) {\n    next = assign({}, next)\n    next._normalized = true\n    const params: any = assign(assign({}, current.params), next.params)\n    if (current.name) {\n      next.name = current.name\n      next.params = params\n    } else if (current.matched.length) {\n      const rawPath = current.matched[current.matched.length - 1].path\n      next.path = fillParams(rawPath, params, `path ${current.path}`)\n    } else if (process.env.NODE_ENV !== 'production') {\n      warn(false, `relative params navigation requires a current route.`)\n    }\n    return next\n  }\n\n  const parsedPath = parsePath(next.path || '')\n  const basePath = (current && current.path) || '/'\n  const path = parsedPath.path\n    ? resolvePath(parsedPath.path, basePath, append || next.append)\n    : basePath\n\n  const query = resolveQuery(\n    parsedPath.query,\n    next.query,\n    router && router.options.parseQuery\n  )\n\n  let hash = next.hash || parsedPath.hash\n  if (hash && hash.charAt(0) !== '#') {\n    hash = `#${hash}`\n  }\n\n  return {\n    _normalized: true,\n    path,\n    query,\n    hash\n  }\n}\n\nfunction assign (a, b) {\n  for (const key in b) {\n    a[key] = b[key]\n  }\n  return a\n}\n"
  },
  {
    "path": "vue-router-src/util/params.js",
    "content": "/* @flow */\n\nimport { warn } from './warn'\nimport Regexp from 'path-to-regexp'\n\n// $flow-disable-line\nconst regexpCompileCache: {\n  [key: string]: Function\n} = Object.create(null)\n\nexport function fillParams (\n  path: string,\n  params: ?Object,\n  routeMsg: string\n): string {\n  try {\n    const filler =\n      regexpCompileCache[path] ||\n      (regexpCompileCache[path] = Regexp.compile(path))\n    return filler(params || {}, { pretty: true })\n  } catch (e) {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(false, `missing param for ${routeMsg}: ${e.message}`)\n    }\n    return ''\n  }\n}\n"
  },
  {
    "path": "vue-router-src/util/path.js",
    "content": "/* @flow */\n\nexport function resolvePath (\n  relative: string,\n  base: string,\n  append?: boolean\n): string {\n  const firstChar = relative.charAt(0)\n  if (firstChar === '/') {\n    return relative\n  }\n\n  if (firstChar === '?' || firstChar === '#') {\n    return base + relative\n  }\n\n  const stack = base.split('/')\n\n  // remove trailing segment if:\n  // - not appending\n  // - appending to trailing slash (last segment is empty)\n  if (!append || !stack[stack.length - 1]) {\n    stack.pop()\n  }\n\n  // resolve relative path\n  const segments = relative.replace(/^\\//, '').split('/')\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i]\n    if (segment === '..') {\n      stack.pop()\n    } else if (segment !== '.') {\n      stack.push(segment)\n    }\n  }\n\n  // ensure leading slash\n  if (stack[0] !== '') {\n    stack.unshift('')\n  }\n\n  return stack.join('/')\n}\n\nexport function parsePath (path: string): {\n  path: string;\n  query: string;\n  hash: string;\n} {\n  let hash = ''\n  let query = ''\n\n  const hashIndex = path.indexOf('#')\n  if (hashIndex >= 0) {\n    hash = path.slice(hashIndex)\n    path = path.slice(0, hashIndex)\n  }\n\n  const queryIndex = path.indexOf('?')\n  if (queryIndex >= 0) {\n    query = path.slice(queryIndex + 1)\n    path = path.slice(0, queryIndex)\n  }\n\n  return {\n    path,\n    query,\n    hash\n  }\n}\n\nexport function cleanPath (path: string): string {\n  return path.replace(/\\/\\//g, '/')\n}\n"
  },
  {
    "path": "vue-router-src/util/push-state.js",
    "content": "/* @flow */\n\nimport { inBrowser } from './dom'\nimport { saveScrollPosition } from './scroll'\n\nexport const supportsPushState = inBrowser && (function () {\n  const ua = window.navigator.userAgent\n\n  if (\n    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&\n    ua.indexOf('Mobile Safari') !== -1 &&\n    ua.indexOf('Chrome') === -1 &&\n    ua.indexOf('Windows Phone') === -1\n  ) {\n    return false\n  }\n\n  return window.history && 'pushState' in window.history\n})()\n\n// use User Timing api (if present) for more accurate key precision\nconst Time = inBrowser && window.performance && window.performance.now\n  ? window.performance\n  : Date\n\nlet _key: string = genKey()\n\nfunction genKey (): string {\n  return Time.now().toFixed(3)\n}\n\nexport function getStateKey () {\n  return _key\n}\n\nexport function setStateKey (key: string) {\n  _key = key\n}\n\nexport function pushState (url?: string, replace?: boolean) {\n  saveScrollPosition()\n  // try...catch the pushState call to get around Safari\n  // DOM Exception 18 where it limits to 100 pushState calls\n  const history = window.history\n  try {\n    if (replace) {\n      history.replaceState({ key: _key }, '', url)\n    } else {\n      _key = genKey()\n      history.pushState({ key: _key }, '', url)\n    }\n  } catch (e) {\n    window.location[replace ? 'replace' : 'assign'](url)\n  }\n}\n\nexport function replaceState (url?: string) {\n  pushState(url, true)\n}\n"
  },
  {
    "path": "vue-router-src/util/query.js",
    "content": "/* @flow */\n\nimport { warn } from './warn'\n\nconst encodeReserveRE = /[!'()*]/g\nconst encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16)\nconst commaRE = /%2C/g\n\n// fixed encodeURIComponent which is more conformant to RFC3986:\n// - escapes [!'()*]\n// - preserve commas\nconst encode = str => encodeURIComponent(str)\n  .replace(encodeReserveRE, encodeReserveReplacer)\n  .replace(commaRE, ',')\n\nconst decode = decodeURIComponent\n\nexport function resolveQuery (\n  query: ?string,\n  extraQuery: Dictionary<string> = {},\n  _parseQuery: ?Function\n): Dictionary<string> {\n  const parse = _parseQuery || parseQuery\n  let parsedQuery\n  try {\n    parsedQuery = parse(query || '')\n  } catch (e) {\n    process.env.NODE_ENV !== 'production' && warn(false, e.message)\n    parsedQuery = {}\n  }\n  for (const key in extraQuery) {\n    parsedQuery[key] = extraQuery[key]\n  }\n  return parsedQuery\n}\n\nfunction parseQuery (query: string): Dictionary<string> {\n  const res = {}\n\n  query = query.trim().replace(/^(\\?|#|&)/, '')\n\n  if (!query) {\n    return res\n  }\n\n  query.split('&').forEach(param => {\n    const parts = param.replace(/\\+/g, ' ').split('=')\n    const key = decode(parts.shift())\n    const val = parts.length > 0\n      ? decode(parts.join('='))\n      : null\n\n    if (res[key] === undefined) {\n      res[key] = val\n    } else if (Array.isArray(res[key])) {\n      res[key].push(val)\n    } else {\n      res[key] = [res[key], val]\n    }\n  })\n\n  return res\n}\n\nexport function stringifyQuery (obj: Dictionary<string>): string {\n  const res = obj ? Object.keys(obj).map(key => {\n    const val = obj[key]\n\n    if (val === undefined) {\n      return ''\n    }\n\n    if (val === null) {\n      return encode(key)\n    }\n\n    if (Array.isArray(val)) {\n      const result = []\n      val.forEach(val2 => {\n        if (val2 === undefined) {\n          return\n        }\n        if (val2 === null) {\n          result.push(encode(key))\n        } else {\n          result.push(encode(key) + '=' + encode(val2))\n        }\n      })\n      return result.join('&')\n    }\n\n    return encode(key) + '=' + encode(val)\n  }).filter(x => x.length > 0).join('&') : null\n  return res ? `?${res}` : ''\n}\n"
  },
  {
    "path": "vue-router-src/util/resolve-components.js",
    "content": "/* @flow */\n\nimport { _Vue } from '../install'\nimport { warn, isError } from './warn'\n\nexport function resolveAsyncComponents (matched: Array<RouteRecord>): Function {\n  return (to, from, next) => {\n    let hasAsync = false\n    let pending = 0\n    let error = null\n\n    flatMapComponents(matched, (def, _, match, key) => {\n      // if it's a function and doesn't have cid attached,\n      // assume it's an async component resolve function.\n      // we are not using Vue's default async resolving mechanism because\n      // we want to halt the navigation until the incoming component has been\n      // resolved.\n      if (typeof def === 'function' && def.cid === undefined) {\n        hasAsync = true\n        pending++\n\n        const resolve = once(resolvedDef => {\n          if (isESModule(resolvedDef)) {\n            resolvedDef = resolvedDef.default\n          }\n          // save resolved on async factory in case it's used elsewhere\n          def.resolved = typeof resolvedDef === 'function'\n            ? resolvedDef\n            : _Vue.extend(resolvedDef)\n          match.components[key] = resolvedDef\n          pending--\n          if (pending <= 0) {\n            next()\n          }\n        })\n\n        const reject = once(reason => {\n          const msg = `Failed to resolve async component ${key}: ${reason}`\n          process.env.NODE_ENV !== 'production' && warn(false, msg)\n          if (!error) {\n            error = isError(reason)\n              ? reason\n              : new Error(msg)\n            next(error)\n          }\n        })\n\n        let res\n        try {\n          res = def(resolve, reject)\n        } catch (e) {\n          reject(e)\n        }\n        if (res) {\n          if (typeof res.then === 'function') {\n            res.then(resolve, reject)\n          } else {\n            // new syntax in Vue 2.3\n            const comp = res.component\n            if (comp && typeof comp.then === 'function') {\n              comp.then(resolve, reject)\n            }\n          }\n        }\n      }\n    })\n\n    if (!hasAsync) next()\n  }\n}\n\nexport function flatMapComponents (\n  matched: Array<RouteRecord>,\n  fn: Function\n): Array<?Function> {\n  return flatten(matched.map(m => {\n    return Object.keys(m.components).map(key => fn(\n      m.components[key],\n      m.instances[key],\n      m, key\n    ))\n  }))\n}\n\nexport function flatten (arr: Array<any>): Array<any> {\n  return Array.prototype.concat.apply([], arr)\n}\n\nconst hasSymbol =\n  typeof Symbol === 'function' &&\n  typeof Symbol.toStringTag === 'symbol'\n\nfunction isESModule (obj) {\n  return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')\n}\n\n// in Webpack 2, require.ensure now also returns a Promise\n// so the resolve/reject functions may get called an extra time\n// if the user uses an arrow function shorthand that happens to\n// return that Promise.\nfunction once (fn) {\n  let called = false\n  return function (...args) {\n    if (called) return\n    called = true\n    return fn.apply(this, args)\n  }\n}\n"
  },
  {
    "path": "vue-router-src/util/route.js",
    "content": "/* @flow */\n\nimport type VueRouter from '../index'\nimport { stringifyQuery } from './query'\n\nconst trailingSlashRE = /\\/?$/\n\nexport function createRoute (\n  record: ?RouteRecord,\n  location: Location,\n  redirectedFrom?: ?Location,\n  router?: VueRouter\n): Route {\n  const stringifyQuery = router && router.options.stringifyQuery\n\n  let query: any = location.query || {}\n  try {\n    query = clone(query)\n  } catch (e) {}\n\n  const route: Route = {\n    name: location.name || (record && record.name),\n    meta: (record && record.meta) || {},\n    path: location.path || '/',\n    hash: location.hash || '',\n    query,\n    params: location.params || {},\n    fullPath: getFullPath(location, stringifyQuery),\n    matched: record ? formatMatch(record) : []\n  }\n  if (redirectedFrom) {\n    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)\n  }\n  return Object.freeze(route)\n}\n\nfunction clone (value) {\n  if (Array.isArray(value)) {\n    return value.map(clone)\n  } else if (value && typeof value === 'object') {\n    const res = {}\n    for (const key in value) {\n      res[key] = clone(value[key])\n    }\n    return res\n  } else {\n    return value\n  }\n}\n\n// the starting route that represents the initial state\n/* 起始路由 */\nexport const START = createRoute(null, {\n  path: '/'\n})\n\nfunction formatMatch (record: ?RouteRecord): Array<RouteRecord> {\n  const res = []\n  while (record) {\n    res.unshift(record)\n    record = record.parent\n  }\n  return res\n}\n\nfunction getFullPath (\n  { path, query = {}, hash = '' },\n  _stringifyQuery\n): string {\n  const stringify = _stringifyQuery || stringifyQuery\n  return (path || '/') + stringify(query) + hash\n}\n\nexport function isSameRoute (a: Route, b: ?Route): boolean {\n  if (b === START) {\n    return a === b\n  } else if (!b) {\n    return false\n  } else if (a.path && b.path) {\n    return (\n      a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&\n      a.hash === b.hash &&\n      isObjectEqual(a.query, b.query)\n    )\n  } else if (a.name && b.name) {\n    return (\n      a.name === b.name &&\n      a.hash === b.hash &&\n      isObjectEqual(a.query, b.query) &&\n      isObjectEqual(a.params, b.params)\n    )\n  } else {\n    return false\n  }\n}\n\nfunction isObjectEqual (a = {}, b = {}): boolean {\n  // handle null value #1566\n  if (!a || !b) return a === b\n  const aKeys = Object.keys(a)\n  const bKeys = Object.keys(b)\n  if (aKeys.length !== bKeys.length) {\n    return false\n  }\n  return aKeys.every(key => {\n    const aVal = a[key]\n    const bVal = b[key]\n    // check nested equality\n    if (typeof aVal === 'object' && typeof bVal === 'object') {\n      return isObjectEqual(aVal, bVal)\n    }\n    return String(aVal) === String(bVal)\n  })\n}\n\nexport function isIncludedRoute (current: Route, target: Route): boolean {\n  return (\n    current.path.replace(trailingSlashRE, '/').indexOf(\n      target.path.replace(trailingSlashRE, '/')\n    ) === 0 &&\n    (!target.hash || current.hash === target.hash) &&\n    queryIncludes(current.query, target.query)\n  )\n}\n\nfunction queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {\n  for (const key in target) {\n    if (!(key in current)) {\n      return false\n    }\n  }\n  return true\n}\n"
  },
  {
    "path": "vue-router-src/util/scroll.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { assert } from './warn'\nimport { getStateKey, setStateKey } from './push-state'\n\nconst positionStore = Object.create(null)\n\nexport function setupScroll () {\n  // Fix for #1585 for Firefox\n  window.history.replaceState({ key: getStateKey() }, '')\n  window.addEventListener('popstate', e => {\n    saveScrollPosition()\n    if (e.state && e.state.key) {\n      setStateKey(e.state.key)\n    }\n  })\n}\n\nexport function handleScroll (\n  router: Router,\n  to: Route,\n  from: Route,\n  isPop: boolean\n) {\n  if (!router.app) {\n    return\n  }\n\n  const behavior = router.options.scrollBehavior\n  if (!behavior) {\n    return\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(typeof behavior === 'function', `scrollBehavior must be a function`)\n  }\n\n  // wait until re-render finishes before scrolling\n  router.app.$nextTick(() => {\n    const position = getScrollPosition()\n    const shouldScroll = behavior.call(router, to, from, isPop ? position : null)\n\n    if (!shouldScroll) {\n      return\n    }\n\n    if (typeof shouldScroll.then === 'function') {\n      shouldScroll.then(shouldScroll => {\n        scrollToPosition((shouldScroll: any), position)\n      }).catch(err => {\n        if (process.env.NODE_ENV !== 'production') {\n          assert(false, err.toString())\n        }\n      })\n    } else {\n      scrollToPosition(shouldScroll, position)\n    }\n  })\n}\n\nexport function saveScrollPosition () {\n  const key = getStateKey()\n  if (key) {\n    positionStore[key] = {\n      x: window.pageXOffset,\n      y: window.pageYOffset\n    }\n  }\n}\n\nfunction getScrollPosition (): ?Object {\n  const key = getStateKey()\n  if (key) {\n    return positionStore[key]\n  }\n}\n\nfunction getElementPosition (el: Element, offset: Object): Object {\n  const docEl: any = document.documentElement\n  const docRect = docEl.getBoundingClientRect()\n  const elRect = el.getBoundingClientRect()\n  return {\n    x: elRect.left - docRect.left - offset.x,\n    y: elRect.top - docRect.top - offset.y\n  }\n}\n\nfunction isValidPosition (obj: Object): boolean {\n  return isNumber(obj.x) || isNumber(obj.y)\n}\n\nfunction normalizePosition (obj: Object): Object {\n  return {\n    x: isNumber(obj.x) ? obj.x : window.pageXOffset,\n    y: isNumber(obj.y) ? obj.y : window.pageYOffset\n  }\n}\n\nfunction normalizeOffset (obj: Object): Object {\n  return {\n    x: isNumber(obj.x) ? obj.x : 0,\n    y: isNumber(obj.y) ? obj.y : 0\n  }\n}\n\nfunction isNumber (v: any): boolean {\n  return typeof v === 'number'\n}\n\nfunction scrollToPosition (shouldScroll, position) {\n  const isObject = typeof shouldScroll === 'object'\n  if (isObject && typeof shouldScroll.selector === 'string') {\n    const el = document.querySelector(shouldScroll.selector)\n    if (el) {\n      let offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {}\n      offset = normalizeOffset(offset)\n      position = getElementPosition(el, offset)\n    } else if (isValidPosition(shouldScroll)) {\n      position = normalizePosition(shouldScroll)\n    }\n  } else if (isObject && isValidPosition(shouldScroll)) {\n    position = normalizePosition(shouldScroll)\n  }\n\n  if (position) {\n    window.scrollTo(position.x, position.y)\n  }\n}\n"
  },
  {
    "path": "vue-router-src/util/warn.js",
    "content": "/* @flow */\n\nexport function assert (condition: any, message: string) {\n  if (!condition) {\n    throw new Error(`[vue-router] ${message}`)\n  }\n}\n\nexport function warn (condition: any, message: string) {\n  if (process.env.NODE_ENV !== 'production' && !condition) {\n    typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)\n  }\n}\n\nexport function isError (err: any): boolean {\n  return Object.prototype.toString.call(err).indexOf('Error') > -1\n}\n"
  },
  {
    "path": "vue-src/compiler/codegen/events.js",
    "content": "/* @flow */\n\nconst fnExpRE = /^\\s*([\\w$_]+|\\([^)]*?\\))\\s*=>|^function\\s*\\(/\nconst simplePathRE = /^\\s*[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*|\\['.*?']|\\[\".*?\"]|\\[\\d+]|\\[[A-Za-z_$][\\w$]*])*\\s*$/\n\n// keyCode aliases\nconst keyCodes: { [key: string]: number | Array<number> } = {\n  esc: 27,\n  tab: 9,\n  enter: 13,\n  space: 32,\n  up: 38,\n  left: 37,\n  right: 39,\n  down: 40,\n  'delete': [8, 46]\n}\n/*Github:https://github.com/answershuto*/\n// #4868: modifiers that prevent the execution of the listener\n// need to explicitly return null so that we can determine whether to remove\n// the listener for .once\nconst genGuard = condition => `if(${condition})return null;`\n\nconst modifierCode: { [key: string]: string } = {\n  stop: '$event.stopPropagation();',\n  prevent: '$event.preventDefault();',\n  self: genGuard(`$event.target !== $event.currentTarget`),\n  ctrl: genGuard(`!$event.ctrlKey`),\n  shift: genGuard(`!$event.shiftKey`),\n  alt: genGuard(`!$event.altKey`),\n  meta: genGuard(`!$event.metaKey`),\n  left: genGuard(`'button' in $event && $event.button !== 0`),\n  middle: genGuard(`'button' in $event && $event.button !== 1`),\n  right: genGuard(`'button' in $event && $event.button !== 2`)\n}\n\nexport function genHandlers (\n  events: ASTElementHandlers,\n  native: boolean,\n  warn: Function\n): string {\n  let res = native ? 'nativeOn:{' : 'on:{'\n  for (const name in events) {\n    const handler = events[name]\n    // #5330: warn click.right, since right clicks do not actually fire click events.\n    if (process.env.NODE_ENV !== 'production' &&\n        name === 'click' &&\n        handler && handler.modifiers && handler.modifiers.right\n      ) {\n      warn(\n        `Use \"contextmenu\" instead of \"click.right\" since right clicks ` +\n        `do not actually fire \"click\" events.`\n      )\n    }\n    res += `\"${name}\":${genHandler(name, handler)},`\n  }\n  return res.slice(0, -1) + '}'\n}\n\nfunction genHandler (\n  name: string,\n  handler: ASTElementHandler | Array<ASTElementHandler>\n): string {\n  if (!handler) {\n    return 'function(){}'\n  }\n\n  if (Array.isArray(handler)) {\n    return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`\n  }\n\n  const isMethodPath = simplePathRE.test(handler.value)\n  const isFunctionExpression = fnExpRE.test(handler.value)\n\n  if (!handler.modifiers) {\n    return isMethodPath || isFunctionExpression\n      ? handler.value\n      : `function($event){${handler.value}}` // inline statement\n  } else {\n    let code = ''\n    let genModifierCode = ''\n    const keys = []\n    for (const key in handler.modifiers) {\n      if (modifierCode[key]) {\n        genModifierCode += modifierCode[key]\n        // left/right\n        if (keyCodes[key]) {\n          keys.push(key)\n        }\n      } else {\n        keys.push(key)\n      }\n    }\n    if (keys.length) {\n      code += genKeyFilter(keys)\n    }\n    // Make sure modifiers like prevent and stop get executed after key filtering\n    if (genModifierCode) {\n      code += genModifierCode\n    }\n    const handlerCode = isMethodPath\n      ? handler.value + '($event)'\n      : isFunctionExpression\n        ? `(${handler.value})($event)`\n        : handler.value\n    return `function($event){${code}${handlerCode}}`\n  }\n}\n\nfunction genKeyFilter (keys: Array<string>): string {\n  return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;`\n}\n\nfunction genFilterCode (key: string): string {\n  const keyVal = parseInt(key, 10)\n  if (keyVal) {\n    return `$event.keyCode!==${keyVal}`\n  }\n  const alias = keyCodes[key]\n  return `_k($event.keyCode,${JSON.stringify(key)}${alias ? ',' + JSON.stringify(alias) : ''})`\n}\n"
  },
  {
    "path": "vue-src/compiler/codegen/index.js",
    "content": "/* @flow */\n\nimport { genHandlers } from './events'\nimport { baseWarn, pluckModuleFunction } from '../helpers'\nimport baseDirectives from '../directives/index'\nimport { camelize, no } from 'shared/util'\n\ntype TransformFunction = (el: ASTElement, code: string) => string;\ntype DataGenFunction = (el: ASTElement) => string;\ntype DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean;\n\n// configurable state\nlet warn\nlet transforms: Array<TransformFunction>\nlet dataGenFns: Array<DataGenFunction>\nlet platformDirectives\nlet isPlatformReservedTag\nlet staticRenderFns\nlet onceCount\nlet currentOptions\n\n/*将AST语法树转化成render以及staticRenderFns的字符串*/\nexport function generate (\n  ast: ASTElement | void,\n  options: CompilerOptions\n): {\n  render: string,\n  staticRenderFns: Array<string>\n} {\n  // save previous staticRenderFns so generate calls can be nested\n  const prevStaticRenderFns: Array<string> = staticRenderFns\n  const currentStaticRenderFns: Array<string> = staticRenderFns = []\n  const prevOnceCount = onceCount\n  onceCount = 0\n  currentOptions = options\n  warn = options.warn || baseWarn\n  transforms = pluckModuleFunction(options.modules, 'transformCode')\n  dataGenFns = pluckModuleFunction(options.modules, 'genData')\n  platformDirectives = options.directives || {}\n  isPlatformReservedTag = options.isReservedTag || no\n  const code = ast ? genElement(ast) : '_c(\"div\")'\n  staticRenderFns = prevStaticRenderFns\n  onceCount = prevOnceCount\n  return {\n    render: `with(this){return ${code}}`,\n    staticRenderFns: currentStaticRenderFns\n  }\n}\n\n/*处理element，分别处理static静态节点、v-once、v-for、v-if、template、slot以及组件或元素*/\nfunction genElement (el: ASTElement): string {\n  if (el.staticRoot && !el.staticProcessed) {\n    /*处理static静态节点*/\n    return genStatic(el)\n  } else if (el.once && !el.onceProcessed) {\n    /*处理v-once*/\n    return genOnce(el)\n  } else if (el.for && !el.forProcessed) {\n    /*处理v-for*/\n    return genFor(el)\n  } else if (el.if && !el.ifProcessed) {\n    /*处理v-if*/\n    return genIf(el)\n  } else if (el.tag === 'template' && !el.slotTarget) {\n    /*处理template*/\n    return genChildren(el) || 'void 0'\n  } else if (el.tag === 'slot') {\n    /*处理slot*/\n    return genSlot(el)\n  } else {\n    // component or element\n    /*处理组件或元素*/\n    let code\n    if (el.component) {\n      code = genComponent(el.component, el)\n    } else {\n      const data = el.plain ? undefined : genData(el)\n\n      const children = el.inlineTemplate ? null : genChildren(el, true)\n      code = `_c('${el.tag}'${\n        data ? `,${data}` : '' // data\n      }${\n        children ? `,${children}` : '' // children\n      })`\n    }\n    // module transforms\n    for (let i = 0; i < transforms.length; i++) {\n      code = transforms[i](el, code)\n    }\n    return code\n  }\n}\n\n// hoist static sub-trees out\n/*处理static静态节点*/\nfunction genStatic (el: ASTElement): string {\n  /*处理过的标记位*/\n  el.staticProcessed = true\n  staticRenderFns.push(`with(this){return ${genElement(el)}}`)\n  return `_m(${staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`\n}\n\n// v-once\n/*处理v-once*/\nfunction genOnce (el: ASTElement): string {\n  /*处理过的标记位*/\n  el.onceProcessed = true\n  if (el.if && !el.ifProcessed) {\n    /*同时还存在v-if的时候需要处理v-if*/\n    return genIf(el)\n  } else if (el.staticInFor) {\n    /*\n      staticInFor标记static的或者有v-once指令同时处于for循环中的节点。\n      此时表示同时存在于for循环中\n      */\n    let key = ''\n    let parent = el.parent\n    /*向上逐级寻找所处的for循环*/\n    while (parent) {\n      if (parent.for) {\n        key = parent.key\n        break\n      }\n      parent = parent.parent\n    }\n    if (!key) {\n      /*如果v-once出现在for循环中，那必须要给设置v-for的element设置key*/\n      process.env.NODE_ENV !== 'production' && warn(\n        `v-once can only be used inside v-for that is keyed. `\n      )\n      return genElement(el)\n    }\n    return `_o(${genElement(el)},${onceCount++}${key ? `,${key}` : ``})`\n  } else {\n    return genStatic(el)\n  }\n}\n\n/*处理v-if*/\nfunction genIf (el: any): string {\n  /*标记位*/\n  el.ifProcessed = true // avoid recursion\n  return genIfConditions(el.ifConditions.slice())\n}\n\n/*处理if条件*/\nfunction genIfConditions (conditions: ASTIfConditions): string {\n  /*表达式不存在*/\n  if (!conditions.length) {\n    return '_e()'\n  }\n\n  const condition = conditions.shift()\n  if (condition.exp) {\n    return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`\n  } else {\n    return `${genTernaryExp(condition.block)}`\n  }\n\n  // v-if with v-once should generate code like (a)?_m(0):_m(1)\n  /*v-if与v-once同时存在的时候应该使用三元运算符，譬如说(a)?_m(0):_m(1)*/\n  function genTernaryExp (el) {\n    return el.once ? genOnce(el) : genElement(el)\n  }\n}\n\n/*处理v-for循环*/\nfunction genFor (el: any): string {\n  const exp = el.for\n  const alias = el.alias\n  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''\n  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''\n\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key\n  ) {\n    warn(\n      `<${el.tag} v-for=\"${alias} in ${exp}\">: component lists rendered with ` +\n      `v-for should have explicit keys. ` +\n      `See https://vuejs.org/guide/list.html#key for more info.`,\n      true /* tip */\n    )\n  }\n\n  /*标记位，避免递归*/\n  el.forProcessed = true // avoid recursion\n  return `_l((${exp}),` +\n    `function(${alias}${iterator1}${iterator2}){` +\n      `return ${genElement(el)}` +\n    '})'\n}\n\nfunction genData (el: ASTElement): string {\n  let data = '{'\n\n  // directives first.\n  // directives may mutate the el's other properties before they are generated.\n  const dirs = genDirectives(el)\n  if (dirs) data += dirs + ','\n\n  // key\n  if (el.key) {\n    data += `key:${el.key},`\n  }\n  // ref\n  if (el.ref) {\n    data += `ref:${el.ref},`\n  }\n  if (el.refInFor) {\n    data += `refInFor:true,`\n  }\n  // pre\n  if (el.pre) {\n    data += `pre:true,`\n  }\n  // record original tag name for components using \"is\" attribute\n  if (el.component) {\n    data += `tag:\"${el.tag}\",`\n  }\n  // module data generation functions\n  for (let i = 0; i < dataGenFns.length; i++) {\n    data += dataGenFns[i](el)\n  }\n  // attributes\n  if (el.attrs) {\n    data += `attrs:{${genProps(el.attrs)}},`\n  }\n  // DOM props\n  if (el.props) {\n    data += `domProps:{${genProps(el.props)}},`\n  }\n  // event handlers\n  if (el.events) {\n    data += `${genHandlers(el.events, false, warn)},`\n  }\n  if (el.nativeEvents) {\n    data += `${genHandlers(el.nativeEvents, true, warn)},`\n  }\n  // slot target\n  if (el.slotTarget) {\n    data += `slot:${el.slotTarget},`\n  }\n  // scoped slots\n  if (el.scopedSlots) {\n    data += `${genScopedSlots(el.scopedSlots)},`\n  }\n  // component v-model\n  if (el.model) {\n    data += `model:{value:${\n      el.model.value\n    },callback:${\n      el.model.callback\n    },expression:${\n      el.model.expression\n    }},`\n  }\n  // inline-template\n  if (el.inlineTemplate) {\n    const inlineTemplate = genInlineTemplate(el)\n    if (inlineTemplate) {\n      data += `${inlineTemplate},`\n    }\n  }\n  data = data.replace(/,$/, '') + '}'\n  // v-bind data wrap\n  if (el.wrapData) {\n    data = el.wrapData(data)\n  }\n  return data\n}\n\nfunction genDirectives (el: ASTElement): string | void {\n  const dirs = el.directives\n  if (!dirs) return\n  let res = 'directives:['\n  let hasRuntime = false\n  let i, l, dir, needRuntime\n  for (i = 0, l = dirs.length; i < l; i++) {\n    dir = dirs[i]\n    needRuntime = true\n    const gen: DirectiveFunction = platformDirectives[dir.name] || baseDirectives[dir.name]\n    if (gen) {\n      // compile-time directive that manipulates AST.\n      // returns true if it also needs a runtime counterpart.\n      needRuntime = !!gen(el, dir, warn)\n    }\n    if (needRuntime) {\n      hasRuntime = true\n      res += `{name:\"${dir.name}\",rawName:\"${dir.rawName}\"${\n        dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''\n      }${\n        dir.arg ? `,arg:\"${dir.arg}\"` : ''\n      }${\n        dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''\n      }},`\n    }\n  }\n  if (hasRuntime) {\n    return res.slice(0, -1) + ']'\n  }\n}\n\nfunction genInlineTemplate (el: ASTElement): ?string {\n  const ast = el.children[0]\n  if (process.env.NODE_ENV !== 'production' && (\n    el.children.length > 1 || ast.type !== 1\n  )) {\n    warn('Inline-template components must have exactly one child element.')\n  }\n  if (ast.type === 1) {\n    const inlineRenderFns = generate(ast, currentOptions)\n    return `inlineTemplate:{render:function(){${\n      inlineRenderFns.render\n    }},staticRenderFns:[${\n      inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')\n    }]}`\n  }\n}\n\nfunction genScopedSlots (slots: { [key: string]: ASTElement }): string {\n  return `scopedSlots:_u([${\n    Object.keys(slots).map(key => genScopedSlot(key, slots[key])).join(',')\n  }])`\n}\n\nfunction genScopedSlot (key: string, el: ASTElement) {\n  return `[${key},function(${String(el.attrsMap.scope)}){` +\n    `return ${el.tag === 'template'\n      ? genChildren(el) || 'void 0'\n      : genElement(el)\n  }}]`\n}\n\n/*处理chidren*/\nfunction genChildren (el: ASTElement, checkSkip?: boolean): string | void {\n  const children = el.children\n  if (children.length) {\n    const el: any = children[0]\n    // optimize single v-for\n    /*优化单个v-for*/\n    if (children.length === 1 &&\n        el.for &&\n        el.tag !== 'template' &&\n        el.tag !== 'slot') {\n      return genElement(el)\n    }\n    const normalizationType = checkSkip ? getNormalizationType(children) : 0\n    /*用genNode处理children，内部有子节点也会继续遍历*/\n    return `[${children.map(genNode).join(',')}]${\n      normalizationType ? `,${normalizationType}` : ''\n    }`\n  }\n}\n\n// determine the normalization needed for the children array.\n// 0: no normalization needed\n// 1: simple normalization needed (possible 1-level deep nested array)\n// 2: full normalization needed\n/*\n  得到子数组所需的序列化类型\n  0:不需要序列化\n  1:需要做简单的序列化（可能是一级深层嵌套数组）\n  2:需要完全序列化\n*/\nfunction getNormalizationType (children: Array<ASTNode>): number {\n  let res = 0\n  for (let i = 0; i < children.length; i++) {\n    const el: ASTNode = children[i]\n    /*当不是元素节点的时候直接continue*/\n    if (el.type !== 1) {\n      continue\n    }\n    /*if条件中是存在满足needsNormalization条件的*/\n    if (needsNormalization(el) ||\n        (el.ifConditions && el.ifConditions.some(c => needsNormalization(c.block)))) {\n      res = 2\n      break\n    }\n    /*if条件中有满足有可能是组件的返回1*/\n    if (maybeComponent(el) ||\n        (el.ifConditions && el.ifConditions.some(c => maybeComponent(c.block)))) {\n      res = 1\n    }\n  }\n  return res\n}\n\n/*是否需要序列化（元素不是slot标签或者templete，同时不存在于v-for循环中）*/\nfunction needsNormalization (el: ASTElement): boolean {\n  return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'\n}\n\n/*有可能是组件（判断不是平台保留标签，就是组件，当然也有可能是一个乱七八糟的标签并不在compoments中，这个后面在compoments中寻找组件的地方会处理）*/\nfunction maybeComponent (el: ASTElement): boolean {\n  return !isPlatformReservedTag(el.tag)\n}\n\n/*处理节点*/\nfunction genNode (node: ASTNode): string {\n  if (node.type === 1) {\n    return genElement(node)\n  } else {\n    return genText(node)\n  }\n}\n\n/*处理文本*/\nfunction genText (text: ASTText | ASTExpression): string {\n  return `_v(${text.type === 2\n    ? text.expression // no need for () because already wrapped in _s()\n    : transformSpecialNewlines(JSON.stringify(text.text))\n  })`\n}\n/*Github:https://github.com/answershuto*/\nfunction genSlot (el: ASTElement): string {\n  /*不存在slotName的时候改slot的name为default*/\n  const slotName = el.slotName || '\"default\"'\n  const children = genChildren(el)\n  let res = `_t(${slotName}${children ? `,${children}` : ''}`\n  const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`\n  const bind = el.attrsMap['v-bind']\n  if ((attrs || bind) && !children) {\n    res += `,null`\n  }\n  if (attrs) {\n    res += `,${attrs}`\n  }\n  if (bind) {\n    res += `${attrs ? '' : ',null'},${bind}`\n  }\n  return res + ')'\n}\n\n// componentName is el.component, take it as argument to shun flow's pessimistic refinement\n/*处理compoment*/\nfunction genComponent (componentName: string, el: ASTElement): string {\n  const children = el.inlineTemplate ? null : genChildren(el, true)\n  return `_c(${componentName},${genData(el)}${\n    children ? `,${children}` : ''\n  })`\n}\n\nfunction genProps (props: Array<{ name: string, value: string }>): string {\n  let res = ''\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i]\n    res += `\"${prop.name}\":${transformSpecialNewlines(prop.value)},`\n  }\n  return res.slice(0, -1)\n}\n\n// #3895, #4268\nfunction transformSpecialNewlines (text: string): string {\n  return text\n    .replace(/\\u2028/g, '\\\\u2028')\n    .replace(/\\u2029/g, '\\\\u2029')\n}\n"
  },
  {
    "path": "vue-src/compiler/directives/bind.js",
    "content": "/* @flow */\n/*Github:https://github.com/answershuto*/\nexport default function bind (el: ASTElement, dir: ASTDirective) {\n  el.wrapData = (code: string) => {\n    return `_b(${code},'${el.tag}',${dir.value}${\n      dir.modifiers && dir.modifiers.prop ? ',true' : ''\n    })`\n  }\n}\n"
  },
  {
    "path": "vue-src/compiler/directives/index.js",
    "content": "/* @flow */\n\nimport bind from './bind'\nimport { noop } from 'shared/util'\n/*Github:https://github.com/answershuto*/\nexport default {\n  bind,\n  cloak: noop\n}\n"
  },
  {
    "path": "vue-src/compiler/directives/model.js",
    "content": "/* @flow */\n\n/**\n * Cross-platform code generation for component v-model\n */\nexport function genComponentModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const { number, trim } = modifiers || {}\n\n  const baseValueExpression = '$$v'\n  let valueExpression = baseValueExpression\n  if (trim) {\n    valueExpression =\n      `(typeof ${baseValueExpression} === 'string'` +\n        `? ${baseValueExpression}.trim()` +\n        `: ${baseValueExpression})`\n  }\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n  const assignment = genAssignmentCode(value, valueExpression)\n\n  el.model = {\n    value: `(${value})`,\n    expression: `\"${value}\"`,\n    callback: `function (${baseValueExpression}) {${assignment}}`\n  }\n}\n/*Github:https://github.com/answershuto*/\n/**\n * Cross-platform codegen helper for generating v-model value assignment code.\n */\nexport function genAssignmentCode (\n  value: string,\n  assignment: string\n): string {\n  const modelRs = parseModel(value)\n  if (modelRs.idx === null) {\n    return `${value}=${assignment}`\n  } else {\n    return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +\n      `if (!Array.isArray($$exp)){` +\n        `${value}=${assignment}}` +\n      `else{$$exp.splice($$idx, 1, ${assignment})}`\n  }\n}\n\n/**\n * parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)\n *\n * for loop possible cases:\n *\n * - test\n * - test[idx]\n * - test[test1[idx]]\n * - test[\"a\"][idx]\n * - xxx.test[a[a].test1[idx]]\n * - test.xxx.a[\"asa\"][test1[idx]]\n *\n */\n\nlet len, str, chr, index, expressionPos, expressionEndPos\n\nexport function parseModel (val: string): Object {\n  str = val\n  len = str.length\n  index = expressionPos = expressionEndPos = 0\n\n  if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {\n    return {\n      exp: val,\n      idx: null\n    }\n  }\n\n  while (!eof()) {\n    chr = next()\n    /* istanbul ignore if */\n    if (isStringStart(chr)) {\n      parseString(chr)\n    } else if (chr === 0x5B) {\n      parseBracket(chr)\n    }\n  }\n\n  return {\n    exp: val.substring(0, expressionPos),\n    idx: val.substring(expressionPos + 1, expressionEndPos)\n  }\n}\n\nfunction next (): number {\n  return str.charCodeAt(++index)\n}\n\nfunction eof (): boolean {\n  return index >= len\n}\n\nfunction isStringStart (chr: number): boolean {\n  return chr === 0x22 || chr === 0x27\n}\n\nfunction parseBracket (chr: number): void {\n  let inBracket = 1\n  expressionPos = index\n  while (!eof()) {\n    chr = next()\n    if (isStringStart(chr)) {\n      parseString(chr)\n      continue\n    }\n    if (chr === 0x5B) inBracket++\n    if (chr === 0x5D) inBracket--\n    if (inBracket === 0) {\n      expressionEndPos = index\n      break\n    }\n  }\n}\n\nfunction parseString (chr: number): void {\n  const stringQuote = chr\n  while (!eof()) {\n    chr = next()\n    if (chr === stringQuote) {\n      break\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/compiler/error-detector.js",
    "content": "/* @flow */\n\nimport { dirRE, onRE } from './parser/index'\n\n// these keywords should not appear inside expressions, but operators like\n// typeof, instanceof and in are allowed\nconst prohibitedKeywordRE = new RegExp('\\\\b' + (\n  'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +\n  'super,throw,while,yield,delete,export,import,return,switch,default,' +\n  'extends,finally,continue,debugger,function,arguments'\n).split(',').join('\\\\b|\\\\b') + '\\\\b')\n\n// these unary operators should not be used as property/method names\nconst unaryOperatorsRE = new RegExp('\\\\b' + (\n  'delete,typeof,void'\n).split(',').join('\\\\s*\\\\([^\\\\)]*\\\\)|\\\\b') + '\\\\s*\\\\([^\\\\)]*\\\\)')\n\n// check valid identifier for v-for\nconst identRE = /[A-Za-z_$][\\w$]*/\n\n// strip strings in expressions\nconst stripStringRE = /'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*\\$\\{|\\}(?:[^`\\\\]|\\\\.)*`|`(?:[^`\\\\]|\\\\.)*`/g\n\n// detect problematic expressions in a template\nexport function detectErrors (ast: ?ASTNode): Array<string> {\n  const errors: Array<string> = []\n  if (ast) {\n    checkNode(ast, errors)\n  }\n  return errors\n}\n\nfunction checkNode (node: ASTNode, errors: Array<string>) {\n  if (node.type === 1) {\n    for (const name in node.attrsMap) {\n      if (dirRE.test(name)) {\n        const value = node.attrsMap[name]\n        if (value) {\n          if (name === 'v-for') {\n            checkFor(node, `v-for=\"${value}\"`, errors)\n          } else if (onRE.test(name)) {\n            checkEvent(value, `${name}=\"${value}\"`, errors)\n          } else {\n            checkExpression(value, `${name}=\"${value}\"`, errors)\n          }\n        }\n      }\n    }\n    if (node.children) {\n      for (let i = 0; i < node.children.length; i++) {\n        checkNode(node.children[i], errors)\n      }\n    }\n  } else if (node.type === 2) {\n    checkExpression(node.expression, node.text, errors)\n  }\n}\n\nfunction checkEvent (exp: string, text: string, errors: Array<string>) {\n  const stipped = exp.replace(stripStringRE, '')\n  const keywordMatch: any = stipped.match(unaryOperatorsRE)\n  if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {\n    errors.push(\n      `avoid using JavaScript unary operator as property name: ` +\n      `\"${keywordMatch[0]}\" in expression ${text.trim()}`\n    )\n  }\n  checkExpression(exp, text, errors)\n}\n\nfunction checkFor (node: ASTElement, text: string, errors: Array<string>) {\n  checkExpression(node.for || '', text, errors)\n  checkIdentifier(node.alias, 'v-for alias', text, errors)\n  checkIdentifier(node.iterator1, 'v-for iterator', text, errors)\n  checkIdentifier(node.iterator2, 'v-for iterator', text, errors)\n}\n\nfunction checkIdentifier (ident: ?string, type: string, text: string, errors: Array<string>) {\n  if (typeof ident === 'string' && !identRE.test(ident)) {\n    errors.push(`invalid ${type} \"${ident}\" in expression: ${text.trim()}`)\n  }\n}\n\nfunction checkExpression (exp: string, text: string, errors: Array<string>) {\n  try {\n    new Function(`return ${exp}`)\n  } catch (e) {\n    const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE)\n    if (keywordMatch) {\n      errors.push(\n        `avoid using JavaScript keyword as property name: ` +\n        `\"${keywordMatch[0]}\" in expression ${text.trim()}`\n      )\n    } else {\n      errors.push(`invalid expression: ${text.trim()}`)\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/compiler/helpers.js",
    "content": "/* @flow */\n\nimport { parseFilters } from './parser/filter-parser'\n\n/*Vue 编译器警告*/\nexport function baseWarn (msg: string) {\n  console.error(`[Vue compiler]: ${msg}`)\n}\n\nexport function pluckModuleFunction<F: Function> (\n  modules: ?Array<Object>,\n  key: string\n): Array<F> {\n  return modules\n    ? modules.map(m => m[key]).filter(_ => _)\n    : []\n}\n\n/*将属性放入ele的props属性中*/\nexport function addProp (el: ASTElement, name: string, value: string) {\n  (el.props || (el.props = [])).push({ name, value })\n}\n\n/*将属性放入ele的attr属性中*/\nexport function addAttr (el: ASTElement, name: string, value: string) {\n  (el.attrs || (el.attrs = [])).push({ name, value })\n}\n\n/*将参数加入到ele的directives中去*/\nexport function addDirective (\n  el: ASTElement,\n  name: string,\n  rawName: string,\n  value: string,\n  arg: ?string,\n  modifiers: ?ASTModifiers\n) {\n  (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })\n}\n\nexport function addHandler (\n  el: ASTElement,\n  name: string,\n  value: string,\n  modifiers: ?ASTModifiers,\n  important?: boolean,\n  warn?: Function\n) {\n  // warn prevent and passive modifier\n  /* istanbul ignore if */\n  if (\n    process.env.NODE_ENV !== 'production' && warn &&\n    modifiers && modifiers.prevent && modifiers.passive\n  ) {\n    warn(\n      'passive and prevent can\\'t be used together. ' +\n      'Passive handler can\\'t prevent default event.'\n    )\n  }\n  // check capture modifier\n  if (modifiers && modifiers.capture) {\n    delete modifiers.capture\n    name = '!' + name // mark the event as captured\n  }\n  if (modifiers && modifiers.once) {\n    delete modifiers.once\n    name = '~' + name // mark the event as once\n  }\n  /* istanbul ignore if */\n  if (modifiers && modifiers.passive) {\n    delete modifiers.passive\n    name = '&' + name // mark the event as passive\n  }\n  let events\n  if (modifiers && modifiers.native) {\n    delete modifiers.native\n    events = el.nativeEvents || (el.nativeEvents = {})\n  } else {\n    events = el.events || (el.events = {})\n  }\n  const newHandler = { value, modifiers }\n  const handlers = events[name]\n  /* istanbul ignore if */\n  if (Array.isArray(handlers)) {\n    important ? handlers.unshift(newHandler) : handlers.push(newHandler)\n  } else if (handlers) {\n    events[name] = important ? [newHandler, handlers] : [handlers, newHandler]\n  } else {\n    events[name] = newHandler\n  }\n}\n\nexport function getBindingAttr (\n  el: ASTElement,\n  name: string,\n  getStatic?: boolean\n): ?string {\n  /*得到用:或者v-bind:修饰的特殊属性*/\n  const dynamicValue =\n    getAndRemoveAttr(el, ':' + name) ||\n    getAndRemoveAttr(el, 'v-bind:' + name)\n  if (dynamicValue != null) {\n    /*存在特殊属性*/\n    return parseFilters(dynamicValue)\n  } else if (getStatic !== false) {\n    /*getStatic非false的时候返回静态属性，即一般的属性*/\n    const staticValue = getAndRemoveAttr(el, name)\n    if (staticValue != null) {\n      return JSON.stringify(staticValue)\n    }\n  }\n}\n\n/*从ele的属性中获取name对应的值并将它从中删除*/\nexport function getAndRemoveAttr (el: ASTElement, name: string): ?string {\n  let val\n  if ((val = el.attrsMap[name]) != null) {\n    const list = el.attrsList\n    for (let i = 0, l = list.length; i < l; i++) {\n      if (list[i].name === name) {\n        list.splice(i, 1)\n        break\n      }\n    }\n  }\n  return val\n}\n"
  },
  {
    "path": "vue-src/compiler/index.js",
    "content": "/* @flow */\n\nimport { parse } from './parser/index'\nimport { optimize } from './optimizer'\nimport { generate } from './codegen/index'\nimport { detectErrors } from './error-detector'\nimport { extend, noop } from 'shared/util'\nimport { warn, tip } from 'core/util/debug'\n\nfunction baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  /*parse解析得到ast树*/\n  const ast = parse(template.trim(), options)\n  /*\n    将AST树进行优化\n    优化的目标：生成模板AST树，检测不需要进行DOM改变的静态子树。\n    一旦检测到这些静态树，我们就能做以下这些事情：\n    1.把它们变成常数，这样我们就再也不需要每次重新渲染时创建新的节点了。\n    2.在patch的过程中直接跳过。\n */\n  optimize(ast, options)\n  /*根据ast树生成所需的code（内部包含render与staticRenderFns）*/\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n}\n\n/*新建成Funtion对象*/\nfunction makeFunction (code, errors) {\n  try {\n    return new Function(code)\n  } catch (err) {\n    errors.push({ err, code })\n    return noop\n  }\n}\n\n/*提供一个方法，根据传递的baseOptions（不同平台可以有不同的实现）创建相应的编译器*/\nexport function createCompiler (baseOptions: CompilerOptions) {\n  /*\n    作为缓存，防止每次都重新编译。\n    模板的key为delimiters(https://cn.vuejs.org/v2/api/#delimiters)+template，value为编译结果\n  */\n  const functionCompileCache: {\n    [key: string]: CompiledFunctionResult;\n  } = Object.create(null)\n\n  /*编译，将模板template编译成AST树、render函数以及staticRenderFns函数*/\n  function compile (\n    template: string,\n    options?: CompilerOptions\n  ): CompiledResult {\n    const finalOptions = Object.create(baseOptions)\n    const errors = []\n    const tips = []\n    finalOptions.warn = (msg, tip) => {\n      (tip ? tips : errors).push(msg)\n    }\n\n    /*做下面这些merge的目的因为不同平台可以提供自己本身平台的一个baseOptions，内部封装了平台自己的实现，然后把共同的部分抽离开来放在这层compiler中，所以在这里需要merge一下*/\n    if (options) {\n      // merge custom modules\n      /*合并modules*/\n      if (options.modules) {\n        finalOptions.modules = (baseOptions.modules || []).concat(options.modules)\n      }\n      // merge custom directives\n      if (options.directives) {\n        /*合并directives*/\n        finalOptions.directives = extend(\n          Object.create(baseOptions.directives),\n          options.directives\n        )\n      }\n      // copy other options\n      for (const key in options) {\n        /*合并其余的options，modules与directives已经在上面做了特殊处理了*/\n        if (key !== 'modules' && key !== 'directives') {\n          finalOptions[key] = options[key]\n        }\n      }\n    }\n\n    /*基础模板编译，得到编译结果*/\n    const compiled = baseCompile(template, finalOptions)\n    if (process.env.NODE_ENV !== 'production') {\n      errors.push.apply(errors, detectErrors(compiled.ast))\n    }\n    compiled.errors = errors\n    compiled.tips = tips\n    return compiled\n  }\n\n  /*带缓存的编译器，同时staticRenderFns以及render函数会被转换成Funtion对象*/\n  function compileToFunctions (\n    template: string,\n    options?: CompilerOptions,\n    vm?: Component\n  ): CompiledFunctionResult {\n    options = options || {}\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      // detect possible CSP restriction\n      try {\n        new Function('return 1')\n      } catch (e) {\n        if (e.toString().match(/unsafe-eval|CSP/)) {\n          warn(\n            'It seems you are using the standalone build of Vue.js in an ' +\n            'environment with Content Security Policy that prohibits unsafe-eval. ' +\n            'The template compiler cannot work in this environment. Consider ' +\n            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +\n            'templates into render functions.'\n          )\n        }\n      }\n    }\n\n    // check cache\n    /*有缓存的时候直接取出缓存中的结果即可*/\n    const key = options.delimiters\n      ? String(options.delimiters) + template\n      : template\n    if (functionCompileCache[key]) {\n      return functionCompileCache[key]\n    }\n\n    // compile\n    /*编译*/\n    const compiled = compile(template, options)\n\n    // check compilation errors/tips\n    if (process.env.NODE_ENV !== 'production') {\n      if (compiled.errors && compiled.errors.length) {\n        warn(\n          `Error compiling template:\\n\\n${template}\\n\\n` +\n          compiled.errors.map(e => `- ${e}`).join('\\n') + '\\n',\n          vm\n        )\n      }\n      if (compiled.tips && compiled.tips.length) {\n        compiled.tips.forEach(msg => tip(msg, vm))\n      }\n    }\n\n    // turn code into functions\n    const res = {}\n    const fnGenErrors = []\n    /*将render转换成Funtion对象*/\n    res.render = makeFunction(compiled.render, fnGenErrors)\n    /*将staticRenderFns全部转化成Funtion对象 */\n    const l = compiled.staticRenderFns.length\n    res.staticRenderFns = new Array(l)\n    for (let i = 0; i < l; i++) {\n      res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)\n    }\n\n    // check function generation errors.\n    // this should only happen if there is a bug in the compiler itself.\n    // mostly for codegen development use\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {\n        warn(\n          `Failed to generate render function:\\n\\n` +\n          fnGenErrors.map(({ err, code }) => `${err.toString()} in\\n\\n${code}\\n`).join('\\n'),\n          vm\n        )\n      }\n    }\n\n    /*存放在缓存中，以免每次都重新编译*/\n    return (functionCompileCache[key] = res) \n  }\n\n  return {\n    compile,\n    compileToFunctions\n  }\n}\n/*Github:https://github.com/answershuto*/"
  },
  {
    "path": "vue-src/compiler/optimizer.js",
    "content": "/* @flow */\n\nimport { makeMap, isBuiltInTag, cached, no } from 'shared/util'\n\n/*标记是否为静态属性*/\nlet isStaticKey\n/*标记是否是平台保留的标签*/\nlet isPlatformReservedTag\n\nconst genStaticKeysCached = cached(genStaticKeys)\n\n/**\n * Goal of the optimizer: walk the generated template AST tree\n * and detect sub-trees that are purely static, i.e. parts of\n * the DOM that never needs to change.\n *\n * Once we detect these sub-trees, we can:\n *\n * 1. Hoist them into constants, so that we no longer need to\n *    create fresh nodes for them on each re-render;\n * 2. Completely skip them in the patching process.\n */\n /*\n  将AST树进行优化\n  优化的目标：生成模板AST树，检测不需要进行DOM改变的静态子树。\n  一旦检测到这些静态树，我们就能做以下这些事情：\n  1.把它们变成常数，这样我们就再也不需要每次重新渲染时创建新的节点了。\n  2.在patch的过程中直接跳过。\n */\nexport function optimize (root: ?ASTElement, options: CompilerOptions) {\n  if (!root) return\n  /*标记是否为静态属性*/\n  isStaticKey = genStaticKeysCached(options.staticKeys || '')\n  /*标记是否是平台保留的标签*/\n  isPlatformReservedTag = options.isReservedTag || no\n  // first pass: mark all non-static nodes.\n  /*处理所有非静态节点*/\n  markStatic(root)\n  // second pass: mark static roots.\n  /*处理static root*/\n  markStaticRoots(root, false)\n}\n\n/*静态属性的map表*/\nfunction genStaticKeys (keys: string): Function {\n  return makeMap(\n    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +\n    (keys ? ',' + keys : '')\n  )\n}\n\n/*处理所有非静态节点*/\nfunction markStatic (node: ASTNode) {\n  /*标记一个node节点是否是static的*/\n  node.static = isStatic(node)\n  if (node.type === 1) {\n    // do not make component slot content static. this avoids\n    // 1. components not able to mutate slot nodes\n    // 2. static slot content fails for hot-reloading\n    /*\n      不要使组件slot成为静态的，避免下面这两种情况：\n      1.\n    */\n    if (\n      !isPlatformReservedTag(node.tag) &&\n      node.tag !== 'slot' &&\n      node.attrsMap['inline-template'] == null\n    ) {\n      return\n    }\n    /*遍历子节点*/\n    for (let i = 0, l = node.children.length; i < l; i++) {\n      const child = node.children[i]\n      markStatic(child)\n      /*如果子节点不是静态的，则本身也不是静态的*/\n      if (!child.static) {\n        node.static = false\n      }\n    }\n  }\n}\n\nfunction markStaticRoots (node: ASTNode, isInFor: boolean) {\n  if (node.type === 1) {\n    if (node.static || node.once) {\n      /*标记static的或者有v-once指令同时处于for循环中的节点*/\n      node.staticInFor = isInFor\n    }\n    // For a node to qualify as a static root, it should have children that\n    // are not just static text. Otherwise the cost of hoisting out will\n    // outweigh the benefits and it's better off to just always render it fresh.\n    /*一个static root节点必须有子节点否则它可能只是一个static的文本节点，而且它不能只有文本子节点*/\n    if (node.static && node.children.length && !(\n      node.children.length === 1 &&\n      node.children[0].type === 3\n    )) {\n      node.staticRoot = true\n      return\n    } else {\n      node.staticRoot = false\n    }\n    /*遍历子节点*/\n    if (node.children) {\n      for (let i = 0, l = node.children.length; i < l; i++) {\n        markStaticRoots(node.children[i], isInFor || !!node.for)\n      }\n    }\n    /*\n      ifConditions存储了if条件。\n      是一个数组，格式为[{exp: xxx, block:xxx}, {exp: xxx, block:xxx}, {exp: xxx, block:xxx}]\n      block存储了element，exp存储了表达式。\n    */\n    if (node.ifConditions) {\n      walkThroughConditionsBlocks(node.ifConditions, isInFor)\n    }\n  }\n}\n\nfunction walkThroughConditionsBlocks (conditionBlocks: ASTIfConditions, isInFor: boolean): void {\n  for (let i = 1, len = conditionBlocks.length; i < len; i++) {\n    markStaticRoots(conditionBlocks[i].block, isInFor)\n  }\n}\n\n/*判断一个node节点是否是static的*/\nfunction isStatic (node: ASTNode): boolean {\n  if (node.type === 2) { // expression\n    return false\n  }\n  if (node.type === 3) { // text\n    return true\n  }\n  return !!(node.pre || (\n    !node.hasBindings && // no dynamic bindings\n    !node.if && !node.for && // not v-if or v-for or v-else\n    !isBuiltInTag(node.tag) && // not a built-in\n    isPlatformReservedTag(node.tag) && // not a component\n    !isDirectChildOfTemplateFor(node) &&\n    Object.keys(node).every(isStaticKey)\n  ))\n}\n\nfunction isDirectChildOfTemplateFor (node: ASTElement): boolean {\n  while (node.parent) {\n    node = node.parent\n    if (node.tag !== 'template') {\n      return false\n    }\n    if (node.for) {\n      return true\n    }\n  }\n  return false\n}\n/*Github:https://github.com/answershuto*/"
  },
  {
    "path": "vue-src/compiler/parser/entity-decoder.js",
    "content": "/* @flow */\n\nlet decoder\n\nexport function decode (html: string): string {\n  decoder = decoder || document.createElement('div')\n  decoder.innerHTML = html\n  return decoder.textContent\n}\n"
  },
  {
    "path": "vue-src/compiler/parser/filter-parser.js",
    "content": "/* @flow */\n\nconst validDivisionCharRE = /[\\w).+\\-_$\\]]/\n\n/*解析过滤器*/\nexport function parseFilters (exp: string): string {\n  let inSingle = false\n  let inDouble = false\n  let inTemplateString = false\n  let inRegex = false\n  let curly = 0\n  let square = 0\n  let paren = 0\n  let lastFilterIndex = 0\n  let c, prev, i, expression, filters\n\n  for (i = 0; i < exp.length; i++) {\n    prev = c\n    c = exp.charCodeAt(i)\n    if (inSingle) {\n      // '  单引号\n      if (c === 0x27 && prev !== 0x5C) inSingle = false\n    } else if (inDouble) {\n      // \"  双引号\n      if (c === 0x22 && prev !== 0x5C) inDouble = false\n    } else if (inTemplateString) {\n      // `  模板字符串\n      if (c === 0x60 && prev !== 0x5C) inTemplateString = false\n    } else if (inRegex) {\n      // /  正则\n      if (c === 0x2f && prev !== 0x5C) inRegex = false\n    } else if (\n      // |  管道\n      c === 0x7C && // pipe\n      exp.charCodeAt(i + 1) !== 0x7C &&\n      exp.charCodeAt(i - 1) !== 0x7C &&\n      !curly && !square && !paren\n    ) {\n      if (expression === undefined) {\n        // first filter, end of expression\n        lastFilterIndex = i + 1\n        expression = exp.slice(0, i).trim()\n      } else {\n        pushFilter()\n      }\n    } else {\n      switch (c) {\n        case 0x22: inDouble = true; break         // \"\n        case 0x27: inSingle = true; break         // '\n        case 0x60: inTemplateString = true; break // `\n        case 0x28: paren++; break                 // (\n        case 0x29: paren--; break                 // )\n        case 0x5B: square++; break                // [\n        case 0x5D: square--; break                // ]\n        case 0x7B: curly++; break                 // {\n        case 0x7D: curly--; break                 // }\n      }\n      if (c === 0x2f) { // /\n        let j = i - 1\n        let p\n        // find first non-whitespace prev char\n        for (; j >= 0; j--) {\n          p = exp.charAt(j)\n          if (p !== ' ') break\n        }\n        if (!p || !validDivisionCharRE.test(p)) {\n          inRegex = true\n        }\n      }\n    }\n  }\n\n  if (expression === undefined) {\n    expression = exp.slice(0, i).trim()\n  } else if (lastFilterIndex !== 0) {\n    pushFilter()\n  }\n\n  function pushFilter () {\n    (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())\n    lastFilterIndex = i + 1\n  }\n\n  if (filters) {\n    for (i = 0; i < filters.length; i++) {\n      expression = wrapFilter(expression, filters[i])\n    }\n  }\n\n  return expression\n}\n\nfunction wrapFilter (exp: string, filter: string): string {\n  const i = filter.indexOf('(')\n  if (i < 0) {\n    // _f: resolveFilter\n    return `_f(\"${filter}\")(${exp})`\n  } else {\n    const name = filter.slice(0, i)\n    const args = filter.slice(i + 1)\n    return `_f(\"${name}\")(${exp},${args}`\n  }\n}\n"
  },
  {
    "path": "vue-src/compiler/parser/html-parser.js",
    "content": "/**\n * Not type-checking this file because it's mostly vendor code.\n */\n\n/*!\n * HTML Parser By John Resig (ejohn.org)\n * Modified by Juriy \"kangax\" Zaytsev\n * Original code by Erik Arvidsson, Mozilla Public License\n * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js\n */\n\nimport { makeMap, no } from 'shared/util'\nimport { isNonPhrasingTag } from 'web/compiler/util'\n\n// Regular Expressions for parsing tags and attributes\nconst singleAttrIdentifier = /([^\\s\"'<>/=]+)/\nconst singleAttrAssign = /(?:=)/\nconst singleAttrValues = [\n  // attr value double quotes\n  /\"([^\"]*)\"+/.source,\n  // attr value, single quotes\n  /'([^']*)'+/.source,\n  // attr value, no quotes\n  /([^\\s\"'=<>`]+)/.source\n]\nconst attribute = new RegExp(\n  '^\\\\s*' + singleAttrIdentifier.source +\n  '(?:\\\\s*(' + singleAttrAssign.source + ')' +\n  '\\\\s*(?:' + singleAttrValues.join('|') + '))?'\n)\n/*Github:https://github.com/answershuto*/\n// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName\n// but for Vue templates we can enforce a simple charset\nconst ncname = '[a-zA-Z_][\\\\w\\\\-\\\\.]*'\nconst qnameCapture = '((?:' + ncname + '\\\\:)?' + ncname + ')'\nconst startTagOpen = new RegExp('^<' + qnameCapture)\nconst startTagClose = /^\\s*(\\/?)>/\nconst endTag = new RegExp('^<\\\\/' + qnameCapture + '[^>]*>')\n/*匹配<!DOCTYPE> 标签*/\nconst doctype = /^<!DOCTYPE [^>]+>/i\n/*匹配注释*/\nconst comment = /^<!--/\nconst conditionalComment = /^<!\\[/\n\nlet IS_REGEX_CAPTURING_BROKEN = false\n'x'.replace(/x(.)?/g, function (m, g) {\n  IS_REGEX_CAPTURING_BROKEN = g === ''\n})\n\n// Special Elements (can contain anything)\n/*返回一个函数用以检测传入的key值是否为script、style或者是textarea*/\nexport const isPlainTextElement = makeMap('script,style,textarea', true)\nconst reCache = {}\n\n/*转义表*/\nconst decodingMap = {\n  '&lt;': '<',\n  '&gt;': '>',\n  '&quot;': '\"',\n  '&amp;': '&',\n  '&#10;': '\\n'\n}\nconst encodedAttr = /&(?:lt|gt|quot|amp);/g\nconst encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10);/g\n\nfunction decodeAttr (value, shouldDecodeNewlines) {\n  const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr\n  return value.replace(re, match => decodingMap[match])\n}\n\n/*解析HTML*/\nexport function parseHTML (html, options) {\n  const stack = []\n  const expectHTML = options.expectHTML\n  const isUnaryTag = options.isUnaryTag || no\n  const canBeLeftOpenTag = options.canBeLeftOpenTag || no\n  let index = 0\n  let last, lastTag\n  while (html) {\n    last = html\n    // Make sure we're not in a plaintext content element like script/style\n    /*保证lastTag不是纯文本标签，比如script、style以及textarea*/\n    if (!lastTag || !isPlainTextElement(lastTag)) {\n      let textEnd = html.indexOf('<')\n      if (textEnd === 0) {\n        // Comment:\n        /*如果是注释则直接去除*/\n        if (comment.test(html)) {\n          const commentEnd = html.indexOf('-->')\n\n          if (commentEnd >= 0) {\n            advance(commentEnd + 3)\n            continue\n          }\n        }\n\n        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment\n        if (conditionalComment.test(html)) {\n          const conditionalEnd = html.indexOf(']>')\n\n          if (conditionalEnd >= 0) {\n            advance(conditionalEnd + 2)\n            continue\n          }\n        }\n\n        // Doctype:\n        /*<!DOCTYPE>标签不需要处理直接去除*/\n        const doctypeMatch = html.match(doctype)\n        if (doctypeMatch) {\n          advance(doctypeMatch[0].length)\n          continue\n        }\n\n        // End tag:\n        const endTagMatch = html.match(endTag)\n        if (endTagMatch) {\n          const curIndex = index\n          advance(endTagMatch[0].length)\n          parseEndTag(endTagMatch[1], curIndex, index)\n          continue\n        }\n\n        // Start tag:\n        const startTagMatch = parseStartTag()\n        if (startTagMatch) {\n          handleStartTag(startTagMatch)\n          continue\n        }\n      }\n\n      let text, rest, next\n      if (textEnd >= 0) {\n        rest = html.slice(textEnd)\n        while (\n          !endTag.test(rest) &&\n          !startTagOpen.test(rest) &&\n          !comment.test(rest) &&\n          !conditionalComment.test(rest)\n        ) {\n          // < in plain text, be forgiving and treat it as text\n          next = rest.indexOf('<', 1)\n          if (next < 0) break\n          textEnd += next\n          rest = html.slice(textEnd)\n        }\n        text = html.substring(0, textEnd)\n        advance(textEnd)\n      }\n\n      if (textEnd < 0) {\n        text = html\n        html = ''\n      }\n\n      if (options.chars && text) {\n        options.chars(text)\n      }\n    } else {\n      var stackedTag = lastTag.toLowerCase()\n      var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\\\s\\\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))\n      var endTagLength = 0\n      var rest = html.replace(reStackedTag, function (all, text, endTag) {\n        endTagLength = endTag.length\n        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {\n          text = text\n            .replace(/<!--([\\s\\S]*?)-->/g, '$1')\n            .replace(/<!\\[CDATA\\[([\\s\\S]*?)]]>/g, '$1')\n        }\n        if (options.chars) {\n          options.chars(text)\n        }\n        return ''\n      })\n      index += html.length - rest.length\n      html = rest\n      parseEndTag(stackedTag, index - endTagLength, index)\n    }\n\n    if (html === last) {\n      options.chars && options.chars(html)\n      if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {\n        options.warn(`Mal-formatted tag at end of template: \"${html}\"`)\n      }\n      break\n    }\n  }\n\n  // Clean up any remaining tags\n  /*清楚多余的标签*/\n  parseEndTag()\n\n  /*为计数index加上n，同时，使html到n个字符以后到位置作为起始位*/\n  function advance (n) {\n    index += n\n    html = html.substring(n)\n  }\n\n  function parseStartTag () {\n    const start = html.match(startTagOpen)\n    if (start) {\n      const match = {\n        tagName: start[1],\n        attrs: [],\n        start: index\n      }\n      advance(start[0].length)\n      let end, attr\n      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {\n        advance(attr[0].length)\n        match.attrs.push(attr)\n      }\n      if (end) {\n        match.unarySlash = end[1]\n        advance(end[0].length)\n        match.end = index\n        return match\n      }\n    }\n  }\n\n  function handleStartTag (match) {\n    const tagName = match.tagName\n    const unarySlash = match.unarySlash\n\n    if (expectHTML) {\n      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {\n        parseEndTag(lastTag)\n      }\n      if (canBeLeftOpenTag(tagName) && lastTag === tagName) {\n        parseEndTag(tagName)\n      }\n    }\n\n    const unary = isUnaryTag(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash\n\n    const l = match.attrs.length\n    const attrs = new Array(l)\n    for (let i = 0; i < l; i++) {\n      const args = match.attrs[i]\n      // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778\n      if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('\"\"') === -1) {\n        if (args[3] === '') { delete args[3] }\n        if (args[4] === '') { delete args[4] }\n        if (args[5] === '') { delete args[5] }\n      }\n      const value = args[3] || args[4] || args[5] || ''\n      attrs[i] = {\n        name: args[1],\n        value: decodeAttr(\n          value,\n          options.shouldDecodeNewlines\n        )\n      }\n    }\n\n    if (!unary) {\n      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })\n      lastTag = tagName\n    }\n\n    if (options.start) {\n      options.start(tagName, attrs, unary, match.start, match.end)\n    }\n  }\n\n  function parseEndTag (tagName, start, end) {\n    let pos, lowerCasedTagName\n    if (start == null) start = index\n    if (end == null) end = index\n\n    if (tagName) {\n      lowerCasedTagName = tagName.toLowerCase()\n    }\n\n    // Find the closest opened tag of the same type\n    if (tagName) {\n      for (pos = stack.length - 1; pos >= 0; pos--) {\n        if (stack[pos].lowerCasedTag === lowerCasedTagName) {\n          break\n        }\n      }\n    } else {\n      // If no tag name is provided, clean shop\n      pos = 0\n    }\n\n    if (pos >= 0) {\n      // Close all the open elements, up the stack\n      for (let i = stack.length - 1; i >= pos; i--) {\n        if (process.env.NODE_ENV !== 'production' &&\n            (i > pos || !tagName) &&\n            options.warn) {\n          options.warn(\n            `tag <${stack[i].tag}> has no matching end tag.`\n          )\n        }\n        if (options.end) {\n          options.end(stack[i].tag, start, end)\n        }\n      }\n\n      // Remove the open elements from the stack\n      stack.length = pos\n      lastTag = pos && stack[pos - 1].tag\n    } else if (lowerCasedTagName === 'br') {\n      if (options.start) {\n        options.start(tagName, [], true, start, end)\n      }\n    } else if (lowerCasedTagName === 'p') {\n      if (options.start) {\n        options.start(tagName, [], false, start, end)\n      }\n      if (options.end) {\n        options.end(tagName, start, end)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/compiler/parser/index.js",
    "content": "/* @flow */\n\nimport { decode } from 'he'\nimport { parseHTML } from './html-parser'\nimport { parseText } from './text-parser'\nimport { parseFilters } from './filter-parser'\nimport { cached, no, camelize } from 'shared/util'\nimport { genAssignmentCode } from '../directives/model'\nimport { isIE, isEdge, isServerRendering } from 'core/util/env'\n\nimport {\n  addProp,\n  addAttr,\n  baseWarn,\n  addHandler,\n  addDirective,\n  getBindingAttr,\n  getAndRemoveAttr,\n  pluckModuleFunction\n} from '../helpers'\n\n/*匹配@以及v-on，绑定事件 */\nexport const onRE = /^@|^v-on:/\n/*匹配v-、@以及:*/\nexport const dirRE = /^v-|^@|^:/\n/*匹配v-for中的in以及of*/\nexport const forAliasRE = /(.*?)\\s+(?:in|of)\\s+(.*)/\n/*v-for参数中带括号的情况匹配，比如(item, index)这样的参数*/\nexport const forIteratorRE = /\\((\\{[^}]*\\}|[^,]*),([^,]*)(?:,([^,]*))?\\)/\n/*Github:https://github.com/answershuto*/\nconst argRE = /:(.*)$/\n/*匹配v-bind以及:*/\nconst bindRE = /^:|^v-bind:/\n/*根据点来分开各个级别的正则，比如a.b.c.d解析后可以得到.b .c .d*/\nconst modifierRE = /\\.[^.]+/g\n\nconst decodeHTMLCached = cached(decode)\n\n// configurable state\nexport let warn\nlet delimiters\nlet transforms\nlet preTransforms\nlet postTransforms\nlet platformIsPreTag\nlet platformMustUseProp\nlet platformGetTagNamespace\n\n/**\n * Convert HTML string to AST.\n */\n /*将HTML字符串转换成AST*/\nexport function parse (\n  template: string,\n  options: CompilerOptions\n): ASTElement | void {\n  /*警告函数，baseWarn是Vue 编译器默认警告*/\n  warn = options.warn || baseWarn \n  platformGetTagNamespace = options.platformGetTagNamespace || no\n  platformMustUseProp = options.mustUseProp || no\n  /*检测是否是<pre>标签*/\n  platformIsPreTag = options.isPreTag || no\n  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')\n  transforms = pluckModuleFunction(options.modules, 'transformNode')\n  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')\n  delimiters = options.delimiters\n\n  /*存放ele*/\n  const stack = []\n  const preserveWhitespace = options.preserveWhitespace !== false\n  let root\n  let currentParent\n  /*标志位，是否有v-pre属性*/\n  let inVPre = false\n  /*标志位，是否是pre标签*/\n  let inPre = false\n  let warned = false\n\n  /*只发出一次的warning*/\n  function warnOnce (msg) {\n    if (!warned) {\n      warned = true\n      warn(msg)\n    }\n  }\n\n  function endPre (element) {\n    // check pre state\n    /*是否有v-pre属性，存在则标志位变为false，因为这里已经是结束end，存在v-pre时在start中会被标志为true*/\n    if (element.pre) {\n      inVPre = false\n    }\n    /*检测是否是<pre>标签*/\n    if (platformIsPreTag(element.tag)) {\n      inPre = false\n    }\n  }\n\n  /*解析HTML*/\n  parseHTML(template, {\n    warn,\n    expectHTML: options.expectHTML,\n    isUnaryTag: options.isUnaryTag,\n    canBeLeftOpenTag: options.canBeLeftOpenTag,\n    shouldDecodeNewlines: options.shouldDecodeNewlines,\n    start (tag, attrs, unary) {\n      // check namespace.\n      // inherit parent ns if there is one\n      const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)\n\n      // handle IE svg bug\n      /* istanbul ignore if */\n      /*处理IE的svg bug*/\n      if (isIE && ns === 'svg') {\n        attrs = guardIESVGBug(attrs)\n      }\n\n      const element: ASTElement = {\n        type: 1,\n        tag,\n        attrsList: attrs,\n        attrsMap: makeAttrsMap(attrs),\n        parent: currentParent,\n        children: []\n      }\n      if (ns) {\n        element.ns = ns\n      }\n\n      /*如果是被禁止的标签或者是服务端渲染*/\n      if (isForbiddenTag(element) && !isServerRendering()) {\n        element.forbidden = true\n        process.env.NODE_ENV !== 'production' && warn(\n          'Templates should only be responsible for mapping the state to the ' +\n          'UI. Avoid placing tags with side-effects in your templates, such as ' +\n          `<${tag}>` + ', as they will not be parsed.'\n        )\n      }\n\n      // apply pre-transforms\n      for (let i = 0; i < preTransforms.length; i++) {\n        preTransforms[i](element, options)\n      }\n\n      if (!inVPre) {\n        /*\n          处理v-pre属性\n          v-pre元素及其子元素被跳过编译\n          https://cn.vuejs.org/v2/api/#v-pre\n        */\n        processPre(element)\n        if (element.pre) {\n          inVPre = true\n        }\n      }\n      /*检测是否是<pre>标签*/\n      if (platformIsPreTag(element.tag)) {\n        inPre = true\n      }\n      /*如果有v-pre属性，元素及其子元素不会被编译*/\n      if (inVPre) {\n        processRawAttrs(element)\n      } else {\n        /*匹配v-for属性*/\n        processFor(element)\n        /*匹配if属性，分别处理v-if、v-else以及v-else-if属性*/\n        processIf(element)\n        /*处理v-once属性，https://cn.vuejs.org/v2/api/#v-once*/\n        processOnce(element)\n        /*处理key属性 https://cn.vuejs.org/v2/api/#key*/\n        processKey(element)\n\n        // determine whether this is a plain element after\n        // removing structural attributes\n        /*去掉属性后，确定这是一个普通元素。*/\n        element.plain = !element.key && !attrs.length\n\n        /*处理ref属性 https://cn.vuejs.org/v2/api/#ref*/\n        processRef(element)\n        /*处理slot属性 https://cn.vuejs.org/v2/api/#slot*/\n        processSlot(element)\n        /*处理组件*/\n        processComponent(element)\n        /*转换*/\n        for (let i = 0; i < transforms.length; i++) {\n          transforms[i](element, options)\n        }\n        /*处理属性*/\n        processAttrs(element)\n      }\n\n      /*监测根级元素的约束*/\n      function checkRootConstraints (el) {\n        if (process.env.NODE_ENV !== 'production') {\n          /*slot以及templete不能作为根级元素*/\n          if (el.tag === 'slot' || el.tag === 'template') {\n            warnOnce(\n              `Cannot use <${el.tag}> as component root element because it may ` +\n              'contain multiple nodes.'\n            )\n          }\n          /*以及根级元素不能有v-for*/\n          if (el.attrsMap.hasOwnProperty('v-for')) {\n            warnOnce(\n              'Cannot use v-for on stateful component root element because ' +\n              'it renders multiple elements.'\n            )\n          }\n        }\n      }\n\n      // tree management\n      if (!root) {\n        root = element\n        /*检测根级元素的约束*/\n        checkRootConstraints(root)\n      } else if (!stack.length) {\n        // allow root elements with v-if, v-else-if and v-else\n        /*\n          根级元素是可以用v-if、v-else来写多个条件下的多个根级元素的\n          比如说\n          <template>\n            <div v-if=\"fff\">aaa</div>\n            <div v-else>bbb</div>\n          </template>\n          是完全允许的\n        */\n        if (root.if && (element.elseif || element.else)) {\n          /*监测根级元素的约束*/\n          checkRootConstraints(element)\n          /*在el的ifConditions属性中加入condition*/\n          addIfCondition(root, {\n            exp: element.elseif,\n            block: element\n          })\n        } else if (process.env.NODE_ENV !== 'production') {\n          /*在根级元素包含多个ele的时候，有不含v-else的ele则报出打印*/\n          warnOnce(\n            `Component template should contain exactly one root element. ` +\n            `If you are using v-if on multiple elements, ` +\n            `use v-else-if to chain them instead.`\n          )\n        }\n      }\n      /*forbidden标志是否是被禁止的标签（style标签或者script标签）*/\n      if (currentParent && !element.forbidden) {\n        if (element.elseif || element.else) {\n          /*当遇到当前ele有v-else或者v-elseif属性的时候，需要处理if属性，在其上级兄弟元素中必然存在一个v-if属性*/\n          processIfConditions(element, currentParent)\n        } else if (element.slotScope) { // scoped slot\n          currentParent.plain = false\n          /*slot如果没有则是默认的default*/\n          const name = element.slotTarget || '\"default\"'\n          /*\n              scopedSlots中存放slot元素 https://cn.vuejs.org/v2/api/#vm-scopedSlots\n          */\n          ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element\n        } else {\n          currentParent.children.push(element)\n          element.parent = currentParent\n        }\n      }\n      if (!unary) {\n        currentParent = element\n        stack.push(element)\n      } else {\n        endPre(element)\n      }\n      // apply post-transforms\n      for (let i = 0; i < postTransforms.length; i++) {\n        postTransforms[i](element, options)\n      }\n    },\n\n    end () {\n      // remove trailing whitespace\n      /*从stack中取出最后一个ele*/\n      const element = stack[stack.length - 1]\n      /*获取该ele的最后一个子节点*/\n      const lastNode = element.children[element.children.length - 1]\n      /*该子节点是非<pre>标签的文本*/\n      if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {\n        element.children.pop()\n      }\n      // pop stack\n      /*ele出栈*/\n      stack.length -= 1\n      currentParent = stack[stack.length - 1]\n      endPre(element)\n    },\n\n    chars (text: string) {\n      if (!currentParent) {\n        if (process.env.NODE_ENV !== 'production') {\n          if (text === template) {\n            warnOnce(\n              'Component template requires a root element, rather than just text.'\n            )\n          } else if ((text = text.trim())) {\n            warnOnce(\n              `text \"${text}\" outside root element will be ignored.`\n            )\n          }\n        }\n        return\n      }\n      // IE textarea placeholder bug\n      /* istanbul ignore if */\n      if (isIE &&\n          currentParent.tag === 'textarea' &&\n          currentParent.attrsMap.placeholder === text) {\n        return\n      }\n      const children = currentParent.children\n      text = inPre || text.trim()\n        ? isTextTag(currentParent) ? text : decodeHTMLCached(text)\n        // only preserve whitespace if its not right after a starting tag\n        : preserveWhitespace && children.length ? ' ' : ''\n      if (text) {\n        let expression\n        if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {\n          children.push({\n            type: 2,\n            expression,\n            text\n          })\n        } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {\n          children.push({\n            type: 3,\n            text\n          })\n        }\n      }\n    }\n  })\n  return root\n}\n\n/*\n  处理v-pre属性\n  v-pre元素及其子元素被跳过编译\n  https://cn.vuejs.org/v2/api/#v-pre\n*/\nfunction processPre (el) {\n  if (getAndRemoveAttr(el, 'v-pre') != null) {\n    el.pre = true\n  }\n}\n\n/*处理原生属性，将其放入attrs中，以{name, value}的形式*/\nfunction processRawAttrs (el) {\n  const l = el.attrsList.length\n  if (l) {\n    const attrs = el.attrs = new Array(l)\n    for (let i = 0; i < l; i++) {\n      attrs[i] = {\n        name: el.attrsList[i].name,\n        value: JSON.stringify(el.attrsList[i].value)\n      }\n    }\n  } else if (!el.pre) {\n    // non root node in pre blocks with no attributes\n    el.plain = true\n  }\n}\n\n/*处理key属性 https://cn.vuejs.org/v2/api/#key*/\nfunction processKey (el) {\n  const exp = getBindingAttr(el, 'key')\n  if (exp) {\n    if (process.env.NODE_ENV !== 'production' && el.tag === 'template') {\n      warn(`<template> cannot be keyed. Place the key on real elements instead.`)\n    }\n    el.key = exp\n  }\n}\n\n/*处理ref属性 https://cn.vuejs.org/v2/api/#ref*/\nfunction processRef (el) {\n  const ref = getBindingAttr(el, 'ref')\n  if (ref) {\n    el.ref = ref\n    /*\n      检测该元素是否存在一个for循环中。\n      将会沿着parent元素一级一级向上便利寻找是否处于一个for循环中。\n      当 v-for 用于元素或组件的时候，引用信息将是包含 DOM 节点或组件实例的数组。\n    */\n    el.refInFor = checkInFor(el)\n  }\n}\n\n/*匹配v-for属性*/\nfunction processFor (el) {\n  let exp\n  /*取出v-for属性*/\n  if ((exp = getAndRemoveAttr(el, 'v-for'))) {\n    /*匹配v-for中的in以及of 以item in sz为例 inMatch = [ 'item of sz', 'item', 'sz', index: 0, input: 'item of sz' ]*/\n    const inMatch = exp.match(forAliasRE)\n    /*匹配失败则在非生产环境中打印v-for的无效表达式*/\n    if (!inMatch) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `Invalid v-for expression: ${exp}`\n      )\n      return\n    }\n    /*在这里是sz*/\n    el.for = inMatch[2].trim()\n    /*item*/\n    const alias = inMatch[1].trim()\n    /*\n      因为item可能是被括号包裹的，比如(item, index) in sz这样的形式，匹配出这些项\n      例：(item, index)匹配得到结果\n      [ '(item, index, l)',\n      'item',\n      ' index',\n      l,\n      index: 0,\n      input: '(item, index, l);' ]\n    */\n    const iteratorMatch = alias.match(forIteratorRE)\n    if (iteratorMatch) {\n      el.alias = iteratorMatch[1].trim()\n      el.iterator1 = iteratorMatch[2].trim()\n      if (iteratorMatch[3]) {\n        el.iterator2 = iteratorMatch[3].trim()\n      }\n    } else {\n      el.alias = alias\n    }\n  }\n}\n\n/*匹配if属性，分别处理v-if、v-else以及v-else-if属性*/\nfunction processIf (el) {\n  /*取出v-if属性*/\n  const exp = getAndRemoveAttr(el, 'v-if')\n  if (exp) {\n  /*存在v-if属性*/\n    el.if = exp\n    /*在el的ifConditions属性中加入{exp, block}*/\n    addIfCondition(el, {\n      exp: exp,\n      block: el\n    })\n  } else {\n  /*不存在v-if属性*/\n    if (getAndRemoveAttr(el, 'v-else') != null) {\n      el.else = true\n    }\n    const elseif = getAndRemoveAttr(el, 'v-else-if')\n    if (elseif) {\n      el.elseif = elseif\n    }\n  }\n}\n\n/*处理if条件*/\nfunction processIfConditions (el, parent) {\n  /*当遇到当前ele有v-else或者v-elseif属性的时候，需要处理if属性，在其上级兄弟元素中必然存在v-if属性*/\n  const prev = findPrevElement(parent.children)\n  if (prev && prev.if) {\n    addIfCondition(prev, {\n      exp: el.elseif,\n      block: el\n    })\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `v-${el.elseif ? ('else-if=\"' + el.elseif + '\"') : 'else'} ` +\n      `used on element <${el.tag}> without corresponding v-if.`\n    )\n  }\n}\n\n/*找到上一个ele*/\nfunction findPrevElement (children: Array<any>): ASTElement | void {\n  let i = children.length\n  while (i--) {\n    if (children[i].type === 1) {\n      return children[i]\n    } else {\n      if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {\n        warn(\n          `text \"${children[i].text.trim()}\" between v-if and v-else(-if) ` +\n          `will be ignored.`\n        )\n      }\n      children.pop()\n    }\n  }\n}\n\n/*在el的ifConditions属性中加入condition*/\nfunction addIfCondition (el, condition) {\n  if (!el.ifConditions) {\n    el.ifConditions = []\n  }\n  el.ifConditions.push(condition)\n}\n\n/*处理v-once属性，https://cn.vuejs.org/v2/api/#v-once*/\nfunction processOnce (el) {\n  const once = getAndRemoveAttr(el, 'v-once')\n  if (once != null) {\n    el.once = true\n  }\n}\n\n/*处理slot属性 https://cn.vuejs.org/v2/api/#slot*/\nfunction processSlot (el) {\n  if (el.tag === 'slot') {\n    /*获取name特殊属性:name或者bind:name，用作slot的name https://cn.vuejs.org/v2/api/#slot-1*/\n    el.slotName = getBindingAttr(el, 'name')\n    if (process.env.NODE_ENV !== 'production' && el.key) {\n      warn(\n        `\\`key\\` does not work on <slot> because slots are abstract outlets ` +\n        `and can possibly expand into multiple elements. ` +\n        `Use the key on a wrapping element instead.`\n      )\n    }\n  } else {\n    /*获取属性为slot的slot https://cn.vuejs.org/v2/api/#slot*/\n    const slotTarget = getBindingAttr(el, 'slot')\n    if (slotTarget) {\n      el.slotTarget = slotTarget === '\"\"' ? '\"default\"' : slotTarget\n    }\n    if (el.tag === 'template') {\n      el.slotScope = getAndRemoveAttr(el, 'scope')\n    }\n  }\n}\n\n/*处理组件*/\nfunction processComponent (el) {\n  let binding\n  /*获取is属性，用于动态动态组件 https://cn.vuejs.org/v2/api/#is */\n  if ((binding = getBindingAttr(el, 'is'))) {\n    el.component = binding\n  }\n  /*inline-template 内置组件 https://cn.vuejs.org/v2/api/#内置的组件*/\n  if (getAndRemoveAttr(el, 'inline-template') != null) {\n    el.inlineTemplate = true\n  }\n}\n\n/*处理属性*/\nfunction processAttrs (el) {\n  /*获取元素属性列表*/\n  const list = el.attrsList\n  let i, l, name, rawName, value, modifiers, isProp\n  for (i = 0, l = list.length; i < l; i++) {\n    name = rawName = list[i].name\n    value = list[i].value\n    /*匹配v-、@以及:，处理ele的特殊属性*/\n    if (dirRE.test(name)) {\n      /*标记该ele为动态的*/\n      // mark element as dynamic\n      el.hasBindings = true\n      // modifiers\n      /*解析表达式，比如a.b.c.d得到结果{b: true, c: true, d:true}*/\n      modifiers = parseModifiers(name)\n      if (modifiers) {\n        /*得到第一级，比如a.b.c.d得到a，也就是上面的操作把所有子级取出来，这个把第一级取出来*/\n        name = name.replace(modifierRE, '')\n      }\n      /*如果属性是v-bind的*/\n      if (bindRE.test(name)) { // v-bind\n        /*这样处理以后v-bind:aaa得到aaa*/\n        name = name.replace(bindRE, '')\n        /*解析过滤器*/\n        value = parseFilters(value)\n        isProp = false\n        if (modifiers) {\n          /*\n              https://cn.vuejs.org/v2/api/#v-bind\n              这里用来处理v-bind的修饰符\n          */\n          /*.prop - 被用于绑定 DOM 属性。*/\n          if (modifiers.prop) {\n            isProp = true\n             /*将原本用-连接的字符串变成驼峰 aaa-bbb-ccc => aaaBbbCcc*/\n            name = camelize(name)\n            if (name === 'innerHtml') name = 'innerHTML'\n          }\n          /*.camel - (2.1.0+) 将 kebab-case 特性名转换为 camelCase. (从 2.1.0 开始支持)*/\n          if (modifiers.camel) {\n            name = camelize(name)\n          }\n          //.sync (2.3.0+) 语法糖，会扩展成一个更新父组件绑定值的 v-on 侦听器。\n          if (modifiers.sync) {\n            addHandler(\n              el,\n              `update:${camelize(name)}`,\n              genAssignmentCode(value, `$event`)\n            )\n          }\n        }\n        if (isProp || platformMustUseProp(el.tag, el.attrsMap.type, name)) {\n          /*将属性放入ele的props属性中*/\n          addProp(el, name, value)\n        } else {\n          /*将属性放入ele的attr属性中*/\n          addAttr(el, name, value)\n        }\n      } else if (onRE.test(name)) { // v-on\n        /*处理v-on以及bind*/\n        name = name.replace(onRE, '')\n        addHandler(el, name, value, modifiers, false, warn)\n      } else { // normal directives\n        /*去除@、:、v-*/\n        name = name.replace(dirRE, '')\n        // parse arg\n        const argMatch = name.match(argRE)\n        /*比如:fun=\"functionA\"解析出fun=\"functionA\"*/\n        const arg = argMatch && argMatch[1]\n        if (arg) {\n          name = name.slice(0, -(arg.length + 1))\n        }\n        /*将参数加入到ele的directives中去*/\n        addDirective(el, name, rawName, value, arg, modifiers)\n        if (process.env.NODE_ENV !== 'production' && name === 'model') {\n          checkForAliasModel(el, value)\n        }\n      }\n    } else {\n      /*处理常规的字符串属性*/\n      // literal attribute\n      if (process.env.NODE_ENV !== 'production') {\n        const expression = parseText(value, delimiters)\n        if (expression) {\n          /*\n            插入属性内部会被删除，请改用冒号或者v-bind\n            比如应该用<div :id=\"test\">来代替<div id=\"{{test}}\">\n          */\n          warn(\n            `${name}=\"${value}\": ` +\n            'Interpolation inside attributes has been removed. ' +\n            'Use v-bind or the colon shorthand instead. For example, ' +\n            'instead of <div id=\"{{ val }}\">, use <div :id=\"val\">.'\n          )\n        }\n      }\n      /*将属性放入ele的attr属性中*/\n      addAttr(el, name, JSON.stringify(value))\n    }\n  }\n}\n\n/*检测该元素是否存在一个for循环中，将会沿着parent元素一级一级向上便利寻找是否处于一个for循环中。*/\nfunction checkInFor (el: ASTElement): boolean {\n  let parent = el\n  while (parent) {\n    if (parent.for !== undefined) {\n      return true\n    }\n    parent = parent.parent\n  }\n  return false\n}\n\n/*解析表达式，比如a.b.c.d得到结果{b: true, c: true, d:true}*/\nfunction parseModifiers (name: string): Object | void {\n  /*根据点来分开各个级别的正则，比如a.b.c.d解析后可以得到.b .c .d*/\n  const match = name.match(modifierRE)\n  if (match) {\n    const ret = {}\n    match.forEach(m => { ret[m.slice(1)] = true })\n    return ret\n  }\n}\n\nfunction makeAttrsMap (attrs: Array<Object>): Object {\n  const map = {}\n  for (let i = 0, l = attrs.length; i < l; i++) {\n    if (\n      process.env.NODE_ENV !== 'production' &&\n      map[attrs[i].name] && !isIE && !isEdge\n    ) {\n      warn('duplicate attribute: ' + attrs[i].name)\n    }\n    map[attrs[i].name] = attrs[i].value\n  }\n  return map\n}\n\n// for script (e.g. type=\"x/template\") or style, do not decode content\nfunction isTextTag (el): boolean {\n  return el.tag === 'script' || el.tag === 'style'\n}\n\n/*判断是否是被禁止的标签（style标签或者script标签）*/\nfunction isForbiddenTag (el): boolean {\n  return (\n    el.tag === 'style' ||\n    (el.tag === 'script' && (\n      !el.attrsMap.type ||\n      el.attrsMap.type === 'text/javascript'\n    ))\n  )\n}\n\nconst ieNSBug = /^xmlns:NS\\d+/\nconst ieNSPrefix = /^NS\\d+:/\n\n/* istanbul ignore next */\nfunction guardIESVGBug (attrs) {\n  const res = []\n  for (let i = 0; i < attrs.length; i++) {\n    const attr = attrs[i]\n    if (!ieNSBug.test(attr.name)) {\n      attr.name = attr.name.replace(ieNSPrefix, '')\n      res.push(attr)\n    }\n  }\n  return res\n}\n\nfunction checkForAliasModel (el, value) {\n  let _el = el\n  while (_el) {\n    if (_el.for && _el.alias === value) {\n      warn(\n        `<${el.tag} v-model=\"${value}\">: ` +\n        `You are binding v-model directly to a v-for iteration alias. ` +\n        `This will not be able to modify the v-for source array because ` +\n        `writing to the alias is like modifying a function local variable. ` +\n        `Consider using an array of objects and use v-model on an object property instead.`\n      )\n    }\n    _el = _el.parent\n  }\n}\n"
  },
  {
    "path": "vue-src/compiler/parser/text-parser.js",
    "content": "/* @flow */\n\nimport { cached } from 'shared/util'\nimport { parseFilters } from './filter-parser'\n\nconst defaultTagRE = /\\{\\{((?:.|\\n)+?)\\}\\}/g\nconst regexEscapeRE = /[-.*+?^${}()|[\\]\\/\\\\]/g\n\nconst buildRegex = cached(delimiters => {\n  const open = delimiters[0].replace(regexEscapeRE, '\\\\$&')\n  const close = delimiters[1].replace(regexEscapeRE, '\\\\$&')\n  return new RegExp(open + '((?:.|\\\\n)+?)' + close, 'g')\n})\n/*Github:https://github.com/answershuto*/\nexport function parseText (\n  text: string,\n  delimiters?: [string, string]\n): string | void {\n  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE\n  if (!tagRE.test(text)) {\n    return\n  }\n  const tokens = []\n  let lastIndex = tagRE.lastIndex = 0\n  let match, index\n  while ((match = tagRE.exec(text))) {\n    index = match.index\n    // push text token\n    if (index > lastIndex) {\n      tokens.push(JSON.stringify(text.slice(lastIndex, index)))\n    }\n    // tag token\n    const exp = parseFilters(match[1].trim())\n    tokens.push(`_s(${exp})`)\n    lastIndex = index + match[0].length\n  }\n  if (lastIndex < text.length) {\n    tokens.push(JSON.stringify(text.slice(lastIndex)))\n  }\n  return tokens.join('+')\n}\n"
  },
  {
    "path": "vue-src/core/components/index.js",
    "content": "import KeepAlive from './keep-alive'\n\nexport default {\n  KeepAlive\n}\n"
  },
  {
    "path": "vue-src/core/components/keep-alive.js",
    "content": "/* @flow */\n\nimport { isRegExp } from 'shared/util'\nimport { getFirstComponentChild } from 'core/vdom/helpers/index'\n\ntype VNodeCache = { [key: string]: ?VNode };\n\nconst patternTypes: Array<Function> = [String, RegExp]\n\n/* 获取组件名称 */\nfunction getComponentName (opts: ?VNodeComponentOptions): ?string {\n  return opts && (opts.Ctor.options.name || opts.tag)\n}\n\n/* 检测name是否匹配 */\nfunction matches (pattern: string | RegExp, name: string): boolean {\n  if (typeof pattern === 'string') {\n    /* 字符串情况，如a,b,c */\n    return pattern.split(',').indexOf(name) > -1\n  } else if (isRegExp(pattern)) {\n    /* 正则 */\n    return pattern.test(name)\n  }\n  /* istanbul ignore next */\n  return false\n}\n\n/* 修正cache */\nfunction pruneCache (cache: VNodeCache, current: VNode, filter: Function) {\n  for (const key in cache) {\n    /* 取出cache中的vnode */\n    const cachedNode: ?VNode = cache[key]\n    if (cachedNode) {\n      const name: ?string = getComponentName(cachedNode.componentOptions)\n      /* name不符合filter条件的，同时不是目前渲染的vnode时，销毁vnode对应的组件实例（Vue实例），并从cache中移除 */\n      if (name && !filter(name)) {\n        if (cachedNode !== current) {\n          pruneCacheEntry(cachedNode)\n        }\n        cache[key] = null\n      }\n    }\n  }\n}\n\n/* 销毁vnode对应的组件实例（Vue实例） */\nfunction pruneCacheEntry (vnode: ?VNode) {\n  if (vnode) {\n    vnode.componentInstance.$destroy()\n  }\n}\n\n/* keep-alive组件 */\nexport default {\n  name: 'keep-alive',\n  /* 抽象组件 */\n  abstract: true,\n\n  props: {\n    include: patternTypes,\n    exclude: patternTypes\n  },\n\n  created () {\n    /* 缓存对象 */\n    this.cache = Object.create(null)\n  },\n\n  /* destroyed钩子中销毁所有cache中的组件实例 */\n  destroyed () {\n    for (const key in this.cache) {\n      pruneCacheEntry(this.cache[key])\n    }\n  },\n\n  watch: {\n    /* 监视include以及exclude，在被修改的时候对cache进行修正 */\n    include (val: string | RegExp) {\n      pruneCache(this.cache, this._vnode, name => matches(val, name))\n    },\n    exclude (val: string | RegExp) {\n      pruneCache(this.cache, this._vnode, name => !matches(val, name))\n    }\n  },\n\n  render () {\n    /* 得到slot插槽中的第一个组件 */\n    const vnode: VNode = getFirstComponentChild(this.$slots.default)\n\n    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n    if (componentOptions) {\n      // check pattern\n      /* 获取组件名称，优先获取组件的name字段，否则是组件的tag */\n      const name: ?string = getComponentName(componentOptions)\n      /* name不在inlcude中或者在exlude中则直接返回vnode（没有取缓存） */\n      if (name && (\n        (this.include && !matches(this.include, name)) ||\n        (this.exclude && matches(this.exclude, name))\n      )) {\n        return vnode\n      }\n      const key: ?string = vnode.key == null\n        // same constructor may get registered as different local components\n        // so cid alone is not enough (#3269)\n        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n        : vnode.key\n      /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode，还未缓存过则进行缓存 */\n      if (this.cache[key]) {\n        vnode.componentInstance = this.cache[key].componentInstance\n      } else {\n        this.cache[key] = vnode\n      }\n      /* keepAlive标记位 */\n      vnode.data.keepAlive = true\n    }\n    return vnode\n  }\n}\n"
  },
  {
    "path": "vue-src/core/config.js",
    "content": "/* @flow */\n\nimport {\n  no,\n  noop,\n  identity\n} from 'shared/util'\n\nimport { LIFECYCLE_HOOKS } from 'shared/constants'\n\nexport type Config = {\n  // user\n  optionMergeStrategies: { [key: string]: Function };\n  silent: boolean;\n  productionTip: boolean;\n  performance: boolean;\n  devtools: boolean;\n  errorHandler: ?(err: Error, vm: Component, info: string) => void;\n  ignoredElements: Array<string>;\n  keyCodes: { [key: string]: number | Array<number> };\n\n  // platform\n  isReservedTag: (x?: string) => boolean;\n  isReservedAttr: (x?: string) => boolean;\n  parsePlatformTagName: (x: string) => string;\n  isUnknownElement: (x?: string) => boolean;\n  getTagNamespace: (x?: string) => string | void;\n  mustUseProp: (tag: string, type: ?string, name: string) => boolean;\n\n  // legacy\n  _lifecycleHooks: Array<string>;\n};\n\nexport default ({\n  /**\n   * Option merge strategies (used in core/util/options)\n   */\n  optionMergeStrategies: Object.create(null),\n\n  /**\n   * Whether to suppress warnings.\n   */\n  silent: false,\n\n  /**\n   * Show production mode tip message on boot?\n   */\n  productionTip: process.env.NODE_ENV !== 'production',\n\n  /**\n   * Whether to enable devtools\n   */\n  devtools: process.env.NODE_ENV !== 'production',\n\n  /**\n   * Whether to record perf\n   */\n  performance: false,\n\n  /**\n   * Error handler for watcher errors\n   */\n  errorHandler: null,\n\n  /**\n   * Ignore certain custom elements\n   */\n  ignoredElements: [],\n\n  /**\n   * Custom user key aliases for v-on\n   */\n  keyCodes: Object.create(null),\n\n  /**\n   * Check if a tag is reserved so that it cannot be registered as a\n   * component. This is platform-dependent and may be overwritten.\n   */\n  isReservedTag: no,\n\n  /**\n   * Check if an attribute is reserved so that it cannot be used as a component\n   * prop. This is platform-dependent and may be overwritten.\n   */\n  isReservedAttr: no,\n\n  /**\n   * Check if a tag is an unknown element.\n   * Platform-dependent.\n   */\n  isUnknownElement: no,\n\n  /**\n   * Get the namespace of an element\n   */\n  getTagNamespace: noop,\n\n  /**\n   * Parse the real tag name for the specific platform.\n   */\n  parsePlatformTagName: identity,\n\n  /**\n   * Check if an attribute must be bound using property, e.g. value\n   * Platform-dependent.\n   */\n  mustUseProp: no,\n\n  /**\n   * Exposed for legacy reasons\n   */\n  _lifecycleHooks: LIFECYCLE_HOOKS\n}: Config)\n"
  },
  {
    "path": "vue-src/core/global-api/assets.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { ASSET_TYPES } from 'shared/constants'\nimport { warn, isPlainObject } from '../util/index'\n\nexport function initAssetRegisters (Vue: GlobalAPI) {\n  /**\n   * Create asset registration methods.\n   */\n  ASSET_TYPES.forEach(type => {\n    Vue[type] = function (\n      id: string,\n      definition: Function | Object\n    ): Function | Object | void {\n      if (!definition) {\n        return this.options[type + 's'][id]\n      } else {\n        /* istanbul ignore if */\n        if (process.env.NODE_ENV !== 'production') {\n          if (type === 'component' && config.isReservedTag(id)) {\n            warn(\n              'Do not use built-in or reserved HTML elements as component ' +\n              'id: ' + id\n            )\n          }\n        }\n        if (type === 'component' && isPlainObject(definition)) {\n          definition.name = definition.name || id\n          definition = this.options._base.extend(definition)\n        }\n        if (type === 'directive' && typeof definition === 'function') {\n          definition = { bind: definition, update: definition }\n        }\n        this.options[type + 's'][id] = definition\n        return definition\n      }\n    }\n  })\n}\n"
  },
  {
    "path": "vue-src/core/global-api/extend.js",
    "content": "/* @flow */\n\nimport { ASSET_TYPES } from 'shared/constants'\nimport { warn, extend, mergeOptions } from '../util/index'\nimport { defineComputed, proxy } from '../instance/state'\n\nexport function initExtend (Vue: GlobalAPI) {\n  /**\n   * Each instance constructor, including Vue, has a unique\n   * cid. This enables us to create wrapped \"child\n   * constructors\" for prototypal inheritance and cache them.\n   */\n  /*\n    每个构造函数实例（包括Vue本身）都会有一个唯一的cid\n    它为我们能够创造继承创建自构造函数并进行缓存创造了可能\n  */\n  Vue.cid = 0\n  let cid = 1\n\n  /**\n   * Class inheritance\n   */\n   /*\n   使用基础 Vue 构造器，创建一个“子类”。\n   其实就是扩展了基础构造器，形成了一个可复用的有指定选项功能的子构造器。\n   参数是一个包含组件option的对象。  https://cn.vuejs.org/v2/api/#Vue-extend-options\n   */\n  Vue.extend = function (extendOptions: Object): Function {\n    extendOptions = extendOptions || {}\n    /*父类的构造*/\n    const Super = this\n    /*父类的cid*/\n    const SuperId = Super.cid\n    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})\n    /*如果构造函数中已经存在了该cid，则代表已经extend过了，直接返回*/\n    if (cachedCtors[SuperId]) {\n      return cachedCtors[SuperId]\n    }\n\n    const name = extendOptions.name || Super.options.name\n    if (process.env.NODE_ENV !== 'production') {\n      /*name只能包含字母与连字符*/\n      if (!/^[a-zA-Z][\\w-]*$/.test(name)) {\n        warn(\n          'Invalid component name: \"' + name + '\". Component names ' +\n          'can only contain alphanumeric characters and the hyphen, ' +\n          'and must start with a letter.'\n        )\n      }\n    }\n\n    /*\n      Sub构造函数其实就一个_init方法，这跟Vue的构造方法是一致的，在_init中处理各种数据初始化、生命周期等。\n      因为Sub作为一个Vue的扩展构造器，所以基础的功能还是需要保持一致，跟Vue构造器一样在构造函数中初始化_init。\n    */\n    const Sub = function VueComponent (options) {\n      this._init(options)\n    }\n    /*继承父类*/\n    Sub.prototype = Object.create(Super.prototype)\n    /*构造函数*/\n    Sub.prototype.constructor = Sub\n    /*创建一个新的cid*/\n    Sub.cid = cid++\n    /*将父组件的option与子组件的合并到一起(Vue有一个cid为0的基类，即Vue本身，会将一些默认初始化的option何入)*/\n    Sub.options = mergeOptions(\n      Super.options,\n      extendOptions\n    )\n    /*es6语法，super为父类构造*/\n    Sub['super'] = Super\n\n    // For props and computed properties, we define the proxy getters on\n    // the Vue instances at extension time, on the extended prototype. This\n    // avoids Object.defineProperty calls for each instance created.\n    /*在扩展时，我们将计算属性以及props通过代理绑定在Vue实例上（也就是vm），这也避免了Object.defineProperty被每一个实例调用*/\n    if (Sub.options.props) {\n      /*初始化props，将option中的_props代理到vm上*/\n      initProps(Sub)\n    }\n    if (Sub.options.computed) {\n      /*处理计算属性，给计算属性设置defineProperty并绑定在vm上*/\n      initComputed(Sub)\n    }\n\n    // allow further extension/mixin/plugin usage\n    /*加入extend、mixin以及use方法，允许将来继续为该组件提供扩展、混合或者插件*/\n    Sub.extend = Super.extend\n    Sub.mixin = Super.mixin\n    Sub.use = Super.use\n\n    // create asset registers, so extended classes\n    // can have their private assets too.\n    /*使得Sub也会拥有父类的私有选项（directives、filters、components）*/\n    ASSET_TYPES.forEach(function (type) {\n      Sub[type] = Super[type]\n    })\n    // enable recursive self-lookup\n    /*把组件自身也加入components中，为递归自身提供可能（递归组件也会查找components是否存在当前组件，也就是自身）*/\n    if (name) {\n      Sub.options.components[name] = Sub\n    }\n\n    // keep a reference to the super options at extension time.\n    // later at instantiation we can check if Super's options have\n    // been updated.\n    /*保存一个父类的options，此后我们可以用来检测父类的options是否已经被更新*/\n    Sub.superOptions = Super.options，\n    /*extendOptions存储起来*/\n    Sub.extendOptions = extendOptions\n    /*保存一份option，extend的作用是将Sub.options中的所有属性放入{}中*/\n    Sub.sealedOptions = extend({}, Sub.options)\n\n    // cache constructor\n    /*缓存构造函数（用cid），防止重复extend*/\n    cachedCtors[SuperId] = Sub\n    return Sub\n  }\n}\n\n/*初始化props，将option中的_props代理到vm上*/\nfunction initProps (Comp) {\n  const props = Comp.options.props\n  for (const key in props) {\n    proxy(Comp.prototype, `_props`, key)\n  }\n}\n\n/*处理计算属性，给计算属性设置defineProperty并绑定在vm上*/\nfunction initComputed (Comp) {\n  const computed = Comp.options.computed\n  for (const key in computed) {\n    defineComputed(Comp.prototype, key, computed[key])\n  }\n}\n"
  },
  {
    "path": "vue-src/core/global-api/index.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { initUse } from './use'\nimport { initMixin } from './mixin'\nimport { initExtend } from './extend'\nimport { initAssetRegisters } from './assets'\nimport { set, del } from '../observer/index'\nimport { ASSET_TYPES } from 'shared/constants'\nimport builtInComponents from '../components/index'\n\nimport {\n  warn,\n  extend,\n  nextTick,\n  mergeOptions,\n  defineReactive\n} from '../util/index'\n\nexport function initGlobalAPI (Vue: GlobalAPI) {\n  // config\n  const configDef = {}\n  configDef.get = () => config\n  if (process.env.NODE_ENV !== 'production') {\n    configDef.set = () => {\n      warn(\n        'Do not replace the Vue.config object, set individual fields instead.'\n      )\n    }\n  }\n  Object.defineProperty(Vue, 'config', configDef)\n\n  // exposed util methods.\n  // NOTE: these are not considered part of the public API - avoid relying on\n  // them unless you are aware of the risk.\n  Vue.util = {\n    warn,\n    extend,\n    mergeOptions,\n    defineReactive\n  }\n\n  Vue.set = set\n  Vue.delete = del\n  Vue.nextTick = nextTick\n\n  Vue.options = Object.create(null)\n  ASSET_TYPES.forEach(type => {\n    Vue.options[type + 's'] = Object.create(null)\n  })\n\n  // this is used to identify the \"base\" constructor to extend all plain-object\n  // components with in Weex's multi-instance scenarios.\n  /*_base被用来标识基本构造函数（也就是Vue），以便在多场景下添加组件扩展*/\n  Vue.options._base = Vue\n\n  extend(Vue.options.components, builtInComponents)\n\n  initUse(Vue)\n  initMixin(Vue)\n  initExtend(Vue)\n  initAssetRegisters(Vue)\n}\n"
  },
  {
    "path": "vue-src/core/global-api/mixin.js",
    "content": "/* @flow */\n\nimport { mergeOptions } from '../util/index'\n\n/*初始化mixin*/\nexport function initMixin (Vue: GlobalAPI) {\n    /*https://cn.vuejs.org/v2/api/#Vue-mixin*/\n  Vue.mixin = function (mixin: Object) {\n    /*mergeOptions合并optiuons*/\n    this.options = mergeOptions(this.options, mixin)\n  }\n}\n"
  },
  {
    "path": "vue-src/core/global-api/use.js",
    "content": "/* @flow */\n\nimport { toArray } from '../util/index'\n\n/*初始化use*/\nexport function initUse (Vue: GlobalAPI) {\n  /*https://cn.vuejs.org/v2/api/#Vue-use*/\n  Vue.use = function (plugin: Function | Object) {\n    /* istanbul ignore if */\n    /*标识位检测该插件是否已经被安装*/\n    if (plugin.installed) {\n      return\n    }\n    // additional parameters\n    const args = toArray(arguments, 1)\n    /*a*/\n    args.unshift(this)\n    if (typeof plugin.install === 'function') {\n      /*install执行插件安装*/\n      plugin.install.apply(plugin, args)\n    } else if (typeof plugin === 'function') {\n      plugin.apply(null, args)\n    }\n    plugin.installed = true\n    return this\n  }\n}\n"
  },
  {
    "path": "vue-src/core/index.js",
    "content": "import Vue from './instance/index'\nimport { initGlobalAPI } from './global-api/index'\nimport { isServerRendering } from 'core/util/env'\n\ninitGlobalAPI(Vue)\n\nObject.defineProperty(Vue.prototype, '$isServer', {\n  get: isServerRendering\n})\n\nVue.version = '__VERSION__'\n\nexport default Vue\n"
  },
  {
    "path": "vue-src/core/instance/events.js",
    "content": "/* @flow */\n\nimport { updateListeners } from '../vdom/helpers/index'\nimport { toArray, tip, hyphenate, formatComponentName } from '../util/index'\n\n/*初始化事件*/\nexport function initEvents (vm: Component) {\n  /*在vm上创建一个_events对象，用来存放事件。*/\n  vm._events = Object.create(null)\n  /*这个bool标志位来表明是否存在钩子，而不需要通过哈希表的方法来查找是否有钩子，这样做可以减少不必要的开销，优化性能。*/\n  vm._hasHookEvent = false\n  // init parent attached events\n  /*初始化父组件attach的事件*/\n  const listeners = vm.$options._parentListeners\n  if (listeners) {\n    updateComponentListeners(vm, listeners)\n  }\n}\n\nlet target: Component\n\n/*有once的时候注册一个只会触发一次的方法，没有once的时候注册一个事件方法*/\nfunction add (event, fn, once) {\n  if (once) {\n    target.$once(event, fn)\n  } else {\n    target.$on(event, fn)\n  }\n}\n\n/*销毁一个事件方法*/\nfunction remove (event, fn) {\n  target.$off(event, fn)\n}\n\n/*更新组件的监听事件*/\nexport function updateComponentListeners (\n  vm: Component,\n  listeners: Object,\n  oldListeners: ?Object\n) {\n  target = vm\n  updateListeners(listeners, oldListeners || {}, add, remove, vm)\n}\n/*Github:https://github.com/answershuto*/\n/*为Vue原型加入操作事件的方法*/\nexport function eventsMixin (Vue: Class<Component>) {\n  const hookRE = /^hook:/\n\n  /*在vm实例上绑定事件方法*/\n  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {\n    const vm: Component = this\n\n    /*如果是数组的时候，则递归$on，为每一个成员都绑定上方法*/\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$on(event[i], fn)\n      }\n    } else {\n      (vm._events[event] || (vm._events[event] = [])).push(fn)\n      // optimize hook:event cost by using a boolean flag marked at registration\n      // instead of a hash lookup\n      /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子，而不需要通过哈希表的方法来查找是否有钩子，这样做可以减少不必要的开销，优化性能。*/\n      if (hookRE.test(event)) {\n        vm._hasHookEvent = true\n      }\n    }\n    return vm\n  }\n\n  /*注册一个只执行一次的事件方法*/\n  Vue.prototype.$once = function (event: string, fn: Function): Component {\n    const vm: Component = this\n    function on () {\n      /*在第一次执行的时候将该事件销毁*/\n      vm.$off(event, on)\n      /*执行注册的方法*/\n      fn.apply(vm, arguments)\n    }\n    on.fn = fn\n    vm.$on(event, on)\n    return vm\n  }\n\n  /*注销一个事件，如果不传参则注销所有事件，如果只传event名则注销该event下的所有方法*/\n  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {\n    const vm: Component = this\n    // all\n    /*如果不传参数则注销所有事件*/\n    if (!arguments.length) {\n      vm._events = Object.create(null)\n      return vm\n    }\n    // array of events\n    /*如果event是数组则递归注销事件*/\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$off(event[i], fn)\n      }\n      return vm\n    }\n    // specific event\n    const cbs = vm._events[event]\n    /*本身不存在该事件则直接返回*/\n    if (!cbs) {\n      return vm\n    }\n    /*如果只传了event参数则注销该event方法下的所有方法*/\n    if (arguments.length === 1) {\n      vm._events[event] = null\n      return vm\n    }\n    // specific handler\n    /*遍历寻找对应方法并删除*/\n    let cb\n    let i = cbs.length\n    while (i--) {\n      cb = cbs[i]\n      if (cb === fn || cb.fn === fn) {\n        cbs.splice(i, 1)\n        break\n      }\n    }\n    return vm\n  }\n\n  /*触发一个事件方法*/\n  Vue.prototype.$emit = function (event: string): Component {\n    const vm: Component = this\n    if (process.env.NODE_ENV !== 'production') {\n      const lowerCaseEvent = event.toLowerCase()\n      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {\n        tip(\n          `Event \"${lowerCaseEvent}\" is emitted in component ` +\n          `${formatComponentName(vm)} but the handler is registered for \"${event}\". ` +\n          `Note that HTML attributes are case-insensitive and you cannot use ` +\n          `v-on to listen to camelCase events when using in-DOM templates. ` +\n          `You should probably use \"${hyphenate(event)}\" instead of \"${event}\".`\n        )\n      }\n    }\n    let cbs = vm._events[event]\n    if (cbs) {\n      /*将类数组的对象转换成数组*/\n      cbs = cbs.length > 1 ? toArray(cbs) : cbs\n      const args = toArray(arguments, 1)\n      /*遍历执行*/\n      for (let i = 0, l = cbs.length; i < l; i++) {\n        cbs[i].apply(vm, args)\n      }\n    }\n    return vm\n  }\n}\n"
  },
  {
    "path": "vue-src/core/instance/index.js",
    "content": "import { initMixin } from './init'\nimport { stateMixin } from './state'\nimport { renderMixin } from './render'\nimport { eventsMixin } from './events'\nimport { lifecycleMixin } from './lifecycle'\nimport { warn } from '../util/index'\n/*Github:https://github.com/answershuto*/\nfunction Vue (options) {\n  if (process.env.NODE_ENV !== 'production' &&\n    !(this instanceof Vue)) {\n    warn('Vue is a constructor and should be called with the `new` keyword')\n  }\n  /*初始化*/\n  this._init(options)\n}\n\ninitMixin(Vue)\nstateMixin(Vue)\neventsMixin(Vue)\nlifecycleMixin(Vue)\nrenderMixin(Vue)\n\nexport default Vue\n"
  },
  {
    "path": "vue-src/core/instance/init.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { initProxy } from './proxy'\nimport { initState } from './state'\nimport { initRender } from './render'\nimport { initEvents } from './events'\nimport { mark, measure } from '../util/perf'\nimport { initLifecycle, callHook } from './lifecycle'\nimport { initProvide, initInjections } from './inject'\nimport { extend, mergeOptions, formatComponentName } from '../util/index'\n/*Github:https://github.com/answershuto*/\nlet uid = 0\n\n/*initMixin就做了一件事情，在Vue的原型上增加_init方法，构造Vue实例的时候会调用这个_init方法来初始化Vue实例*/\nexport function initMixin (Vue: Class<Component>) {\n  Vue.prototype._init = function (options?: Object) {\n    const vm: Component = this\n    // a uid\n    vm._uid = uid++\n\n    let startTag, endTag\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      startTag = `vue-perf-init:${vm._uid}`\n      endTag = `vue-perf-end:${vm._uid}`\n      mark(startTag)\n    }\n\n    // a flag to avoid this being observed\n    /*一个防止vm实例自身被观察的标志位*/\n    vm._isVue = true\n    // merge options\n    if (options && options._isComponent) {\n      // optimize internal component instantiation\n      // since dynamic options merging is pretty slow, and none of the\n      // internal component options needs special treatment.\n      initInternalComponent(vm, options)\n    } else {\n      vm.$options = mergeOptions(\n        resolveConstructorOptions(vm.constructor),\n        options || {},\n        vm\n      )\n    }\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      initProxy(vm)\n    } else {\n      vm._renderProxy = vm\n    }\n    // expose real self\n    vm._self = vm\n    /*初始化生命周期*/\n    initLifecycle(vm)\n    /*初始化事件*/\n    initEvents(vm)\n    /*初始化render*/\n    initRender(vm)\n    /*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/\n    callHook(vm, 'beforeCreate')\n    initInjections(vm) // resolve injections before data/props\n    /*初始化props、methods、data、computed与watch*/\n    initState(vm)\n    initProvide(vm) // resolve provide after data/props\n    /*调用created钩子函数并且触发created钩子事件*/\n    callHook(vm, 'created')\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      /*格式化组件名*/\n      vm._name = formatComponentName(vm, false)\n      mark(endTag)\n      measure(`${vm._name} init`, startTag, endTag)\n    }\n\n    if (vm.$options.el) {\n      /*挂载组件*/\n      vm.$mount(vm.$options.el)\n    }\n  }\n}\n\nfunction initInternalComponent (vm: Component, options: InternalComponentOptions) {\n  const opts = vm.$options = Object.create(vm.constructor.options)\n  // doing this because it's faster than dynamic enumeration.\n  opts.parent = options.parent\n  opts.propsData = options.propsData\n  opts._parentVnode = options._parentVnode\n  opts._parentListeners = options._parentListeners\n  opts._renderChildren = options._renderChildren\n  opts._componentTag = options._componentTag\n  opts._parentElm = options._parentElm\n  opts._refElm = options._refElm\n  if (options.render) {\n    opts.render = options.render\n    opts.staticRenderFns = options.staticRenderFns\n  }\n}\n\nexport function resolveConstructorOptions (Ctor: Class<Component>) {\n  let options = Ctor.options\n  /*如果存在父类的时候*/\n  if (Ctor.super) {\n    /*对其父类进行resolveConstructorOptions，获取父类的options*/\n    const superOptions = resolveConstructorOptions(Ctor.super)\n    /*之前已经缓存起来的父类的options，用以检测是否更新*/\n    const cachedSuperOptions = Ctor.superOptions\n    /*对比当前父类的option以及缓存中的option，两个不一样则代表已经被更新*/\n    if (superOptions !== cachedSuperOptions) {\n      // super option changed,\n      // need to resolve new options.\n      /*父类的opiton已经被改变，需要去处理新的option*/\n\n      /*把新的option缓存起来*/\n      Ctor.superOptions = superOptions\n      // check if there are any late-modified/attached options (#4976)\n      const modifiedOptions = resolveModifiedOptions(Ctor)\n      // update base extend options\n      if (modifiedOptions) {\n        extend(Ctor.extendOptions, modifiedOptions)\n      }\n      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)\n      if (options.name) {\n        options.components[options.name] = Ctor\n      }\n    }\n  }\n  return options\n}\n\nfunction resolveModifiedOptions (Ctor: Class<Component>): ?Object {\n  let modified\n  const latest = Ctor.options\n  const extended = Ctor.extendOptions\n  const sealed = Ctor.sealedOptions\n  for (const key in latest) {\n    if (latest[key] !== sealed[key]) {\n      if (!modified) modified = {}\n      modified[key] = dedupe(latest[key], extended[key], sealed[key])\n    }\n  }\n  return modified\n}\n\nfunction dedupe (latest, extended, sealed) {\n  // compare latest and sealed to ensure lifecycle hooks won't be duplicated\n  // between merges\n  if (Array.isArray(latest)) {\n    const res = []\n    sealed = Array.isArray(sealed) ? sealed : [sealed]\n    extended = Array.isArray(extended) ? extended : [extended]\n    for (let i = 0; i < latest.length; i++) {\n      // push original options and not sealed options to exclude duplicated options\n      if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {\n        res.push(latest[i])\n      }\n    }\n    return res\n  } else {\n    return latest\n  }\n}\n"
  },
  {
    "path": "vue-src/core/instance/inject.js",
    "content": "/* @flow */\n/*Github:https://github.com/answershuto*/\nimport { hasSymbol } from 'core/util/env'\nimport { warn } from '../util/index'\nimport { defineReactive } from '../observer/index'\n/*Github:https://github.com/answershuto*/\nexport function initProvide (vm: Component) {\n  const provide = vm.$options.provide\n  if (provide) {\n    vm._provided = typeof provide === 'function'\n      ? provide.call(vm)\n      : provide\n  }\n}\n\nexport function initInjections (vm: Component) {\n  const result = resolveInject(vm.$options.inject, vm)\n  if (result) {\n    Object.keys(result).forEach(key => {\n      /* istanbul ignore else */\n      /*为对象defineProperty上在变化时通知的属性*/\n      if (process.env.NODE_ENV !== 'production') {\n        defineReactive(vm, key, result[key], () => {\n          warn(\n            `Avoid mutating an injected value directly since the changes will be ` +\n            `overwritten whenever the provided component re-renders. ` +\n            `injection being mutated: \"${key}\"`,\n            vm\n          )\n        })\n      } else {\n        defineReactive(vm, key, result[key])\n      }\n    })\n  }\n}\n\nexport function resolveInject (inject: any, vm: Component): ?Object {\n  if (inject) {\n    // inject is :any because flow is not smart enough to figure out cached\n    // isArray here\n    const isArray = Array.isArray(inject)\n    const result = Object.create(null)\n    const keys = isArray\n      ? inject\n      : hasSymbol\n        ? Reflect.ownKeys(inject)\n        : Object.keys(inject)\n\n    for (let i = 0; i < keys.length; i++) {\n      const key = keys[i]\n      const provideKey = isArray ? key : inject[key]\n      let source = vm\n      while (source) {\n        if (source._provided && provideKey in source._provided) {\n          result[key] = source._provided[provideKey]\n          break\n        }\n        source = source.$parent\n      }\n    }\n    return result\n  }\n}\n"
  },
  {
    "path": "vue-src/core/instance/lifecycle.js",
    "content": "/* @flow */\n/*Github:https://github.com/answershuto*/\nimport config from '../config'\nimport Watcher from '../observer/watcher'\nimport { mark, measure } from '../util/perf'\nimport { createEmptyVNode } from '../vdom/vnode'\nimport { observerState } from '../observer/index'\nimport { updateComponentListeners } from './events'\nimport { resolveSlots } from './render-helpers/resolve-slots'\n\nimport {\n  warn,\n  noop,\n  remove,\n  handleError,\n  emptyObject,\n  validateProp\n} from '../util/index'\n\nexport let activeInstance: any = null\n\n/*初始化生命周期*/\nexport function initLifecycle (vm: Component) {\n  const options = vm.$options\n\n  // locate first non-abstract parent\n  /* 将vm对象存储到parent组件中（保证parent组件是非抽象组件，比如keep-alive） */\n  let parent = options.parent\n  if (parent && !options.abstract) {\n    while (parent.$options.abstract && parent.$parent) {\n      parent = parent.$parent\n    }\n    parent.$children.push(vm)\n  }\n\n  vm.$parent = parent\n  vm.$root = parent ? parent.$root : vm\n\n  vm.$children = []\n  vm.$refs = {}\n\n  vm._watcher = null\n  vm._inactive = null\n  vm._directInactive = false\n  vm._isMounted = false\n  vm._isDestroyed = false\n  vm._isBeingDestroyed = false\n}\n\nexport function lifecycleMixin (Vue: Class<Component>) {\n  /*更新节点*/\n  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n    const vm: Component = this\n    /*如果已经该组件已经挂载过了则代表进入这个步骤是个更新的过程，触发beforeUpdate钩子*/\n    if (vm._isMounted) {\n      callHook(vm, 'beforeUpdate')\n    }\n    const prevEl = vm.$el\n    const prevVnode = vm._vnode\n    const prevActiveInstance = activeInstance\n    activeInstance = vm\n    vm._vnode = vnode\n    // Vue.prototype.__patch__ is injected in entry points\n    // based on the rendering backend used.\n    /*基于后端渲染Vue.prototype.__patch__被用来作为一个入口*/\n    if (!prevVnode) {\n      // initial render\n      vm.$el = vm.__patch__(\n        vm.$el, vnode, hydrating, false /* removeOnly */,\n        vm.$options._parentElm,\n        vm.$options._refElm\n      )\n    } else {\n      // updates\n      vm.$el = vm.__patch__(prevVnode, vnode)\n    }\n    activeInstance = prevActiveInstance\n    // update __vue__ reference\n    /*更新新的实例对象的__vue__*/\n    if (prevEl) {\n      prevEl.__vue__ = null\n    }\n    if (vm.$el) {\n      vm.$el.__vue__ = vm\n    }\n    // if parent is an HOC, update its $el as well\n    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n      vm.$parent.$el = vm.$el\n    }\n    // updated hook is called by the scheduler to ensure that children are\n    // updated in a parent's updated hook.\n  }\n\n  Vue.prototype.$forceUpdate = function () {\n    const vm: Component = this\n    if (vm._watcher) {\n      vm._watcher.update()\n    }\n  }\n\n  Vue.prototype.$destroy = function () {\n    const vm: Component = this\n    if (vm._isBeingDestroyed) {\n      return\n    }\n    /* 调用beforeDestroy钩子 */\n    callHook(vm, 'beforeDestroy')\n    /* 标志位 */\n    vm._isBeingDestroyed = true\n    // remove self from parent\n    const parent = vm.$parent\n    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {\n      remove(parent.$children, vm)\n    }\n    // teardown watchers\n    /* 该组件下的所有Watcher从其所在的Dep中释放 */\n    if (vm._watcher) {\n      vm._watcher.teardown()\n    }\n    let i = vm._watchers.length\n    while (i--) {\n      vm._watchers[i].teardown()\n    }\n    // remove reference from data ob\n    // frozen object may not have observer.\n    if (vm._data.__ob__) {\n      vm._data.__ob__.vmCount--\n    }\n    // call the last hook...\n    vm._isDestroyed = true\n    // invoke destroy hooks on current rendered tree\n    vm.__patch__(vm._vnode, null)\n    // fire destroyed hook\n    /* 调用destroyed钩子 */\n    callHook(vm, 'destroyed')\n    // turn off all instance listeners.\n    /* 移除所有事件监听 */\n    vm.$off()\n    // remove __vue__ reference\n    if (vm.$el) {\n      vm.$el.__vue__ = null\n    }\n    // remove reference to DOM nodes (prevents leak)\n    vm.$options._parentElm = vm.$options._refElm = null\n  }\n}\n\n/*挂载组件*/\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  vm.$el = el\n  if (!vm.$options.render) {\n    /*render函数不存在的时候创建一个空的VNode节点*/\n    vm.$options.render = createEmptyVNode\n    if (process.env.NODE_ENV !== 'production') {\n      /* istanbul ignore if */\n      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||\n        vm.$options.el || el) {\n        warn(\n          'You are using the runtime-only build of Vue where the template ' +\n          'compiler is not available. Either pre-compile the templates into ' +\n          'render functions, or use the compiler-included build.',\n          vm\n        )\n      } else {\n        warn(\n          'Failed to mount component: template or render function not defined.',\n          vm\n        )\n      }\n    }\n  }\n  /*触发beforeMount钩子*/\n  callHook(vm, 'beforeMount')\n\n  /*updateComponent作为Watcher对象的getter函数，用来依赖收集*/\n  let updateComponent\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    updateComponent = () => {\n      const name = vm._name\n      const id = vm._uid\n      const startTag = `vue-perf-start:${id}`\n      const endTag = `vue-perf-end:${id}`\n\n      mark(startTag)\n      const vnode = vm._render()\n      mark(endTag)\n      measure(`${name} render`, startTag, endTag)\n\n      mark(startTag)\n      vm._update(vnode, hydrating)\n      mark(endTag)\n      measure(`${name} patch`, startTag, endTag)\n    }\n  } else {\n    updateComponent = () => {\n      vm._update(vm._render(), hydrating)\n    }\n  }\n\n  /*这里对该vm注册一个Watcher实例，Watcher的getter为updateComponent函数，用于触发所有渲染所需要用到的数据的getter，进行依赖收集，该Watcher实例会存在所有渲染所需数据的闭包Dep中*/\n  vm._watcher = new Watcher(vm, updateComponent, noop)\n  hydrating = false\n\n  // manually mounted instance, call mounted on self\n  // mounted is called for render-created child components in its inserted hook\n  if (vm.$vnode == null) {\n    /*标志位，代表该组件已经挂载*/\n    vm._isMounted = true\n    /*调用mounted钩子*/\n    callHook(vm, 'mounted')\n  }\n  return vm\n}\n\nexport function updateChildComponent (\n  vm: Component,\n  propsData: ?Object,\n  listeners: ?Object,\n  parentVnode: VNode,\n  renderChildren: ?Array<VNode>\n) {\n  // determine whether component has slot children\n  // we need to do this before overwriting $options._renderChildren\n  const hasChildren = !!(\n    renderChildren ||               // has new static slots\n    vm.$options._renderChildren ||  // has old static slots\n    parentVnode.data.scopedSlots || // has new scoped slots\n    vm.$scopedSlots !== emptyObject // has old scoped slots\n  )\n\n  vm.$options._parentVnode = parentVnode\n  vm.$vnode = parentVnode // update vm's placeholder node without re-render\n  if (vm._vnode) { // update child tree's parent\n    vm._vnode.parent = parentVnode\n  }\n  vm.$options._renderChildren = renderChildren\n\n  // update props\n  if (propsData && vm.$options.props) {\n    observerState.shouldConvert = false\n    if (process.env.NODE_ENV !== 'production') {\n      observerState.isSettingProps = true\n    }\n    const props = vm._props\n    const propKeys = vm.$options._propKeys || []\n    for (let i = 0; i < propKeys.length; i++) {\n      const key = propKeys[i]\n      props[key] = validateProp(key, vm.$options.props, propsData, vm)\n    }\n    observerState.shouldConvert = true\n    if (process.env.NODE_ENV !== 'production') {\n      observerState.isSettingProps = false\n    }\n    // keep a copy of raw propsData\n    vm.$options.propsData = propsData\n  }\n  // update listeners\n  if (listeners) {\n    const oldListeners = vm.$options._parentListeners\n    vm.$options._parentListeners = listeners\n    updateComponentListeners(vm, listeners, oldListeners)\n  }\n  // resolve slots + force update if has children\n  if (hasChildren) {\n    vm.$slots = resolveSlots(renderChildren, parentVnode.context)\n    vm.$forceUpdate()\n  }\n}\n\n/*判断组件是否已经是active的*/\nfunction isInInactiveTree (vm) {\n  while (vm && (vm = vm.$parent)) {\n    if (vm._inactive) return true\n  }\n  return false\n}\n\n/*使子组件状态都改编成active同时调用activated钩子*/\nexport function activateChildComponent (vm: Component, direct?: boolean) {\n  if (direct) {\n    vm._directInactive = false\n    if (isInInactiveTree(vm)) {\n      return\n    }\n  } else if (vm._directInactive) {\n    return\n  }\n  if (vm._inactive || vm._inactive === null) {\n    vm._inactive = false\n    /*递归*/\n    for (let i = 0; i < vm.$children.length; i++) {\n      activateChildComponent(vm.$children[i])\n    }\n    /*触发actived钩子*/\n    callHook(vm, 'activated')\n  }\n}\n\nexport function deactivateChildComponent (vm: Component, direct?: boolean) {\n  if (direct) {\n    vm._directInactive = true\n    if (isInInactiveTree(vm)) {\n      return\n    }\n  }\n  if (!vm._inactive) {\n    vm._inactive = true\n    for (let i = 0; i < vm.$children.length; i++) {\n      deactivateChildComponent(vm.$children[i])\n    }\n    callHook(vm, 'deactivated')\n  }\n}\n\n/*调用钩子函数并且触发钩子事件*/\nexport function callHook (vm: Component, hook: string) {\n  const handlers = vm.$options[hook]\n  if (handlers) {\n    for (let i = 0, j = handlers.length; i < j; i++) {\n      try {\n        handlers[i].call(vm)\n      } catch (e) {\n        handleError(e, vm, `${hook} hook`)\n      }\n    }\n  }\n  if (vm._hasHookEvent) {\n    vm.$emit('hook:' + hook)\n  }\n}\n"
  },
  {
    "path": "vue-src/core/instance/proxy.js",
    "content": "/* not type checking this file because flow doesn't play well with Proxy */\n/*Github:https://github.com/answershuto*/\nimport config from 'core/config'\nimport { warn, makeMap } from '../util/index'\n/*Github:https://github.com/answershuto*/\nlet initProxy\n\nif (process.env.NODE_ENV !== 'production') {\n  const allowedGlobals = makeMap(\n    'Infinity,undefined,NaN,isFinite,isNaN,' +\n    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +\n    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +\n    'require' // for Webpack/Browserify\n  )\n\n  const warnNonPresent = (target, key) => {\n    warn(\n      `Property or method \"${key}\" is not defined on the instance but ` +\n      `referenced during render. Make sure to declare reactive data ` +\n      `properties in the data option.`,\n      target\n    )\n  }\n\n  const hasProxy =\n    typeof Proxy !== 'undefined' &&\n    Proxy.toString().match(/native code/)\n\n  if (hasProxy) {\n    const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta')\n    /*为config.keyCodes设置一个代理，在set赋值的时候先从isBuiltInModifier里检查，不存在再赋值*/\n    config.keyCodes = new Proxy(config.keyCodes, {\n      set (target, key, value) {\n        if (isBuiltInModifier(key)) {\n          warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)\n          return false\n        } else {\n          target[key] = value\n          return true\n        }\n      }\n    })\n  }\n\n  const hasHandler = {\n    has (target, key) {\n      const has = key in target\n      const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'\n      if (!has && !isAllowed) {\n        warnNonPresent(target, key)\n      }\n      return has || !isAllowed\n    }\n  }\n\n  const getHandler = {\n    get (target, key) {\n      if (typeof key === 'string' && !(key in target)) {\n        warnNonPresent(target, key)\n      }\n      return target[key]\n    }\n  }\n\n  initProxy = function initProxy (vm) {\n    if (hasProxy) {\n      // determine which proxy handler to use\n      const options = vm.$options\n      const handlers = options.render && options.render._withStripped\n        ? getHandler\n        : hasHandler\n      vm._renderProxy = new Proxy(vm, handlers)\n    } else {\n      vm._renderProxy = vm\n    }\n  }\n}\n\nexport { initProxy }\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/bind-object-props.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\nimport { isObject, warn, toObject } from 'core/util/index'\n\n/**\n * Runtime helper for merging v-bind=\"object\" into a VNode's data.\n */\n /*合并v-bind指令到VNode中*/\nexport function bindObjectProps (\n  data: any,\n  tag: string,\n  value: any,\n  asProp?: boolean\n): VNodeData {\n  if (value) {\n    if (!isObject(value)) {\n      /*v-bind必须提供一个Object或者Array作为参数*/\n      process.env.NODE_ENV !== 'production' && warn(\n        'v-bind without argument expects an Object or Array value',\n        this\n      )\n    } else {\n      if (Array.isArray(value)) {\n        /*合并Array数组中的每一个对象到一个新的Object中*/\n        value = toObject(value)\n      }\n      let hash\n      for (const key in value) {\n        if (key === 'class' || key === 'style') {\n          hash = data\n        } else {\n          const type = data.attrs && data.attrs.type\n          hash = asProp || config.mustUseProp(tag, type, key)\n            ? data.domProps || (data.domProps = {})\n            : data.attrs || (data.attrs = {})\n        }\n        if (!(key in hash)) {\n          hash[key] = value[key]\n        }\n      }\n    }\n  }\n  return data\n}\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/check-keycodes.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\n\n/**\n * Runtime helper for checking keyCodes from config.\n */\n /*从config配置中检查eventKeyCode是否存在*/\nexport function checkKeyCodes (\n  eventKeyCode: number,\n  key: string,\n  builtInAlias: number | Array<number> | void\n): boolean {\n  const keyCodes = config.keyCodes[key] || builtInAlias\n  if (Array.isArray(keyCodes)) {\n    return keyCodes.indexOf(eventKeyCode) === -1\n  } else {\n    return keyCodes !== eventKeyCode\n  }\n}\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/render-list.js",
    "content": "/* @flow */\n\nimport { isObject } from 'core/util/index'\n\n/**\n * Runtime helper for rendering v-for lists.\n */\n /*处理v-for列表渲染*/\nexport function renderList (\n  val: any,\n  render: () => VNode\n): ?Array<VNode> {\n  /*根据类型循环render*/\n  let ret: ?Array<VNode>, i, l, keys, key\n  if (Array.isArray(val) || typeof val === 'string') {\n    ret = new Array(val.length)\n    for (i = 0, l = val.length; i < l; i++) {\n      ret[i] = render(val[i], i)\n    }\n  } else if (typeof val === 'number') {\n    ret = new Array(val)\n    for (i = 0; i < val; i++) {\n      ret[i] = render(i + 1, i)\n    }\n  } else if (isObject(val)) {\n    keys = Object.keys(val)\n    ret = new Array(keys.length)\n    for (i = 0, l = keys.length; i < l; i++) {\n      key = keys[i]\n      ret[i] = render(val[key], key, i)\n    }\n  }\n  return ret\n}\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/render-slot.js",
    "content": "/* @flow */\n\nimport { extend, warn } from 'core/util/index'\n\n/**\n * Runtime helper for rendering <slot>\n */\n /*处理slot的渲染*/\nexport function renderSlot (\n  name: string,\n  fallback: ?Array<VNode>,\n  props: ?Object,\n  bindObject: ?Object\n): ?Array<VNode> {\n  const scopedSlotFn = this.$scopedSlots[name]\n  if (scopedSlotFn) { // scoped slot\n    props = props || {}\n    if (bindObject) {\n      extend(props, bindObject)\n    }\n    return scopedSlotFn(props) || fallback\n  } else {\n    const slotNodes = this.$slots[name]\n    // warn duplicate slot usage\n    if (slotNodes && process.env.NODE_ENV !== 'production') {\n      slotNodes._rendered && warn(\n        `Duplicate presence of slot \"${name}\" found in the same render tree ` +\n        `- this will likely cause render errors.`,\n        this\n      )\n      slotNodes._rendered = true\n    }\n    return slotNodes || fallback\n  }\n}\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/render-static.js",
    "content": "/* @flow */\n\nimport { cloneVNode, cloneVNodes } from 'core/vdom/vnode'\n\n/**\n * Runtime helper for rendering static trees.\n */\n /*处理static树的渲染*/\nexport function renderStatic (\n  index: number,\n  isInFor?: boolean\n): VNode | Array<VNode> {\n  /*从_staticTrees中取出tree，如果已经被渲染则会存在*/\n  let tree = this._staticTrees[index]\n  // if has already-rendered static tree and not inside v-for,\n  // we can reuse the same tree by doing a shallow clone.\n  /*如果已经被渲染的static tree并且它不是在v-for中，我们能够通过浅拷贝来重用同一棵树*/\n  if (tree && !isInFor) {\n    return Array.isArray(tree)\n      ? cloneVNodes(tree)\n      : cloneVNode(tree)\n  }\n  // otherwise, render a fresh tree.\n  /*否则渲染一刻新的树，同时存储在_staticTrees中，供上面检测是否已经渲染过*/\n  tree = this._staticTrees[index] =\n    this.$options.staticRenderFns[index].call(this._renderProxy)\n  markStatic(tree, `__static__${index}`, false)\n  return tree\n}\n\n/**\n * Runtime helper for v-once.\n * Effectively it means marking the node as static with a unique key.\n */\n /*处理v-once的渲染函数*/\nexport function markOnce (\n  tree: VNode | Array<VNode>,\n  index: number,\n  key: string\n) {\n  markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true)\n  return tree\n}\n\nfunction markStatic (\n  tree: VNode | Array<VNode>,\n  key: string,\n  isOnce: boolean\n) {\n  /*处理static节点*/\n  if (Array.isArray(tree)) {\n    for (let i = 0; i < tree.length; i++) {\n      if (tree[i] && typeof tree[i] !== 'string') {\n        markStaticNode(tree[i], `${key}_${i}`, isOnce)\n      }\n    }\n  } else {\n    markStaticNode(tree, key, isOnce)\n  }\n}\n\n/*处理static节点*/\nfunction markStaticNode (node, key, isOnce) {\n  node.isStatic = true\n  node.key = key\n  node.isOnce = isOnce\n}\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/resolve-filter.js",
    "content": "/* @flow */\n\nimport { identity, resolveAsset } from 'core/util/index'\n\n/**\n * Runtime helper for resolving filters\n */\n /*处理filters*/\nexport function resolveFilter (id: string): Function {\n  return resolveAsset(this.$options, 'filters', id, true) || identity\n}\n"
  },
  {
    "path": "vue-src/core/instance/render-helpers/resolve-slots.js",
    "content": "/* @flow */\n\n/**\n * Runtime helper for resolving raw children VNodes into a slot object.\n */\nexport function resolveSlots (\n  children: ?Array<VNode>,\n  context: ?Component\n): { [key: string]: Array<VNode> } {\n  const slots = {}\n  if (!children) {\n    return slots\n  }\n  const defaultSlot = []\n  for (let i = 0, l = children.length; i < l; i++) {\n    const child = children[i]\n    // named slots should only be respected if the vnode was rendered in the\n    // same context.\n    if ((child.context === context || child.functionalContext === context) &&\n        child.data && child.data.slot != null) {\n      const name = child.data.slot\n      const slot = (slots[name] || (slots[name] = []))\n      if (child.tag === 'template') {\n        slot.push.apply(slot, child.children)\n      } else {\n        slot.push(child)\n      }\n    } else {\n      defaultSlot.push(child)\n    }\n  }\n  // ignore whitespace\n  if (!defaultSlot.every(isWhitespace)) {\n    slots.default = defaultSlot\n  }\n  return slots\n}\n\nfunction isWhitespace (node: VNode): boolean {\n  return node.isComment || node.text === ' '\n}\n\n/*处理ScopedSlots*/\nexport function resolveScopedSlots (\n  fns: Array<[string, Function]>\n): { [key: string]: Function } {\n  const res = {}\n  for (let i = 0; i < fns.length; i++) {\n    res[fns[i][0]] = fns[i][1]\n  }\n  return res\n}\n"
  },
  {
    "path": "vue-src/core/instance/render.js",
    "content": "/* @flow */\n\nimport {\n  warn,\n  nextTick,\n  toNumber,\n  toString,\n  looseEqual,\n  emptyObject,\n  handleError,\n  looseIndexOf\n} from '../util/index'\n\nimport VNode, {\n  cloneVNodes,\n  createTextVNode,\n  createEmptyVNode\n} from '../vdom/vnode'\n/*Github:https://github.com/answershuto*/\nimport { createElement } from '../vdom/create-element'\nimport { renderList } from './render-helpers/render-list'\nimport { renderSlot } from './render-helpers/render-slot'\nimport { resolveFilter } from './render-helpers/resolve-filter'\nimport { checkKeyCodes } from './render-helpers/check-keycodes'\nimport { bindObjectProps } from './render-helpers/bind-object-props'\nimport { renderStatic, markOnce } from './render-helpers/render-static'\nimport { resolveSlots, resolveScopedSlots } from './render-helpers/resolve-slots'\n\n/*初始化render*/\nexport function initRender (vm: Component) {\n  vm._vnode = null // the root of the child tree\n  vm._staticTrees = null\n  const parentVnode = vm.$vnode = vm.$options._parentVnode // the placeholder node in parent tree  父树中的占位符节点\n  const renderContext = parentVnode && parentVnode.context\n  vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)\n  vm.$scopedSlots = emptyObject\n  // bind the createElement fn to this instance\n  // so that we get proper render context inside it.\n  // args order: tag, data, children, normalizationType, alwaysNormalize\n  // internal version is used by render functions compiled from templates\n  /*将createElement函数绑定到该实例上，该vm存在闭包中，不可修改，vm实例则固定。这样我们就可以得到正确的上下文渲染*/\n  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)\n  // normalization is always applied for the public version, used in\n  // user-written render functions.\n  /*常规方法被用于公共版本，被用来作为用户界面的渲染方法*/\n  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)\n}\n\nexport function renderMixin (Vue: Class<Component>) {\n  Vue.prototype.$nextTick = function (fn: Function) {\n    return nextTick(fn, this)\n  }\n\n  /*_render渲染函数，返回一个VNode节点*/\n  Vue.prototype._render = function (): VNode {\n    const vm: Component = this\n    const {\n      render,\n      staticRenderFns,\n      _parentVnode\n    } = vm.$options\n\n    if (vm._isMounted) {\n      // clone slot nodes on re-renders\n      /*在重新渲染时会克隆槽位节点 不知道是不是因为Vnode必须必须唯一的原因，网上也没找到答案，此处存疑。*/\n      for (const key in vm.$slots) {\n        vm.$slots[key] = cloneVNodes(vm.$slots[key])\n      }\n    }\n\n    /*作用域slot*/\n    vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject\n\n    if (staticRenderFns && !vm._staticTrees) {\n      /*用来存放static节点，已经被渲染的并且不存在v-for中的static节点不需要重新渲染，只需要进行浅拷贝*/\n      vm._staticTrees = []\n    }\n    // set parent vnode. this allows render functions to have access\n    // to the data on the placeholder node.\n    vm.$vnode = _parentVnode\n    // render self\n    /*渲染*/\n    let vnode\n    try {\n      /*调用render函数，返回一个VNode节点*/\n      vnode = render.call(vm._renderProxy, vm.$createElement)\n    } catch (e) {\n      handleError(e, vm, `render function`)\n      // return error render result,\n      // or previous vnode to prevent render error causing blank component\n      /* istanbul ignore else */\n      if (process.env.NODE_ENV !== 'production') {\n        vnode = vm.$options.renderError\n          ? vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)\n          : vm._vnode\n      } else {\n        vnode = vm._vnode\n      }\n    }\n    // return empty vnode in case the render function errored out\n    /*如果VNode节点没有创建成功则创建一个空节点*/\n    if (!(vnode instanceof VNode)) {\n      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {\n        warn(\n          'Multiple root nodes returned from render function. Render function ' +\n          'should return a single root node.',\n          vm\n        )\n      }\n      vnode = createEmptyVNode()\n    }\n    // set parent\n    vnode.parent = _parentVnode\n    return vnode\n  }\n\n  // internal render helpers.\n  // these are exposed on the instance prototype to reduce generated render\n  // code size.\n  /*\n    内部处理render的函数\n    这些函数会暴露在Vue原型上以减小渲染函数大小\n  */\n  /*处理v-once的渲染函数*/\n  Vue.prototype._o = markOnce\n  /*将字符串转化为数字，如果转换失败会返回原字符串*/\n  Vue.prototype._n = toNumber\n  /*将val转化成字符串*/\n  Vue.prototype._s = toString\n  /*处理v-for列表渲染*/\n  Vue.prototype._l = renderList\n  /*处理slot的渲染*/\n  Vue.prototype._t = renderSlot\n  /*检测两个变量是否相等*/\n  Vue.prototype._q = looseEqual\n  /*检测arr数组中是否包含与val变量相等的项*/\n  Vue.prototype._i = looseIndexOf\n  /*处理static树的渲染*/\n  Vue.prototype._m = renderStatic\n  /*处理filters*/\n  Vue.prototype._f = resolveFilter\n  /*从config配置中检查eventKeyCode是否存在*/\n  Vue.prototype._k = checkKeyCodes\n  /*合并v-bind指令到VNode中*/\n  Vue.prototype._b = bindObjectProps\n  /*创建一个文本节点*/\n  Vue.prototype._v = createTextVNode\n  /*创建一个空VNode节点*/\n  Vue.prototype._e = createEmptyVNode\n  /*处理ScopedSlots*/\n  Vue.prototype._u = resolveScopedSlots\n}\n"
  },
  {
    "path": "vue-src/core/instance/state.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport Dep from '../observer/dep'\nimport Watcher from '../observer/watcher'\n/*Github:https://github.com/answershuto*/\nimport {\n  set,\n  del,\n  observe,\n  observerState,\n  defineReactive\n} from '../observer/index'\n\nimport {\n  warn,\n  bind,\n  noop,\n  hasOwn,\n  isReserved,\n  handleError,\n  validateProp,\n  isPlainObject\n} from '../util/index'\n\nconst sharedPropertyDefinition = {\n  enumerable: true,\n  configurable: true,\n  get: noop,\n  set: noop\n}\n\n/*通过proxy函数将_data（或者_props等）上面的数据代理到vm上，这样就可以用app.text代替app._data.text了。*/\nexport function proxy (target: Object, sourceKey: string, key: string) {\n  sharedPropertyDefinition.get = function proxyGetter () {\n    return this[sourceKey][key]\n  }\n  sharedPropertyDefinition.set = function proxySetter (val) {\n    this[sourceKey][key] = val\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n\n/*初始化props、methods、data、computed与watch*/\nexport function initState (vm: Component) {\n  vm._watchers = []\n  const opts = vm.$options\n  /*初始化props*/\n  if (opts.props) initProps(vm, opts.props)\n  /*初始化方法*/\n  if (opts.methods) initMethods(vm, opts.methods)\n  /*初始化data*/\n  if (opts.data) {\n    initData(vm)\n  } else {\n    /*该组件没有data的时候绑定一个空对象*/\n    observe(vm._data = {}, true /* asRootData */)\n  }\n  /*初始化computed*/\n  if (opts.computed) initComputed(vm, opts.computed)\n  /*初始化watchers*/\n  if (opts.watch) initWatch(vm, opts.watch)\n}\n\nconst isReservedProp = {\n  key: 1,\n  ref: 1,\n  slot: 1\n}\n\n/*初始化props*/\nfunction initProps (vm: Component, propsOptions: Object) {\n  const propsData = vm.$options.propsData || {}\n  const props = vm._props = {}\n  // cache prop keys so that future props updates can iterate using Array\n  // instead of dynamic object key enumeration.\n  /*缓存属性的key，使得将来能直接使用数组的索引值来更新props来替代动态地枚举对象*/\n  const keys = vm.$options._propKeys = []\n  /*根据$parent是否存在来判断当前是否是根结点*/\n  const isRoot = !vm.$parent\n  // root instance props should be converted\n  /*根结点会给shouldConvert赋true，根结点的props应该被转换*/\n  observerState.shouldConvert = isRoot\n  for (const key in propsOptions) {\n    /*props的key值存入keys（_propKeys）中*/\n    keys.push(key)\n    /*验证prop,不存在用默认值替换，类型为bool则声称true或false，当使用default中的默认值的时候会将默认值的副本进行observe*/\n    const value = validateProp(key, propsOptions, propsData, vm)\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      /*判断是否是保留字段，如果是则发出warning*/\n      if (isReservedProp[key] || config.isReservedAttr(key)) {\n        warn(\n          `\"${key}\" is a reserved attribute and cannot be used as component prop.`,\n          vm\n        )\n      }\n      defineReactive(props, key, value, () => {\n        /*\n          由于父组件重新渲染的时候会重写prop的值，所以应该直接使用prop来作为一个data或者计算属性的依赖\n          https://cn.vuejs.org/v2/guide/components.html#字面量语法-vs-动态语法\n        */\n        if (vm.$parent && !observerState.isSettingProps) {\n          warn(\n            `Avoid mutating a prop directly since the value will be ` +\n            `overwritten whenever the parent component re-renders. ` +\n            `Instead, use a data or computed property based on the prop's ` +\n            `value. Prop being mutated: \"${key}\"`,\n            vm\n          )\n        }\n      })\n    } else {\n      defineReactive(props, key, value)\n    }\n    // static props are already proxied on the component's prototype\n    // during Vue.extend(). We only need to proxy props defined at\n    // instantiation here.\n    /*Vue.extend()期间，静态prop已经在组件原型上代理了，我们只需要在这里进行代理prop*/\n    if (!(key in vm)) {\n      proxy(vm, `_props`, key)\n    }\n  }\n  observerState.shouldConvert = true\n}\n\n/*初始化data*/\nfunction initData (vm: Component) {\n\n  /*得到data数据*/\n  let data = vm.$options.data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n\n  /*对对象类型进行严格检查，只有当对象是纯javascript对象的时候返回true*/\n  if (!isPlainObject(data)) {\n    data = {}\n    process.env.NODE_ENV !== 'production' && warn(\n      'data functions should return an object:\\n' +\n      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',\n      vm\n    )\n  }\n  // proxy data on instance\n  /*遍历data对象*/\n  const keys = Object.keys(data)\n  const props = vm.$options.props\n  let i = keys.length\n\n  //遍历data中的数据\n  while (i--) {\n\n    /*保证data中的key不与props中的key重复，props优先，如果有冲突会产生warning*/\n    if (props && hasOwn(props, keys[i])) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `The data property \"${keys[i]}\" is already declared as a prop. ` +\n        `Use prop default value instead.`,\n        vm\n      )\n    } else if (!isReserved(keys[i])) {\n      /*判断是否是保留字段*/\n\n      /*这里是我们前面讲过的代理，将data上面的属性代理到了vm实例上*/\n      proxy(vm, `_data`, keys[i])\n    }\n  }\n  // observe data\n  /*从这里开始我们要observe了，开始对数据进行绑定，这里有尤大大的注释asRootData，这步作为根数据，下面会进行递归observe进行对深层对象的绑定。*/\n  observe(data, true /* asRootData */)\n}\n\nfunction getData (data: Function, vm: Component): any {\n  try {\n    return data.call(vm)\n  } catch (e) {\n    handleError(e, vm, `data()`)\n    return {}\n  }\n}\n\nconst computedWatcherOptions = { lazy: true }\n\n/*初始化computed*/\nfunction initComputed (vm: Component, computed: Object) {\n  const watchers = vm._computedWatchers = Object.create(null)\n\n  for (const key in computed) {\n    const userDef = computed[key]\n    /*\n      计算属性可能是一个function，也有可能设置了get以及set的对象。\n      可以参考 https://cn.vuejs.org/v2/guide/computed.html#计算-setter\n    */\n    let getter = typeof userDef === 'function' ? userDef : userDef.get\n    if (process.env.NODE_ENV !== 'production') {\n      /*getter不存在的时候抛出warning并且给getter赋空函数*/\n      if (getter === undefined) {\n        warn(\n          `No getter function has been defined for computed property \"${key}\".`,\n          vm\n        )\n        getter = noop\n      }\n    }\n    // create internal watcher for the computed property.\n    /*\n      为计算属性创建一个内部的监视器Watcher，保存在vm实例的_computedWatchers中\n      这里的computedWatcherOptions参数传递了一个lazy为true，会使得watch实例的dirty为true\n    */\n    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)\n\n    // component-defined computed properties are already defined on the\n    // component prototype. We only need to define computed properties defined\n    // at instantiation here.\n    /*组件正在定义的计算属性已经定义在现有组件的原型上则不会进行重复定义*/\n    if (!(key in vm)) {\n      /*定义计算属性*/\n      defineComputed(vm, key, userDef)\n    } else if (process.env.NODE_ENV !== 'production') {\n      /*如果计算属性与已定义的data或者props中的名称冲突则发出warning*/\n      if (key in vm.$data) {\n        warn(`The computed property \"${key}\" is already defined in data.`, vm)\n      } else if (vm.$options.props && key in vm.$options.props) {\n        warn(`The computed property \"${key}\" is already defined as a prop.`, vm)\n      }\n    }\n  }\n}\n\n/*定义计算属性*/\nexport function defineComputed (target: any, key: string, userDef: Object | Function) {\n  if (typeof userDef === 'function') {\n    /*创建计算属性的getter*/\n    sharedPropertyDefinition.get = createComputedGetter(key)\n    /*\n      当userDef是一个function的时候是不需要setter的，所以这边给它设置成了空函数。\n      因为计算属性默认是一个function，只设置getter。\n      当需要设置setter的时候，会将计算属性设置成一个对象。参考：https://cn.vuejs.org/v2/guide/computed.html#计算-setter\n    */\n    sharedPropertyDefinition.set = noop\n  } else {\n    /*get不存在则直接给空函数，如果存在则查看是否有缓存cache，没有依旧赋值get，有的话使用createComputedGetter创建*/\n    sharedPropertyDefinition.get = userDef.get\n      ? userDef.cache !== false\n        ? createComputedGetter(key)\n        : userDef.get\n      : noop\n    /*如果有设置set方法则直接使用，否则赋值空函数*/\n    sharedPropertyDefinition.set = userDef.set\n      ? userDef.set\n      : noop\n  }\n  /*defineProperty上getter与setter*/\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n\n/*创建计算属性的getter*/\nfunction createComputedGetter (key) {\n  return function computedGetter () {\n    const watcher = this._computedWatchers && this._computedWatchers[key]\n    if (watcher) {\n      /*实际是脏检查，在计算属性中的依赖发生改变的时候dirty会变成true，在get的时候重新计算计算属性的输出值*/\n      if (watcher.dirty) {\n        watcher.evaluate()\n      }\n      /*依赖收集*/\n      if (Dep.target) {\n        watcher.depend()\n      }\n      return watcher.value\n    }\n  }\n}\n\n/*初始化方法*/\nfunction initMethods (vm: Component, methods: Object) {\n  const props = vm.$options.props\n  for (const key in methods) {\n    /*在为null的时候写上空方法，有值时候将上下文替换成vm*/\n    vm[key] = methods[key] == null ? noop : bind(methods[key], vm)\n    if (process.env.NODE_ENV !== 'production') {\n      if (methods[key] == null) {\n        warn(\n          `method \"${key}\" has an undefined value in the component definition. ` +\n          `Did you reference the function correctly?`,\n          vm\n        )\n      }\n      /*与props名称冲突报出warning*/\n      if (props && hasOwn(props, key)) {\n        warn(\n          `method \"${key}\" has already been defined as a prop.`,\n          vm\n        )\n      }\n    }\n  }\n}\n\n/*初始化watchers*/\nfunction initWatch (vm: Component, watch: Object) {\n  for (const key in watch) {\n    const handler = watch[key]\n    /*数组则遍历进行createWatcher*/\n    if (Array.isArray(handler)) {\n      for (let i = 0; i < handler.length; i++) {\n        createWatcher(vm, key, handler[i])\n      }\n    } else {\n      createWatcher(vm, key, handler)\n    }\n  }\n}\n\n/*创建一个观察者Watcher*/\nfunction createWatcher (vm: Component, key: string, handler: any) {\n  let options\n  /*对对象类型进行严格检查，只有当对象是纯javascript对象的时候返回true*/\n  if (isPlainObject(handler)) {\n    /*\n      这里是当watch的写法是这样的时候\n      watch: {\n          test: {\n              handler: function () {},\n              deep: true\n          }\n      }\n    */\n    options = handler\n    handler = handler.handler\n  }\n  if (typeof handler === 'string') {\n    /*\n        当然，也可以直接使用vm中methods的方法\n    */\n    handler = vm[handler]\n  }\n  /*用$watch方法创建一个watch来观察该对象的变化*/\n  vm.$watch(key, handler, options)\n}\n\nexport function stateMixin (Vue: Class<Component>) {\n  // flow somehow has problems with directly declared definition object\n  // when using Object.defineProperty, so we have to procedurally build up\n  // the object here.\n  const dataDef = {}\n  dataDef.get = function () { return this._data }\n  const propsDef = {}\n  propsDef.get = function () { return this._props }\n  if (process.env.NODE_ENV !== 'production') {\n    dataDef.set = function (newData: Object) {\n      warn(\n        'Avoid replacing instance root $data. ' +\n        'Use nested data properties instead.',\n        this\n      )\n    }\n    propsDef.set = function () {\n      warn(`$props is readonly.`, this)\n    }\n  }\n  Object.defineProperty(Vue.prototype, '$data', dataDef)\n  Object.defineProperty(Vue.prototype, '$props', propsDef)\n\n  /*\n    https://cn.vuejs.org/v2/api/#vm-set\n    用以将data之外的对象绑定成响应式的\n  */\n  Vue.prototype.$set = set\n  /*\n    https://cn.vuejs.org/v2/api/#vm-delete\n    与set对立，解除绑定\n  */\n  Vue.prototype.$delete = del\n\n  /*\n    https://cn.vuejs.org/v2/api/#vm-watch\n    $watch方法\n    用以为对象建立观察者监视变化\n  */\n  Vue.prototype.$watch = function (\n    expOrFn: string | Function,\n    cb: Function,\n    options?: Object\n  ): Function {\n    const vm: Component = this\n    options = options || {}\n    options.user = true\n    const watcher = new Watcher(vm, expOrFn, cb, options)\n    /*有immediate参数的时候会立即执行*/\n    if (options.immediate) {\n      cb.call(vm, watcher.value)\n    }\n    /*返回一个取消观察函数，用来停止触发回调*/\n    return function unwatchFn () {\n      /*将自身从所有依赖收集订阅列表删除*/\n      watcher.teardown()\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/observer/array.js",
    "content": "/*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototype\n */\n/*Github:https://github.com/answershuto*/\nimport { def } from '../util/index'\n\n/*取得原生数组的原型*/\nconst arrayProto = Array.prototype\n/*创建一个新的数组对象，修改该对象上的数组的七个方法，防止污染原生数组方法*/\nexport const arrayMethods = Object.create(arrayProto)\n\n/**\n * Intercept mutating methods and emit events\n */\n /*这里重写了数组的这些方法，在保证不污染原生数组原型的情况下重写数组的这些方法，截获数组的成员发生的变化，执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/\n;[\n  'push',\n  'pop',\n  'shift',\n  'unshift',\n  'splice',\n  'sort',\n  'reverse'\n]\n.forEach(function (method) {\n  // cache original method\n  /*将数组的原生方法缓存起来，后面要调用*/\n  const original = arrayProto[method]\n  def(arrayMethods, method, function mutator () {\n    // avoid leaking arguments:\n    // http://jsperf.com/closure-with-arguments\n    let i = arguments.length\n    const args = new Array(i)\n    while (i--) {\n      args[i] = arguments[i]\n    }\n    /*调用原生的数组方法*/\n    const result = original.apply(this, args)\n\n    /*数组新插入的元素需要重新进行observe才能响应式*/\n    const ob = this.__ob__\n    let inserted\n    switch (method) {\n      case 'push':\n        inserted = args\n        break\n      case 'unshift':\n        inserted = args\n        break\n      case 'splice':\n        inserted = args.slice(2)\n        break\n    }\n    if (inserted) ob.observeArray(inserted)\n      \n    // notify change\n    /*dep通知所有注册的观察者进行响应式处理*/\n    ob.dep.notify()\n    return result\n  })\n})\n"
  },
  {
    "path": "vue-src/core/observer/dep.js",
    "content": "/* @flow */\n\nimport type Watcher from './watcher'\nimport { remove } from '../util/index'\n\nlet uid = 0\n/*Github:https://github.com/answershuto*/\n/**\n * A dep is an observable that can have multiple\n * directives subscribing to it.\n */\nexport default class Dep {\n  static target: ?Watcher;\n  id: number;\n  subs: Array<Watcher>;\n\n  constructor () {\n    this.id = uid++\n    this.subs = []\n  }\n\n  /*添加一个观察者对象*/\n  addSub (sub: Watcher) {\n    this.subs.push(sub)\n  }\n\n  /*移除一个观察者对象*/\n  removeSub (sub: Watcher) {\n    remove(this.subs, sub)\n  }\n\n  /*依赖收集，当存在Dep.target的时候添加观察者对象*/\n  depend () {\n    if (Dep.target) {\n      Dep.target.addDep(this)\n    }\n  }\n\n  /*通知所有订阅者*/\n  notify () {\n    // stabilize the subscriber list first\n    const subs = this.subs.slice()\n    for (let i = 0, l = subs.length; i < l; i++) {\n      subs[i].update()\n    }\n  }\n}\n\n// the current target watcher being evaluated.\n// this is globally unique because there could be only one\n// watcher being evaluated at any time.\n/*依赖收集完需要将Dep.target设为null，防止后面重复添加依赖。*/\nDep.target = null\nconst targetStack = []\n\n/*将watcher观察者实例设置给Dep.target，用以依赖收集。同时将该实例存入target栈中*/\nexport function pushTarget (_target: Watcher) {\n  if (Dep.target) targetStack.push(Dep.target)\n  Dep.target = _target\n}\n\n/*将观察者实例从target栈中取出并设置给Dep.target*/\nexport function popTarget () {\n  Dep.target = targetStack.pop()\n}\n"
  },
  {
    "path": "vue-src/core/observer/index.js",
    "content": "/* @flow */\n\nimport Dep from './dep'\nimport { arrayMethods } from './array'\nimport {\n  def,\n  isObject,\n  isPlainObject,\n  hasProto,\n  hasOwn,\n  warn,\n  isServerRendering\n} from '../util/index'\n\nconst arrayKeys = Object.getOwnPropertyNames(arrayMethods)\n\n/**\n * By default, when a reactive property is set, the new value is\n * also converted to become reactive. However when passing down props,\n * we don't want to force conversion because the value may be a nested value\n * under a frozen data structure. Converting it would defeat the optimization.\n */\n /*默认情况下，当一个无效的属性被设置时，新的值也会被转换成无效的。不管怎样当传递props时，我们不需要进行强制转换*/\nexport const observerState = {\n  shouldConvert: true,\n  isSettingProps: false\n}\n/*Github:https://github.com/answershuto*/\n/**\n * Observer class that are attached to each observed\n * object. Once attached, the observer converts target\n * object's property keys into getter/setters that\n * collect dependencies and dispatches updates.\n */\n /*\n    每个被观察到对象被附加上观察者实例，一旦被添加，观察者将为目标对象加上getter\\setter属性，进行依赖收集以及调度更新。\n*/\nexport class Observer {\n  value: any;\n  dep: Dep;\n  vmCount: number; // number of vms that has this object as root $data\n\n  constructor (value: any) {\n    this.value = value\n    this.dep = new Dep()\n    this.vmCount = 0\n    /* \n    将Observer实例绑定到data的__ob__属性上面去，之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了，def方法定义可以参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16 \n    */\n    def(value, '__ob__', this)\n    if (Array.isArray(value)) {\n      /*\n          如果是数组，将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法，达到监听数组数据变化响应的效果。\n          这里如果当前浏览器支持__proto__属性，则直接覆盖当前数组对象原型上的原生数组方法，如果不支持该属性，则直接覆盖数组对象的原型。\n      */\n      const augment = hasProto\n        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/\n        : copyAugment   /*定义（覆盖）目标对象或数组的某一个方法*/\n      augment(value, arrayMethods, arrayKeys)\n\n      /*如果是数组则需要遍历数组的每一个成员进行observe*/\n      this.observeArray(value)\n    } else {\n      /*如果是对象则直接walk进行绑定*/\n      this.walk(value)\n    }\n  }\n\n  /**\n   * Walk through each property and convert them into\n   * getter/setters. This method should only be called when\n   * value type is Object.\n   */\n   /*\n      遍历每一个对象并且在它们上面绑定getter与setter。这个方法只有在value的类型是对象的时候才能被调用\n   */\n  walk (obj: Object) {\n    const keys = Object.keys(obj)\n    /*walk方法会遍历对象的每一个属性进行defineReactive绑定*/\n    for (let i = 0; i < keys.length; i++) {\n      defineReactive(obj, keys[i], obj[keys[i]])\n    }\n  }\n\n  /**\n   * Observe a list of Array items.\n   */\n   /*对一个数组的每一个成员进行observe*/\n  observeArray (items: Array<any>) {\n    for (let i = 0, l = items.length; i < l; i++) {\n      /*数组需要遍历每一个成员进行observe*/\n      observe(items[i])\n    }\n  }\n}\n\n// helpers\n\n/**\n * Augment an target Object or Array by intercepting\n * the prototype chain using __proto__\n */\n /*直接覆盖原型的方法来修改目标对象或数组*/\nfunction protoAugment (target, src: Object) {\n  /* eslint-disable no-proto */\n  target.__proto__ = src\n  /* eslint-enable no-proto */\n}\n\n/**\n * Augment an target Object or Array by defining\n * hidden properties.\n */\n/* istanbul ignore next */\n/*定义（覆盖）目标对象或数组的某一个方法*/\nfunction copyAugment (target: Object, src: Object, keys: Array<string>) {\n  for (let i = 0, l = keys.length; i < l; i++) {\n    const key = keys[i]\n    def(target, key, src[key])\n  }\n}\n\n/**\n * Attempt to create an observer instance for a value,\n * returns the new observer if successfully observed,\n * or the existing observer if the value already has one.\n */\n  /*\n 尝试创建一个Observer实例（__ob__），如果成功创建Observer实例则返回新的Observer实例，如果已有Observer实例则返回现有的Observer实例。\n */\nexport function observe (value: any, asRootData: ?boolean): Observer | void {\n  if (!isObject(value)) {\n    return\n  }\n  let ob: Observer | void\n  /*这里用__ob__这个属性来判断是否已经有Observer实例，如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性，如果已有Observer实例则直接返回该Observer实例*/\n  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {\n    ob = value.__ob__\n  } else if (\n    /*\n      这里的判断是为了确保value是单纯的对象，而不是函数或者是Regexp等情况。\n      而且该对象在shouldConvert的时候才会进行Observer。这是一个标识位，避免重复对value进行Observer\n    */\n    observerState.shouldConvert &&\n    !isServerRendering() &&\n    (Array.isArray(value) || isPlainObject(value)) &&\n    Object.isExtensible(value) &&\n    !value._isVue\n  ) {\n    ob = new Observer(value)\n  }\n  if (asRootData && ob) {\n     /*如果是根数据则计数，后面Observer中的observe的asRootData非true*/\n    ob.vmCount++\n  }\n  return ob\n}\n\n/**\n * Define a reactive property on an Object.\n */\n /*为对象defineProperty上在变化时通知的属性*/\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: Function\n) {\n  /*在闭包中定义一个dep对象*/\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  /*如果之前该对象已经预设了getter以及setter函数则将其取出来，新定义的getter/setter中会将其执行，保证不会覆盖之前已经定义的getter/setter。*/\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n\n  /*对象的子对象递归进行observe并返回子节点的Observer对象*/\n  let childOb = observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      /*如果原本对象拥有getter方法则执行*/\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n        /*进行依赖收集*/\n        dep.depend()\n        if (childOb) {\n          /*子对象进行依赖收集，其实就是将同一个watcher观察者实例放进了两个depend中，一个是正在本身闭包中的depend，另一个是子元素的depend*/\n          childOb.dep.depend()\n        }\n        if (Array.isArray(value)) {\n          /*是数组则需要对每一个成员都进行依赖收集，如果数组的成员还是数组，则递归。*/\n          dependArray(value)\n        }\n      }\n      return value\n    },\n    set: function reactiveSetter (newVal) {\n      /*通过getter方法获取当前值，与新值进行比较，一致则不需要执行下面的操作*/\n      const value = getter ? getter.call(obj) : val\n      /* eslint-disable no-self-compare */\n      if (newVal === value || (newVal !== newVal && value !== value)) {\n        return\n      }\n      /* eslint-enable no-self-compare */\n      if (process.env.NODE_ENV !== 'production' && customSetter) {\n        customSetter()\n      }\n      if (setter) {\n        /*如果原本对象拥有setter方法则执行setter*/\n        setter.call(obj, newVal)\n      } else {\n        val = newVal\n      }\n      /*新的值需要重新进行observe，保证数据响应式*/\n      childOb = observe(newVal)\n      /*dep对象通知所有的观察者*/\n      dep.notify()\n    }\n  })\n}\n\n/**\n * Set a property on an object. Adds the new property and\n * triggers change notification if the property doesn't\n * already exist.\n */\nexport function set (target: Array<any> | Object, key: any, val: any): any {\n  /*如果传入数组则在指定位置插入val*/\n  if (Array.isArray(target) && typeof key === 'number') {\n    target.length = Math.max(target.length, key)\n    target.splice(key, 1, val)\n    /*因为数组不需要进行响应式处理，数组会修改七个Array原型上的方法来进行响应式处理*/\n    return val\n  }\n  /*如果是一个对象，并且已经存在了这个key则直接返回*/\n  if (hasOwn(target, key)) {\n    target[key] = val\n    return val\n  }\n  /*获得target的Oberver实例*/\n  const ob = (target : any).__ob__\n  /*\n    _isVue 一个防止vm实例自身被观察的标志位 ，_isVue为true则代表vm实例，也就是this\n    vmCount判断是否为根节点，存在则代表是data的根节点，Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)\n  */\n  if (target._isVue || (ob && ob.vmCount)) {\n    /*  \n      Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)。\n      https://cn.vuejs.org/v2/guide/reactivity.html#变化检测问题\n    */\n    process.env.NODE_ENV !== 'production' && warn(\n      'Avoid adding reactive properties to a Vue instance or its root $data ' +\n      'at runtime - declare it upfront in the data option.'\n    )\n    return val\n  }\n  if (!ob) {\n    target[key] = val\n    return val\n  }\n  /*为对象defineProperty上在变化时通知的属性*/\n  defineReactive(ob.value, key, val)\n  ob.dep.notify()\n  return val\n}\n\n/**\n * Delete a property and trigger change if necessary.\n */\nexport function del (target: Array<any> | Object, key: any) {\n  if (Array.isArray(target) && typeof key === 'number') {\n    target.splice(key, 1)\n    return\n  }\n  const ob = (target : any).__ob__\n  if (target._isVue || (ob && ob.vmCount)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      'Avoid deleting properties on a Vue instance or its root $data ' +\n      '- just set it to null.'\n    )\n    return\n  }\n  if (!hasOwn(target, key)) {\n    return\n  }\n  delete target[key]\n  if (!ob) {\n    return\n  }\n  ob.dep.notify()\n}\n\n/**\n * Collect dependencies on array elements when the array is touched, since\n * we cannot intercept array element access like property getters.\n */\nfunction dependArray (value: Array<any>) {\n  for (let e, i = 0, l = value.length; i < l; i++) {\n    e = value[i]\n    /*通过对象上的观察者进行依赖收集*/\n    e && e.__ob__ && e.__ob__.dep.depend()\n    if (Array.isArray(e)) {\n      /*当数组成员还是数组的时候地柜执行该方法继续深层依赖收集，直到是对象为止。*/\n      dependArray(e)\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/observer/scheduler.js",
    "content": "/* @flow */\n\nimport type Watcher from './watcher'\nimport config from '../config'\nimport { callHook, activateChildComponent } from '../instance/lifecycle'\n\nimport {\n  warn,\n  nextTick,\n  devtools\n} from '../util/index'\n\nexport const MAX_UPDATE_COUNT = 100\n\nconst queue: Array<Watcher> = []\nconst activatedChildren: Array<Component> = []\n/*一个哈希表，用来存放watcher对象的id，防止重复的watcher对象多次加入*/\nlet has: { [key: number]: ?true } = {}\nlet circular: { [key: number]: number } = {}\nlet waiting = false\nlet flushing = false\nlet index = 0\n\n/**\n * Reset the scheduler's state.\n */\n /*重置调度者的状态*/\nfunction resetSchedulerState () {\n  queue.length = activatedChildren.length = 0\n  has = {}\n  if (process.env.NODE_ENV !== 'production') {\n    circular = {}\n  }\n  waiting = flushing = false\n}\n/*Github:https://github.com/answershuto*/\n/**\n * Flush both queues and run the watchers.\n */\n /*nextTick的回调函数，在下一个tick时flush掉两个队列同时运行watchers*/\nfunction flushSchedulerQueue () {\n  flushing = true\n  let watcher, id\n\n  // Sort queue before flush.\n  // This ensures that:\n  // 1. Components are updated from parent to child. (because parent is always\n  //    created before the child)\n  // 2. A component's user watchers are run before its render watcher (because\n  //    user watchers are created before the render watcher)\n  // 3. If a component is destroyed during a parent component's watcher run,\n  //    its watchers can be skipped.\n  /*\n    给queue排序，这样做可以保证：\n    1.组件更新的顺序是从父组件到子组件的顺序，因为父组件总是比子组件先创建。\n    2.一个组件的user watchers比render watcher先运行，因为user watchers往往比render watcher更早创建\n    3.如果一个组件在父组件watcher运行期间被销毁，它的watcher执行将被跳过。\n  */\n  queue.sort((a, b) => a.id - b.id)\n\n  // do not cache length because more watchers might be pushed\n  // as we run existing watchers\n  /*这里不用index = queue.length;index > 0; index--的方式写是因为不要将length进行缓存，因为在执行处理现有watcher对象期间，更多的watcher对象可能会被push进queue*/\n  for (index = 0; index < queue.length; index++) {\n    watcher = queue[index]\n    id = watcher.id\n    /*将has的标记删除*/\n    has[id] = null\n    /*执行watcher*/\n    watcher.run()\n    // in dev build, check and stop circular updates.\n    /*\n      在测试环境中，检测watch是否在死循环中\n      比如这样一种情况\n      watch: {\n        test () {\n          this.test++;\n        }\n      }\n      持续执行了一百次watch代表可能存在死循环\n    */\n    if (process.env.NODE_ENV !== 'production' && has[id] != null) {\n      circular[id] = (circular[id] || 0) + 1\n      if (circular[id] > MAX_UPDATE_COUNT) {\n        warn(\n          'You may have an infinite update loop ' + (\n            watcher.user\n              ? `in watcher with expression \"${watcher.expression}\"`\n              : `in a component render function.`\n          ),\n          watcher.vm\n        )\n        break\n      }\n    }\n  }\n\n  // keep copies of post queues before resetting state\n  /**/\n  /*得到队列的拷贝*/\n  const activatedQueue = activatedChildren.slice()\n  const updatedQueue = queue.slice()\n\n  /*重置调度者的状态*/\n  resetSchedulerState()\n\n  // call component updated and activated hooks\n  /*使子组件状态都改编成active同时调用activated钩子*/\n  callActivatedHooks(activatedQueue)\n  /*调用updated钩子*/\n  callUpdateHooks(updatedQueue)\n\n  // devtool hook\n  /* istanbul ignore if */\n  if (devtools && config.devtools) {\n    devtools.emit('flush')\n  }\n}\n\n/*调用updated钩子*/\nfunction callUpdateHooks (queue) {\n  let i = queue.length\n  while (i--) {\n    const watcher = queue[i]\n    const vm = watcher.vm\n    if (vm._watcher === watcher && vm._isMounted) {\n      callHook(vm, 'updated')\n    }\n  }\n}\n\n/**\n * Queue a kept-alive component that was activated during patch.\n * The queue will be processed after the entire tree has been patched.\n */\n /*\n  在patch期间被激活（activated）的keep-alive组件保存在队列中，\n  是到patch结束以后该队列会被处理\n */\nexport function queueActivatedComponent (vm: Component) {\n  // setting _inactive to false here so that a render function can\n  // rely on checking whether it's in an inactive tree (e.g. router-view)\n  vm._inactive = false\n  activatedChildren.push(vm)\n}\n\n/*使子组件状态都改编成active同时调用activated钩子*/\nfunction callActivatedHooks (queue) {\n  for (let i = 0; i < queue.length; i++) {\n    queue[i]._inactive = true\n    activateChildComponent(queue[i], true /* true */)\n  }\n}\n\n/**\n * Push a watcher into the watcher queue.\n * Jobs with duplicate IDs will be skipped unless it's\n * pushed when the queue is being flushed.\n */\n /*将一个观察者对象push进观察者队列，在队列中已经存在相同的id则该观察者对象将被跳过，除非它是在队列被刷新时推送*/\nexport function queueWatcher (watcher: Watcher) {\n  /*获取watcher的id*/\n  const id = watcher.id\n  /*检验id是否存在，已经存在则直接跳过，不存在则标记哈希表has，用于下次检验*/\n  if (has[id] == null) {\n    has[id] = true\n    if (!flushing) {\n      /*如果没有flush掉，直接push到队列中即可*/\n      queue.push(watcher)\n    } else {\n      // if already flushing, splice the watcher based on its id\n      // if already past its id, it will be run next immediately.\n      let i = queue.length - 1\n      while (i >= 0 && queue[i].id > watcher.id) {\n        i--\n      }\n      queue.splice(Math.max(i, index) + 1, 0, watcher)\n    }\n    // queue the flush\n    if (!waiting) {\n      waiting = true\n      nextTick(flushSchedulerQueue)\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/observer/watcher.js",
    "content": "/* @flow */\n\nimport { queueWatcher } from './scheduler'\nimport Dep, { pushTarget, popTarget } from './dep'\n\nimport {\n  warn,\n  remove,\n  isObject,\n  parsePath,\n  _Set as Set,\n  handleError\n} from '../util/index'\n\nimport type { ISet } from '../util/index'\n\nlet uid = 0\n\n/**\n * A watcher parses an expression, collects dependencies,\n * and fires callback when the expression value changes.\n * This is used for both the $watch() api and directives.\n */\n /*\n    一个解析表达式，进行依赖收集的观察者，同时在表达式数据变更时触发回调函数。它被用于$watch api以及指令\n */\nexport default class Watcher {\n  vm: Component;\n  expression: string;\n  cb: Function;\n  id: number;\n  deep: boolean;\n  user: boolean;\n  lazy: boolean;\n  sync: boolean;\n  dirty: boolean;\n  active: boolean;\n  deps: Array<Dep>;\n  newDeps: Array<Dep>;\n  depIds: ISet;\n  newDepIds: ISet;\n  getter: Function;\n  value: any;\n\n  constructor (\n    vm: Component,\n    expOrFn: string | Function,\n    cb: Function,\n    options?: Object\n  ) {\n    this.vm = vm\n    /*_watchers存放订阅者实例*/\n    vm._watchers.push(this)\n    // options\n    if (options) {\n      this.deep = !!options.deep\n      this.user = !!options.user\n      this.lazy = !!options.lazy\n      this.sync = !!options.sync\n    } else {\n      this.deep = this.user = this.lazy = this.sync = false\n    }\n    this.cb = cb\n    this.id = ++uid // uid for batching\n    this.active = true\n    this.dirty = this.lazy // for lazy watchers\n    this.deps = []\n    this.newDeps = []\n    this.depIds = new Set()\n    this.newDepIds = new Set()\n    this.expression = process.env.NODE_ENV !== 'production'\n      ? expOrFn.toString()\n      : ''\n    // parse expression for getter\n    /*把表达式expOrFn解析成getter*/\n    if (typeof expOrFn === 'function') {\n      this.getter = expOrFn\n    } else {\n      this.getter = parsePath(expOrFn)\n      if (!this.getter) {\n        this.getter = function () {}\n        process.env.NODE_ENV !== 'production' && warn(\n          `Failed watching path: \"${expOrFn}\" ` +\n          'Watcher only accepts simple dot-delimited paths. ' +\n          'For full control, use a function instead.',\n          vm\n        )\n      }\n    }\n    this.value = this.lazy\n      ? undefined\n      : this.get()\n  }\n\n  /**\n   * Evaluate the getter, and re-collect dependencies.\n   */\n   /*获得getter的值并且重新进行依赖收集*/\n  get () {\n    /*将自身watcher观察者实例设置给Dep.target，用以依赖收集。*/\n    pushTarget(this)\n    let value\n    const vm = this.vm\n\n    /*\n      执行了getter操作，看似执行了渲染操作，其实是执行了依赖收集。\n      在将Dep.target设置为自生观察者实例以后，执行getter操作。\n      譬如说现在的的data中可能有a、b、c三个数据，getter渲染需要依赖a跟c，\n      那么在执行getter的时候就会触发a跟c两个数据的getter函数，\n      在getter函数中即可判断Dep.target是否存在然后完成依赖收集，\n      将该观察者对象放入闭包中的Dep的subs中去。\n    */\n    if (this.user) {\n      try {\n        value = this.getter.call(vm, vm)\n      } catch (e) {\n        handleError(e, vm, `getter for watcher \"${this.expression}\"`)\n      }\n    } else {\n      value = this.getter.call(vm, vm)\n    }\n    // \"touch\" every property so they are all tracked as\n    // dependencies for deep watching\n    /*如果存在deep，则触发每个深层对象的依赖，追踪其变化*/\n    if (this.deep) {\n      /*递归每一个对象或者数组，触发它们的getter，使得对象或数组的每一个成员都被依赖收集，形成一个“深（deep）”依赖关系*/\n      traverse(value)\n    }\n\n    /*将观察者实例从target栈中取出并设置给Dep.target*/\n    popTarget()\n    this.cleanupDeps()\n    return value\n  }\n\n  /**\n   * Add a dependency to this directive.\n   */\n   /*添加一个依赖关系到Deps集合中*/\n  addDep (dep: Dep) {\n    const id = dep.id\n    if (!this.newDepIds.has(id)) {\n      this.newDepIds.add(id)\n      this.newDeps.push(dep)\n      if (!this.depIds.has(id)) {\n        dep.addSub(this)\n      }\n    }\n  }\n\n  /**\n   * Clean up for dependency collection.\n   */\n   /*清理依赖收集*/\n  cleanupDeps () {\n    /*移除所有观察者对象*/\n    let i = this.deps.length\n    while (i--) {\n      const dep = this.deps[i]\n      if (!this.newDepIds.has(dep.id)) {\n        dep.removeSub(this)\n      }\n    }\n    let tmp = this.depIds\n    this.depIds = this.newDepIds\n    this.newDepIds = tmp\n    this.newDepIds.clear()\n    tmp = this.deps\n    this.deps = this.newDeps\n    this.newDeps = tmp\n    this.newDeps.length = 0\n  }\n\n  /**\n   * Subscriber interface.\n   * Will be called when a dependency changes.\n   */\n   /*\n      调度者接口，当依赖发生改变的时候进行回调。\n   */\n  update () {\n    /* istanbul ignore else */\n    if (this.lazy) {\n      this.dirty = true\n    } else if (this.sync) {\n      /*同步则执行run直接渲染视图*/\n      this.run()\n    } else {\n      /*异步推送到观察者队列中，下一个tick时调用。*/\n      queueWatcher(this)\n    }\n  }\n\n  /**\n   * Scheduler job interface.\n   * Will be called by the scheduler.\n   */\n   /*\n      调度者工作接口，将被调度者回调。\n    */\n  run () {\n    if (this.active) {\n      /* get操作在获取value本身也会执行getter从而调用update更新视图 */\n      const value = this.get()\n      if (\n        value !== this.value ||\n        // Deep watchers and watchers on Object/Arrays should fire even\n        // when the value is the same, because the value may\n        // have mutated.\n        /*\n            即便值相同，拥有Deep属性的观察者以及在对象／数组上的观察者应该被触发更新，因为它们的值可能发生改变。\n        */\n        isObject(value) ||\n        this.deep\n      ) {\n        // set new value\n        const oldValue = this.value\n        /*设置新的值*/\n        this.value = value\n\n        /*触发回调*/\n        if (this.user) {\n          try {\n            this.cb.call(this.vm, value, oldValue)\n          } catch (e) {\n            handleError(e, this.vm, `callback for watcher \"${this.expression}\"`)\n          }\n        } else {\n          this.cb.call(this.vm, value, oldValue)\n        }\n      }\n    }\n  }\n\n  /**\n   * Evaluate the value of the watcher.\n   * This only gets called for lazy watchers.\n   */\n   /*获取观察者的值*/\n  evaluate () {\n    this.value = this.get()\n    this.dirty = false\n  }\n\n  /**\n   * Depend on all deps collected by this watcher.\n   */\n   /*收集该watcher的所有deps依赖*/\n  depend () {\n    let i = this.deps.length\n    while (i--) {\n      this.deps[i].depend()\n    }\n  }\n\n  /**\n   * Remove self from all dependencies' subscriber list.\n   */\n   /*将自身从所有依赖收集订阅列表删除*/\n  teardown () {\n    if (this.active) {\n      // remove self from vm's watcher list\n      // this is a somewhat expensive operation so we skip it\n      // if the vm is being destroyed.\n      /*从vm实例的观察者列表中将自身移除，由于该操作比较耗费资源，所以如果vm实例正在被销毁则跳过该步骤。*/\n      if (!this.vm._isBeingDestroyed) {\n        remove(this.vm._watchers, this)\n      }\n      let i = this.deps.length\n      while (i--) {\n        this.deps[i].removeSub(this)\n      }\n      this.active = false\n    }\n  }\n}\n\n/**\n * Recursively traverse an object to evoke all converted\n * getters, so that every nested property inside the object\n * is collected as a \"deep\" dependency.\n */\n /*递归每一个对象或者数组，触发它们的getter，使得对象或数组的每一个成员都被依赖收集，形成一个“深（deep）”依赖关系*/\n\n /*用来存放Oberser实例等id，避免重复读取*/\nconst seenObjects = new Set()\nfunction traverse (val: any) {\n  seenObjects.clear()\n  _traverse(val, seenObjects)\n}\n\nfunction _traverse (val: any, seen: ISet) {\n  let i, keys\n  const isA = Array.isArray(val)\n  /*非对象或数组或是不可扩展对象直接return，不需要收集深层依赖关系。*/\n  if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {\n    return\n  }\n  if (val.__ob__) {\n    /*避免重复读取*/\n    const depId = val.__ob__.dep.id\n    if (seen.has(depId)) {\n      return\n    }\n    seen.add(depId)\n  }\n\n  /*递归对象及数组*/\n  if (isA) {\n    i = val.length\n    while (i--) _traverse(val[i], seen)\n  } else {\n    keys = Object.keys(val)\n    i = keys.length\n    while (i--) _traverse(val[keys[i]], seen)\n  }\n}\n"
  },
  {
    "path": "vue-src/core/util/debug.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { noop } from 'shared/util'\n\nexport let warn = noop\nexport let tip = noop\nexport let formatComponentName: Function = (null: any) // work around flow check\n\nif (process.env.NODE_ENV !== 'production') {\n  const hasConsole = typeof console !== 'undefined'\n  const classifyRE = /(?:^|[-_])(\\w)/g\n  const classify = str => str\n    .replace(classifyRE, c => c.toUpperCase())\n    .replace(/[-_]/g, '')\n\n  warn = (msg, vm) => {\n    if (hasConsole && (!config.silent)) {\n      console.error(`[Vue warn]: ${msg}` + (\n        vm ? generateComponentTrace(vm) : ''\n      ))\n    }\n  }\n\n  tip = (msg, vm) => {\n    if (hasConsole && (!config.silent)) {\n      console.warn(`[Vue tip]: ${msg}` + (\n        vm ? generateComponentTrace(vm) : ''\n      ))\n    }\n  }\n\n  /*格式化组件名*/\n  formatComponentName = (vm, includeFile) => {\n    if (vm.$root === vm) {\n      return '<Root>'\n    }\n    let name = typeof vm === 'string'\n      ? vm\n      : typeof vm === 'function' && vm.options\n        ? vm.options.name\n        : vm._isVue\n          ? vm.$options.name || vm.$options._componentTag\n          : vm.name\n\n    const file = vm._isVue && vm.$options.__file\n    if (!name && file) {\n      const match = file.match(/([^/\\\\]+)\\.vue$/)\n      name = match && match[1]\n    }\n\n    return (\n      (name ? `<${classify(name)}>` : `<Anonymous>`) +\n      (file && includeFile !== false ? ` at ${file}` : '')\n    )\n  }\n\n  const repeat = (str, n) => {\n    let res = ''\n    while (n) {\n      if (n % 2 === 1) res += str\n      if (n > 1) str += str\n      n >>= 1\n    }\n    return res\n  }\n\n  const generateComponentTrace = vm => {\n    if (vm._isVue && vm.$parent) {\n      const tree = []\n      let currentRecursiveSequence = 0\n      while (vm) {\n        if (tree.length > 0) {\n          const last = tree[tree.length - 1]\n          if (last.constructor === vm.constructor) {\n            currentRecursiveSequence++\n            vm = vm.$parent\n            continue\n          } else if (currentRecursiveSequence > 0) {\n            tree[tree.length - 1] = [last, currentRecursiveSequence]\n            currentRecursiveSequence = 0\n          }\n        }\n        tree.push(vm)\n        vm = vm.$parent\n      }\n      return '\\n\\nfound in\\n\\n' + tree\n        .map((vm, i) => `${\n          i === 0 ? '---> ' : repeat(' ', 5 + i * 2)\n        }${\n          Array.isArray(vm)\n            ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`\n            : formatComponentName(vm)\n        }`)\n        .join('\\n')\n    } else {\n      return `\\n\\n(found in ${formatComponentName(vm)})`\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/util/env.js",
    "content": "/* @flow */\n/* globals MutationObserver */\n\nimport { noop } from 'shared/util'\nimport { handleError } from './error'\n\n// can we use __proto__?\n/*判断当前浏览器是否支持__proto__这个非标准属性*/\nexport const hasProto = '__proto__' in {}\n\n// Browser environment sniffing\nexport const inBrowser = typeof window !== 'undefined'\nexport const UA = inBrowser && window.navigator.userAgent.toLowerCase()\nexport const isIE = UA && /msie|trident/.test(UA)\nexport const isIE9 = UA && UA.indexOf('msie 9.0') > 0\nexport const isEdge = UA && UA.indexOf('edge/') > 0\nexport const isAndroid = UA && UA.indexOf('android') > 0\nexport const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)\nexport const isChrome = UA && /chrome\\/\\d+/.test(UA) && !isEdge\n\nexport let supportsPassive = false\nif (inBrowser) {\n  try {\n    const opts = {}\n    Object.defineProperty(opts, 'passive', ({\n      get () {\n        /* istanbul ignore next */\n        supportsPassive = true\n      }\n    } : Object)) // https://github.com/facebook/flow/issues/285\n    window.addEventListener('test-passive', null, opts)\n  } catch (e) {}\n}\n\n// this needs to be lazy-evaled because vue may be required before\n// vue-server-renderer can set VUE_ENV\nlet _isServer\nexport const isServerRendering = () => {\n  if (_isServer === undefined) {\n    /* istanbul ignore if */\n    if (!inBrowser && typeof global !== 'undefined') {\n      // detect presence of vue-server-renderer and avoid\n      // Webpack shimming the process\n      _isServer = global['process'].env.VUE_ENV === 'server'\n    } else {\n      _isServer = false\n    }\n  }\n  return _isServer\n}\n\n// detect devtools\nexport const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\n/* istanbul ignore next */\nexport function isNative (Ctor: any): boolean {\n  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())\n}\n\nexport const hasSymbol =\n  typeof Symbol !== 'undefined' && isNative(Symbol) &&\n  typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)\n\n/**\n * Defer a task to execute it asynchronously.\n */\n /*\n    延迟一个任务使其异步执行，在下一个tick时执行，一个立即执行函数，返回一个function\n    这个函数的作用是在task或者microtask中推入一个timerFunc，在当前调用栈执行完以后以此执行直到执行到timerFunc\n    目的是延迟到当前调用栈执行完以后执行\n*/\nexport const nextTick = (function () {\n  /*存放异步执行的回调*/\n  const callbacks = []\n  /*一个标记位，如果已经有timerFunc被推送到任务队列中去则不需要重复推送*/\n  let pending = false\n  /*一个函数指针，指向函数将被推送到任务队列中，等到主线程任务执行完时，任务队列中的timerFunc被调用*/\n  let timerFunc\n\n  /*下一个tick时的回调*/\n  function nextTickHandler () {\n    /*一个标记位，标记等待状态（即函数已经被推入任务队列或者主线程，已经在等待当前栈执行完毕去执行），这样就不需要在push多个回调到callbacks时将timerFunc多次推入任务队列或者主线程*/\n    pending = false\n    /*执行所有callback*/\n    const copies = callbacks.slice(0)\n    callbacks.length = 0\n    for (let i = 0; i < copies.length; i++) {\n      copies[i]()\n    }\n  }\n\n  // the nextTick behavior leverages the microtask queue, which can be accessed\n  // via either native Promise.then or MutationObserver.\n  // MutationObserver has wider support, however it is seriously bugged in\n  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It\n  // completely stops working after triggering a few times... so, if native\n  // Promise is available, we will use it:\n  /* istanbul ignore if */\n\n  /*\n    这里解释一下，一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法。\n    优先使用Promise，在Promise不存在的情况下使用MutationObserver，这两个方法的回调函数都会在microtask中执行，它们会比setTimeout更早执行，所以优先使用。\n    如果上述两种方法都不支持的环境则会使用setTimeout，在task尾部推入这个函数，等待调用执行。\n    为啥要用 microtask？我在顾轶灵在知乎的回答中学习到：\n    根据 HTML Standard，在每个 task 运行完以后，UI 都会重渲染，那么在 microtask 中就完成数据更新，\n    当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新，那么渲染就会进行两次。\n    参考：https://www.zhihu.com/question/55364497/answer/144215284\n  */\n  if (typeof Promise !== 'undefined' && isNative(Promise)) {\n    /*使用Promise*/\n    var p = Promise.resolve()\n    var logError = err => { console.error(err) }\n    timerFunc = () => {\n      p.then(nextTickHandler).catch(logError)\n      // in problematic UIWebViews, Promise.then doesn't completely break, but\n      // it can get stuck in a weird state where callbacks are pushed into the\n      // microtask queue but the queue isn't being flushed, until the browser\n      // needs to do some other work, e.g. handle a timer. Therefore we can\n      // \"force\" the microtask queue to be flushed by adding an empty timer.\n      if (isIOS) setTimeout(noop)\n    }\n  } else if (typeof MutationObserver !== 'undefined' && (\n    isNative(MutationObserver) ||\n    // PhantomJS and iOS 7.x\n    MutationObserver.toString() === '[object MutationObserverConstructor]'\n  )) {\n    // use MutationObserver where native Promise is not available,\n    // e.g. PhantomJS IE11, iOS7, Android 4.4\n    /*新建一个textNode的DOM对象，用MutationObserver绑定该DOM并指定回调函数，在DOM变化的时候则会触发回调,该回调会进入主线程（比任务队列优先执行），即textNode.data = String(counter)时便会加入该回调*/\n    var counter = 1\n    var observer = new MutationObserver(nextTickHandler)\n    var textNode = document.createTextNode(String(counter))\n    observer.observe(textNode, {\n      characterData: true\n    })\n    timerFunc = () => {\n      counter = (counter + 1) % 2\n      textNode.data = String(counter)\n    }\n  } else {\n    // fallback to setTimeout\n    /* istanbul ignore next */\n    /*使用setTimeout将回调推入任务队列尾部*/\n    timerFunc = () => {\n      setTimeout(nextTickHandler, 0)\n    }\n  }\n\n  /*\n    推送到队列中下一个tick时执行\n    cb 回调函数\n    ctx 上下文\n  */\n  return function queueNextTick (cb?: Function, ctx?: Object) {\n    let _resolve\n    /*cb存到callbacks中*/\n    callbacks.push(() => {\n      if (cb) {\n        try {\n          cb.call(ctx)\n        } catch (e) {\n          handleError(e, ctx, 'nextTick')\n        }\n      } else if (_resolve) {\n        _resolve(ctx)\n      }\n    })\n    if (!pending) {\n      pending = true\n      timerFunc()\n    }\n    if (!cb && typeof Promise !== 'undefined') {\n      return new Promise((resolve, reject) => {\n        _resolve = resolve\n      })\n    }\n  }\n})()\n\nlet _Set\n/* istanbul ignore if */\nif (typeof Set !== 'undefined' && isNative(Set)) {\n  // use native Set when available.\n  _Set = Set\n} else {\n  // a non-standard Set polyfill that only works with primitive keys.\n  _Set = class Set implements ISet {\n    set: Object;\n    constructor () {\n      this.set = Object.create(null)\n    }\n    has (key: string | number) {\n      return this.set[key] === true\n    }\n    add (key: string | number) {\n      this.set[key] = true\n    }\n    clear () {\n      this.set = Object.create(null)\n    }\n  }\n}\n\ninterface ISet {\n  has(key: string | number): boolean;\n  add(key: string | number): mixed;\n  clear(): void;\n}\n\nexport { _Set }\nexport type { ISet }\n"
  },
  {
    "path": "vue-src/core/util/error.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { inBrowser } from './env'\n\nexport function handleError (err: Error, vm: any, info: string) {\n  if (config.errorHandler) {\n    config.errorHandler.call(null, err, vm, info)\n  } else {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(`Error in ${info}: \"${err.toString()}\"`, vm)\n    }\n    /* istanbul ignore else */\n    if (inBrowser && typeof console !== 'undefined') {\n      console.error(err)\n    } else {\n      throw err\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/util/index.js",
    "content": "/* @flow */\n\nexport * from 'shared/util'\nexport * from './lang'\nexport * from './env'\nexport * from './options'\nexport * from './debug'\nexport * from './props'\nexport * from './error'\nexport { defineReactive } from '../observer/index'\n"
  },
  {
    "path": "vue-src/core/util/lang.js",
    "content": "/* @flow */\n\nexport const emptyObject = Object.freeze({})\n\n/**\n * Check if a string starts with $ or _\n */\nexport function isReserved (str: string): boolean {\n  const c = (str + '').charCodeAt(0)\n  return c === 0x24 || c === 0x5F\n}\n\n/**\n * Define a property.\n */\nexport function def (obj: Object, key: string, val: any, enumerable?: boolean) {\n  Object.defineProperty(obj, key, {\n    value: val,\n    enumerable: !!enumerable,\n    writable: true,\n    configurable: true\n  })\n}\n\n/**\n * Parse simple path.\n */\nconst bailRE = /[^\\w.$]/\nexport function parsePath (path: string): any {\n  if (bailRE.test(path)) {\n    return\n  }\n  const segments = path.split('.')\n  return function (obj) {\n    for (let i = 0; i < segments.length; i++) {\n      if (!obj) return\n      obj = obj[segments[i]]\n    }\n    return obj\n  }\n}\n"
  },
  {
    "path": "vue-src/core/util/options.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { set } from '../observer/index'\n\nimport {\n  ASSET_TYPES,\n  LIFECYCLE_HOOKS\n} from 'shared/constants'\n\nimport {\n  extend,\n  hasOwn,\n  camelize,\n  capitalize,\n  isBuiltInTag,\n  isPlainObject\n} from 'shared/util'\n\n/**\n * Option overwriting strategies are functions that handle\n * how to merge a parent option value and a child option\n * value into the final value.\n */\n /*\n  这个strats的作用就是，当要合并两个option（比如父组件的option与子组件的option）合并的时候，\n  这里写了如何合并两个数据（或者function等）得到最终结果的方法\n */\nconst strats = config.optionMergeStrategies\n\n/**\n * Options with restrictions\n */\nif (process.env.NODE_ENV !== 'production') {\n  strats.el = strats.propsData = function (parent, child, vm, key) {\n    if (!vm) {\n      warn(\n        `option \"${key}\" can only be used during instance ` +\n        'creation with the `new` keyword.'\n      )\n    }\n    return defaultStrat(parent, child)\n  }\n}\n\n/**\n * Helper that recursively merges two data objects together.\n */\nfunction mergeData (to: Object, from: ?Object): Object {\n  if (!from) return to\n  let key, toVal, fromVal\n  const keys = Object.keys(from)\n  for (let i = 0; i < keys.length; i++) {\n    key = keys[i]\n    toVal = to[key]\n    fromVal = from[key]\n    if (!hasOwn(to, key)) {\n      set(to, key, fromVal)\n    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {\n      mergeData(toVal, fromVal)\n    }\n  }\n  return to\n}\n\n/**\n * Data\n */\nstrats.data = function (\n  parentVal: any,\n  childVal: any,\n  vm?: Component\n): ?Function {\n  if (!vm) {\n    // in a Vue.extend merge, both should be functions\n    if (!childVal) {\n      return parentVal\n    }\n    if (typeof childVal !== 'function') {\n      process.env.NODE_ENV !== 'production' && warn(\n        'The \"data\" option should be a function ' +\n        'that returns a per-instance value in component ' +\n        'definitions.',\n        vm\n      )\n      return parentVal\n    }\n    if (!parentVal) {\n      return childVal\n    }\n    // when parentVal & childVal are both present,\n    // we need to return a function that returns the\n    // merged result of both functions... no need to\n    // check if parentVal is a function here because\n    // it has to be a function to pass previous merges.\n    return function mergedDataFn () {\n      return mergeData(\n        childVal.call(this),\n        parentVal.call(this)\n      )\n    }\n  } else if (parentVal || childVal) {\n    return function mergedInstanceDataFn () {\n      // instance merge\n      const instanceData = typeof childVal === 'function'\n        ? childVal.call(vm)\n        : childVal\n      const defaultData = typeof parentVal === 'function'\n        ? parentVal.call(vm)\n        : undefined\n      if (instanceData) {\n        return mergeData(instanceData, defaultData)\n      } else {\n        return defaultData\n      }\n    }\n  }\n}\n\n/**\n * Hooks and props are merged as arrays.\n */\nfunction mergeHook (\n  parentVal: ?Array<Function>,\n  childVal: ?Function | ?Array<Function>\n): ?Array<Function> {\n  return childVal\n    ? parentVal\n      ? parentVal.concat(childVal)\n      : Array.isArray(childVal)\n        ? childVal\n        : [childVal]\n    : parentVal\n}\n\nLIFECYCLE_HOOKS.forEach(hook => {\n  strats[hook] = mergeHook\n})\n\n/**\n * Assets\n *\n * When a vm is present (instance creation), we need to do\n * a three-way merge between constructor options, instance\n * options and parent options.\n */\nfunction mergeAssets (parentVal: ?Object, childVal: ?Object): Object {\n  const res = Object.create(parentVal || null)\n  return childVal\n    ? extend(res, childVal)\n    : res\n}\n\nASSET_TYPES.forEach(function (type) {\n  strats[type + 's'] = mergeAssets\n})\n\n/**\n * Watchers.\n *\n * Watchers hashes should not overwrite one\n * another, so we merge them as arrays.\n */\nstrats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {\n  /* istanbul ignore if */\n  if (!childVal) return Object.create(parentVal || null)\n  if (!parentVal) return childVal\n  const ret = {}\n  extend(ret, parentVal)\n  for (const key in childVal) {\n    let parent = ret[key]\n    const child = childVal[key]\n    if (parent && !Array.isArray(parent)) {\n      parent = [parent]\n    }\n    ret[key] = parent\n      ? parent.concat(child)\n      : [child]\n  }\n  return ret\n}\n\n/**\n * Other object hashes.\n */\nstrats.props =\nstrats.methods =\nstrats.computed = function (parentVal: ?Object, childVal: ?Object): ?Object {\n  if (!childVal) return Object.create(parentVal || null)\n  if (!parentVal) return childVal\n  const ret = Object.create(null)\n  extend(ret, parentVal)\n  extend(ret, childVal)\n  return ret\n}\n\n/**\n * Default strategy.\n */\nconst defaultStrat = function (parentVal: any, childVal: any): any {\n  return childVal === undefined\n    ? parentVal\n    : childVal\n}\n\n/**\n * Validate component names\n */\n /*检查是否是有效的组件名*/\nfunction checkComponents (options: Object) {\n  for (const key in options.components) {\n    const lower = key.toLowerCase()\n    if (isBuiltInTag(lower) || config.isReservedTag(lower)) {\n      warn(\n        'Do not use built-in or reserved HTML elements as component ' +\n        'id: ' + key\n      )\n    }\n  }\n}\n\n/**\n * Ensure all props option syntax are normalized into the\n * Object-based format.\n */\n /*确保所有props option序列化成正确的格式*/\nfunction normalizeProps (options: Object) {\n  const props = options.props\n  if (!props) return\n  const res = {}\n  let i, val, name\n  if (Array.isArray(props)) {\n    i = props.length\n    while (i--) {\n      val = props[i]\n      if (typeof val === 'string') {\n        /*将原本用-连接的字符串变成驼峰 aaa-bbb-ccc => aaaBbbCcc*/\n        name = camelize(val)\n        res[name] = { type: null }\n      } else if (process.env.NODE_ENV !== 'production') {\n        warn('props must be strings when using array syntax.')\n      }\n    }\n  } else if (isPlainObject(props)) {\n    for (const key in props) {\n      val = props[key]\n      /*将原本用-连接的字符串变成驼峰 aaa-bbb-ccc => aaaBbbCcc*/\n      name = camelize(key)\n      res[name] = isPlainObject(val)\n        ? val\n        : { type: val }\n    }\n  }\n  options.props = res\n}\n\n/**\n * Normalize raw function directives into object format.\n */\n /*将函数指令序列化后加入对象*/\nfunction normalizeDirectives (options: Object) {\n  const dirs = options.directives\n  if (dirs) {\n    for (const key in dirs) {\n      const def = dirs[key]\n      if (typeof def === 'function') {\n        dirs[key] = { bind: def, update: def }\n      }\n    }\n  }\n}\n\n/**\n * Merge two option objects into a new one.\n * Core utility used in both instantiation and inheritance.\n */\n /*合并两个option对象到一个新的对象中*/\nexport function mergeOptions (\n  parent: Object,\n  child: Object,\n  vm?: Component\n): Object {\n  if (process.env.NODE_ENV !== 'production') {\n    /*检查是否是有效的组件名*/\n    checkComponents(child)\n  }\n\n  if (typeof child === 'function') {\n    child = child.options\n  }\n\n  /*确保所有props option序列化成正确的格式*/\n  normalizeProps(child)\n  /*将函数指令序列化后加入对象*/\n  normalizeDirectives(child)\n  /*\n    https://cn.vuejs.org/v2/api/#extends\n    允许声明扩展另一个组件(可以是一个简单的选项对象或构造函数),而无需使用 \n    将child的extends也加入parent扩展\n  */\n  const extendsFrom = child.extends\n  if (extendsFrom) {\n    parent = mergeOptions(parent, extendsFrom, vm)\n  }\n  /*child的mixins加入parent中*/\n  if (child.mixins) {\n    for (let i = 0, l = child.mixins.length; i < l; i++) {\n      parent = mergeOptions(parent, child.mixins[i], vm)\n    }\n  }\n  const options = {}\n  let key\n  for (key in parent) {\n    mergeField(key)\n  }\n  /*合并parent与child*/\n  for (key in child) {\n    if (!hasOwn(parent, key)) {\n      mergeField(key)\n    }\n  }\n  function mergeField (key) {\n    /*strats里面存了options中每一个属性（el、props、watch等等）的合并方法，先取出*/\n    const strat = strats[key] || defaultStrat\n    /*根据合并方法来合并两个option*/\n    options[key] = strat(parent[key], child[key], vm, key)\n  }\n  return options\n}\n\n/**\n * Resolve an asset.\n * This function is used because child instances need access\n * to assets defined in its ancestor chain.\n */\nexport function resolveAsset (\n  options: Object,\n  type: string,\n  id: string,\n  warnMissing?: boolean\n): any {\n  /* istanbul ignore if */\n  if (typeof id !== 'string') {\n    return\n  }\n  /*分别用id本身、驼峰以及大写开头驼峰寻找是否存在，存在则返回，不存在则打印*/\n  const assets = options[type]\n  // check local registration variations first\n  if (hasOwn(assets, id)) return assets[id]\n  /*转化为驼峰命名*/\n  const camelizedId = camelize(id)\n  if (hasOwn(assets, camelizedId)) return assets[camelizedId]\n  /*驼峰首字母大写*/\n  const PascalCaseId = capitalize(camelizedId)\n  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]\n  // fallback to prototype chain\n  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]\n  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {\n    warn(\n      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,\n      options\n    )\n  }\n  return res\n}\n"
  },
  {
    "path": "vue-src/core/util/perf.js",
    "content": "import { inBrowser } from './env'\n\nexport let mark\nexport let measure\n\nif (process.env.NODE_ENV !== 'production') {\n  const perf = inBrowser && window.performance\n  /* istanbul ignore if */\n  if (\n    perf &&\n    perf.mark &&\n    perf.measure &&\n    perf.clearMarks &&\n    perf.clearMeasures\n  ) {\n    mark = tag => perf.mark(tag)\n    measure = (name, startTag, endTag) => {\n      perf.measure(name, startTag, endTag)\n      perf.clearMarks(startTag)\n      perf.clearMarks(endTag)\n      perf.clearMeasures(name)\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/util/props.js",
    "content": "/* @flow */\n\nimport { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'\nimport { observe, observerState } from '../observer/index'\nimport { warn } from './debug'\n\ntype PropOptions = {\n  type: Function | Array<Function> | null,\n  default: any,\n  required: ?boolean,\n  validator: ?Function\n};\n\n/*验证prop,不存在用默认值替换，类型为bool则声称true或false，当使用default中的默认值的时候会将默认值的副本进行observe*/\nexport function validateProp (\n  key: string,/*prop的key值*/\n  propOptions: Object,/*prop的参数，https://cn.vuejs.org/v2/guide/components.html#Prop-验证 */\n  propsData: Object,/*props数据*/\n  vm?: Component/*vm实例*/\n): any {\n  /*获取prop参数*/\n  const prop = propOptions[key]\n  /*该prop是否存在，也就是父组件是否正常传入，存在absent为false，反之为true*/\n  const absent = !hasOwn(propsData, key)\n  /*获得prop的value*/\n  let value = propsData[key]\n  // handle boolean props\n  /*处理bool类型的属性*/\n  if (isType(Boolean, prop.type)) {\n    /*当父组件没有传入prop并且default中也不存在该prop时，赋值为false*/\n    if (absent && !hasOwn(prop, 'default')) {\n      value = false\n    } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {\n      value = true\n    }\n  }\n  // check default value\n  /*当属性值不存在（即父组件没有传递下来）*/\n  if (value === undefined) {\n    /*获取属性的默认值*/\n    value = getPropDefaultValue(vm, prop, key)\n\n    // since the default value is a fresh copy,\n    // make sure to observe it.\n    /*由于默认值是一份新的拷贝副本，确保已经对它进行observe，有观察者观察它的变化。*/\n\n    /*把之前的shouldConvert保存下来，当observe结束以后再设置回来*/\n    const prevShouldConvert = observerState.shouldConvert\n    observerState.shouldConvert = true\n    observe(value)\n    observerState.shouldConvert = prevShouldConvert\n  }\n  if (process.env.NODE_ENV !== 'production') {\n    assertProp(prop, key, value, vm, absent)\n  }\n  return value\n}\n\n/**\n * Get the default value of a prop.\n */\n /*获取属性的默认值*/\nfunction getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {\n  // no default, return undefined\n  /*没有默认值的时候直接返回undefined*/\n  if (!hasOwn(prop, 'default')) {\n    return undefined\n  }\n  const def = prop.default\n  // warn against non-factory defaults for Object & Array\n  /*非生产环境下发出警告，因为当前prop无默认值，当前对象的值非初始值。*/\n  if (process.env.NODE_ENV !== 'production' && isObject(def)) {\n    warn(\n      'Invalid default value for prop \"' + key + '\": ' +\n      'Props with type Object/Array must use a factory function ' +\n      'to return the default value.',\n      vm\n    )\n  }\n  // the raw prop value was also undefined from previous render,\n  // return previous default value to avoid unnecessary watcher trigger\n  /*以前的渲染的值如果不是undefined的，则返回上一次的默认值用以避免触发非必要的观察者*/\n  if (vm && vm.$options.propsData &&\n    vm.$options.propsData[key] === undefined &&\n    vm._props[key] !== undefined) {\n    return vm._props[key]\n  }\n  // call factory function for non-Function types\n  // a value is Function if its prototype is function even across different execution context\n  /*是funtion则改变它的上下文环境，vm。*/\n  return typeof def === 'function' && getType(prop.type) !== 'Function'\n    ? def.call(vm)\n    : def\n}\n\n/**\n * Assert whether a prop is valid.\n */\nfunction assertProp (\n  prop: PropOptions,\n  name: string,\n  value: any,\n  vm: ?Component,\n  absent: boolean\n) {\n  if (prop.required && absent) {\n    warn(\n      'Missing required prop: \"' + name + '\"',\n      vm\n    )\n    return\n  }\n  if (value == null && !prop.required) {\n    return\n  }\n  let type = prop.type\n  let valid = !type || type === true\n  const expectedTypes = []\n  if (type) {\n    if (!Array.isArray(type)) {\n      type = [type]\n    }\n    for (let i = 0; i < type.length && !valid; i++) {\n      const assertedType = assertType(value, type[i])\n      expectedTypes.push(assertedType.expectedType || '')\n      valid = assertedType.valid\n    }\n  }\n  if (!valid) {\n    warn(\n      'Invalid prop: type check failed for prop \"' + name + '\".' +\n      ' Expected ' + expectedTypes.map(capitalize).join(', ') +\n      ', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.',\n      vm\n    )\n    return\n  }\n  const validator = prop.validator\n  if (validator) {\n    if (!validator(value)) {\n      warn(\n        'Invalid prop: custom validator check failed for prop \"' + name + '\".',\n        vm\n      )\n    }\n  }\n}\n\nconst simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/\n\nfunction assertType (value: any, type: Function): {\n  valid: boolean;\n  expectedType: string;\n} {\n  let valid\n  const expectedType = getType(type)\n  if (simpleCheckRE.test(expectedType)) {\n    valid = typeof value === expectedType.toLowerCase()\n  } else if (expectedType === 'Object') {\n    valid = isPlainObject(value)\n  } else if (expectedType === 'Array') {\n    valid = Array.isArray(value)\n  } else {\n    valid = value instanceof type\n  }\n  return {\n    valid,\n    expectedType\n  }\n}\n\n/**\n * Use function string name to check built-in types,\n * because a simple equality check will fail when running\n * across different vms / iframes.\n */\nfunction getType (fn) {\n  const match = fn && fn.toString().match(/^\\s*function (\\w+)/)\n  return match ? match[1] : ''\n}\n\nfunction isType (type, fn) {\n  if (!Array.isArray(fn)) {\n    return getType(fn) === getType(type)\n  }\n  for (let i = 0, len = fn.length; i < len; i++) {\n    if (getType(fn[i]) === getType(type)) {\n      return true\n    }\n  }\n  /* istanbul ignore next */\n  return false\n}\n"
  },
  {
    "path": "vue-src/core/vdom/create-component.js",
    "content": "/* @flow */\n\nimport VNode from './vnode'\nimport { resolveConstructorOptions } from 'core/instance/init'\nimport { queueActivatedComponent } from 'core/observer/scheduler'\nimport { createFunctionalComponent } from './create-functional-component'\n\nimport {\n  warn,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject\n} from '../util/index'\n\nimport {\n  resolveAsyncComponent,\n  extractPropsFromVNodeData\n} from './helpers/index'\n\nimport {\n  callHook,\n  activeInstance,\n  updateChildComponent,\n  activateChildComponent,\n  deactivateChildComponent\n} from '../instance/lifecycle'\n\n// hooks to be invoked on component VNodes during patch\n/*被用来在VNode组件patch期间触发的钩子函数集合*/\nconst componentVNodeHooks = {\n  init (\n    vnode: VNodeWithData,\n    hydrating: boolean,\n    parentElm: ?Node,\n    refElm: ?Node\n  ): ?boolean {\n    if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {\n      const child = vnode.componentInstance = createComponentInstanceForVnode(\n        vnode,\n        activeInstance,\n        parentElm,\n        refElm\n      )\n      child.$mount(hydrating ? vnode.elm : undefined, hydrating)\n    } else if (vnode.data.keepAlive) {\n      // kept-alive components, treat as a patch\n      const mountedNode: any = vnode // work around flow\n      componentVNodeHooks.prepatch(mountedNode, mountedNode)\n    }\n  },\n\n  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {\n    const options = vnode.componentOptions\n    const child = vnode.componentInstance = oldVnode.componentInstance\n    updateChildComponent(\n      child,\n      options.propsData, // updated props\n      options.listeners, // updated listeners\n      vnode, // new parent vnode\n      options.children // new children\n    )\n  },\n\n  insert (vnode: MountedComponentVNode) {\n    const { context, componentInstance } = vnode\n    if (!componentInstance._isMounted) {\n      componentInstance._isMounted = true\n      callHook(componentInstance, 'mounted')\n    }\n    if (vnode.data.keepAlive) {\n      if (context._isMounted) {\n        // vue-router#1212\n        // During updates, a kept-alive component's child components may\n        // change, so directly walking the tree here may call activated hooks\n        // on incorrect children. Instead we push them into a queue which will\n        // be processed after the whole patch process ended.\n        queueActivatedComponent(componentInstance)\n      } else {\n        activateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  },\n\n  destroy (vnode: MountedComponentVNode) {\n    const { componentInstance } = vnode\n    if (!componentInstance._isDestroyed) {\n      if (!vnode.data.keepAlive) {\n        componentInstance.$destroy()\n      } else {\n        deactivateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  }\n}\n\nconst hooksToMerge = Object.keys(componentVNodeHooks)\n\n/*创建一个组件节点，返回Vnode节点*/\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data?: VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | void {\n  /*没有传组件构造类直接返回*/\n  if (isUndef(Ctor)) {\n    return\n  }\n\n  /*_base存放了Vue,作为基类，可以在里面添加扩展*/\n  const baseCtor = context.$options._base\n\n  // plain options object: turn it into a constructor\n  if (isObject(Ctor)) {\n    Ctor = baseCtor.extend(Ctor)\n  }\n\n  // if at this stage it's not a constructor or an async component factory,\n  // reject.\n  /*如果在该阶段Ctor依然不是一个构造函数或者是一个异步组件工厂则直接返回*/\n  if (typeof Ctor !== 'function') {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(`Invalid Component definition: ${String(Ctor)}`, context)\n    }\n    return\n  }\n\n  // async component\n  /*处理异步组件*/\n  if (isUndef(Ctor.cid)) {\n    Ctor = resolveAsyncComponent(Ctor, baseCtor, context)\n    if (Ctor === undefined) {\n      // return nothing if this is indeed an async component\n      // wait for the callback to trigger parent update.\n      /*如果这是一个异步组件则会不会返回任何东西（undifiened），直接return掉，等待回调函数去触发父组件更新。s*/\n      return\n    }\n  }\n\n  // resolve constructor options in case global mixins are applied after\n  // component constructor creation\n  resolveConstructorOptions(Ctor)\n\n  data = data || {}\n\n  // transform component v-model data into props & events\n  if (isDef(data.model)) {\n    transformModel(Ctor.options, data)\n  }\n\n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n\n  // functional component\n  if (isTrue(Ctor.options.functional)) {\n    return createFunctionalComponent(Ctor, propsData, data, context, children)\n  }\n\n  // extract listeners, since these needs to be treated as\n  // child component listeners instead of DOM listeners\n  const listeners = data.on\n  // replace with listeners with .native modifier\n  data.on = data.nativeOn\n\n  if (isTrue(Ctor.options.abstract)) {\n    // abstract components do not keep anything\n    // other than props & listeners\n    data = {}\n  }\n\n  // merge component management hooks onto the placeholder node\n  mergeHooks(data)\n\n  // return a placeholder vnode\n  const name = Ctor.options.name || tag\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children }\n  )\n  return vnode\n}\n\nexport function createComponentInstanceForVnode (\n  vnode: any, // we know it's MountedComponentVNode but flow doesn't\n  parent: any, // activeInstance in lifecycle state\n  parentElm?: ?Node,\n  refElm?: ?Node\n): Component {\n  const vnodeComponentOptions = vnode.componentOptions\n  const options: InternalComponentOptions = {\n    _isComponent: true,\n    parent,\n    propsData: vnodeComponentOptions.propsData,\n    _componentTag: vnodeComponentOptions.tag,\n    _parentVnode: vnode,\n    _parentListeners: vnodeComponentOptions.listeners,\n    _renderChildren: vnodeComponentOptions.children,\n    _parentElm: parentElm || null,\n    _refElm: refElm || null\n  }\n  // check inline-template render functions\n  const inlineTemplate = vnode.data.inlineTemplate\n  if (isDef(inlineTemplate)) {\n    options.render = inlineTemplate.render\n    options.staticRenderFns = inlineTemplate.staticRenderFns\n  }\n  return new vnodeComponentOptions.Ctor(options)\n}\n\nfunction mergeHooks (data: VNodeData) {\n  if (!data.hook) {\n    data.hook = {}\n  }\n  for (let i = 0; i < hooksToMerge.length; i++) {\n    const key = hooksToMerge[i]\n    const fromParent = data.hook[key]\n    const ours = componentVNodeHooks[key]\n    data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours\n  }\n}\n\nfunction mergeHook (one: Function, two: Function): Function {\n  return function (a, b, c, d) {\n    one(a, b, c, d)\n    two(a, b, c, d)\n  }\n}\n\n// transform component v-model info (value and callback) into\n// prop and event handler respectively.\nfunction transformModel (options, data: any) {\n  const prop = (options.model && options.model.prop) || 'value'\n  const event = (options.model && options.model.event) || 'input'\n  ;(data.props || (data.props = {}))[prop] = data.model.value\n  const on = data.on || (data.on = {})\n  if (isDef(on[event])) {\n    on[event] = [data.model.callback].concat(on[event])\n  } else {\n    on[event] = data.model.callback\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/create-element.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport VNode, { createEmptyVNode } from './vnode'\nimport { createComponent } from './create-component'\n\nimport {\n  warn,\n  isDef,\n  isUndef,\n  isTrue,\n  isPrimitive,\n  resolveAsset\n} from '../util/index'\n\nimport {\n  normalizeChildren,\n  simpleNormalizeChildren\n} from './helpers/index'\n\nconst SIMPLE_NORMALIZE = 1\nconst ALWAYS_NORMALIZE = 2\n\n// wrapper function for providing a more flexible interface\n// without getting yelled at by flow\nexport function createElement (\n  context: Component,\n  tag: any,\n  data: any,\n  children: any,\n  normalizationType: any,\n  alwaysNormalize: boolean\n): VNode {\n  /*兼容不传data的情况*/\n  if (Array.isArray(data) || isPrimitive(data)) {\n    normalizationType = children\n    children = data\n    data = undefined\n  }\n  /*如果alwaysNormalize为true，则normalizationType标记为ALWAYS_NORMALIZE*/\n  if (isTrue(alwaysNormalize)) {\n    normalizationType = ALWAYS_NORMALIZE\n  }\n  /*创建虚拟节点*/\n  return _createElement(context, tag, data, children, normalizationType)\n}\n\n/*创建VNode节点*/\nexport function _createElement (\n  context: Component,\n  tag?: string | Class<Component> | Function | Object,\n  data?: VNodeData,\n  children?: any,\n  normalizationType?: number\n): VNode {\n  /*\n    如果data未定义（undefined或者null）或者是data的__ob__已经定义（代表已经被observed，上面绑定了Oberver对象），\n    https://cn.vuejs.org/v2/guide/render-function.html#约束\n    那么创建一个空节点\n  */\n  if (isDef(data) && isDef((data: any).__ob__)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\\n` +\n      'Always create fresh vnode data objects in each render!',\n      context\n    )\n    return createEmptyVNode()\n  }\n  /*如果tag不存在也是创建一个空节点*/\n  if (!tag) {\n    // in case of component :is set to falsy value\n    return createEmptyVNode()\n  }\n  // support single function children as default scoped slot\n  /*默认默认作用域插槽*/\n  if (Array.isArray(children) &&\n      typeof children[0] === 'function') {\n    data = data || {}\n    data.scopedSlots = { default: children[0] }\n    children.length = 0\n  }\n  if (normalizationType === ALWAYS_NORMALIZE) {\n    children = normalizeChildren(children)\n  } else if (normalizationType === SIMPLE_NORMALIZE) {\n    children = simpleNormalizeChildren(children)\n  }\n  let vnode, ns\n  if (typeof tag === 'string') {\n    let Ctor\n    /*获取tag的名字空间*/\n    ns = config.getTagNamespace(tag)\n    /*判断是否是保留的标签*/\n    if (config.isReservedTag(tag)) {\n      // platform built-in elements\n      /*如果是保留的标签则创建一个相应节点*/\n      vnode = new VNode(\n        config.parsePlatformTagName(tag), data, children,\n        undefined, undefined, context\n      )\n    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n      // component\n      /*从vm实例的option的components中寻找该tag，存在则就是一个组件，创建相应节点，Ctor为组件的构造类*/\n      vnode = createComponent(Ctor, data, context, children, tag)\n    } else {\n      // unknown or unlisted namespaced elements\n      // check at runtime because it may get assigned a namespace when its\n      // parent normalizes children\n      /*未知的元素，在运行时检查，因为父组件可能在序列化子组件的时候分配一个名字空间*/\n      vnode = new VNode(\n        tag, data, children,\n        undefined, undefined, context\n      )\n    }\n  } else {\n    // direct component options / constructor\n    /*tag不是字符串的时候则是组件的构造类*/\n    vnode = createComponent(tag, data, context, children)\n  }\n  if (isDef(vnode)) {\n    /*如果有名字空间，则递归所有子节点应用该名字空间*/\n    if (ns) applyNS(vnode, ns)\n    return vnode\n  } else {\n    /*如果vnode没有成功创建则创建空节点*/\n    return createEmptyVNode()\n  }\n}\n/*Github:https://github.com/answershuto*/\nfunction applyNS (vnode, ns) {\n  vnode.ns = ns\n  if (vnode.tag === 'foreignObject') {\n    // use default namespace inside foreignObject\n    return\n  }\n  if (isDef(vnode.children)) {\n    for (let i = 0, l = vnode.children.length; i < l; i++) {\n      const child = vnode.children[i]\n      if (isDef(child.tag) && isUndef(child.ns)) {\n        applyNS(child, ns)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/create-functional-component.js",
    "content": "/* @flow */\n\nimport VNode from './vnode'\nimport { createElement } from './create-element'\nimport { resolveInject } from '../instance/inject'\nimport { resolveSlots } from '../instance/render-helpers/resolve-slots'\n\nimport {\n  isDef,\n  camelize,\n  validateProp\n} from '../util/index'\n\nexport function createFunctionalComponent (\n  Ctor: Class<Component>,\n  propsData: ?Object,\n  data: VNodeData,\n  context: Component,\n  children: ?Array<VNode>\n): VNode | void {\n  const props = {}\n  const propOptions = Ctor.options.props\n  if (isDef(propOptions)) {\n    for (const key in propOptions) {\n      props[key] = validateProp(key, propOptions, propsData || {})\n    }\n  } else {\n    if (isDef(data.attrs)) mergeProps(props, data.attrs)\n    if (isDef(data.props)) mergeProps(props, data.props)\n  }\n  // ensure the createElement function in functional components\n  // gets a unique context - this is necessary for correct named slot check\n  const _context = Object.create(context)\n  const h = (a, b, c, d) => createElement(_context, a, b, c, d, true)\n  const vnode = Ctor.options.render.call(null, h, {\n    data,\n    props,\n    children,\n    parent: context,\n    listeners: data.on || {},\n    injections: resolveInject(Ctor.options.inject, context),\n    slots: () => resolveSlots(children, context)\n  })\n  if (vnode instanceof VNode) {\n    vnode.functionalContext = context\n    if (data.slot) {\n      (vnode.data || (vnode.data = {})).slot = data.slot\n    }\n  }\n  return vnode\n}\n\nfunction mergeProps (to, from) {\n  for (const key in from) {\n    to[camelize(key)] = from[key]\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/extract-props.js",
    "content": "/* @flow */\n\nimport {\n  tip,\n  hasOwn,\n  isDef,\n  isUndef,\n  hyphenate,\n  formatComponentName\n} from 'core/util/index'\n\nexport function extractPropsFromVNodeData (\n  data: VNodeData,\n  Ctor: Class<Component>,\n  tag?: string\n): ?Object {\n  // we are only extracting raw values here.\n  // validation and default values are handled in the child\n  // component itself.\n  const propOptions = Ctor.options.props\n  if (isUndef(propOptions)) {\n    return\n  }\n  const res = {}\n  const { attrs, props } = data\n  if (isDef(attrs) || isDef(props)) {\n    for (const key in propOptions) {\n      const altKey = hyphenate(key)\n      if (process.env.NODE_ENV !== 'production') {\n        const keyInLowerCase = key.toLowerCase()\n        if (\n          key !== keyInLowerCase &&\n          attrs && hasOwn(attrs, keyInLowerCase)\n        ) {\n          tip(\n            `Prop \"${keyInLowerCase}\" is passed to component ` +\n            `${formatComponentName(tag || Ctor)}, but the declared prop name is` +\n            ` \"${key}\". ` +\n            `Note that HTML attributes are case-insensitive and camelCased ` +\n            `props need to use their kebab-case equivalents when using in-DOM ` +\n            `templates. You should probably use \"${altKey}\" instead of \"${key}\".`\n          )\n        }\n      }\n      checkProp(res, props, key, altKey, true) ||\n      checkProp(res, attrs, key, altKey, false)\n    }\n  }\n  return res\n}\n\nfunction checkProp (\n  res: Object,\n  hash: ?Object,\n  key: string,\n  altKey: string,\n  preserve: boolean\n): boolean {\n  if (isDef(hash)) {\n    if (hasOwn(hash, key)) {\n      res[key] = hash[key]\n      if (!preserve) {\n        delete hash[key]\n      }\n      return true\n    } else if (hasOwn(hash, altKey)) {\n      res[key] = hash[altKey]\n      if (!preserve) {\n        delete hash[altKey]\n      }\n      return true\n    }\n  }\n  return false\n}\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/get-first-component-child.js",
    "content": "/* @flow */\n\nimport { isDef } from 'shared/util'\n\n/* 获取第一个子组件 */\nexport function getFirstComponentChild (children: ?Array<VNode>): ?VNode {\n  if (Array.isArray(children)) {\n    for (let i = 0; i < children.length; i++) {\n      const c = children[i]\n      if (isDef(c) && isDef(c.componentOptions)) {\n        return c\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/index.js",
    "content": "/* @flow */\n\nexport * from './merge-hook'\nexport * from './extract-props'\nexport * from './update-listeners'\nexport * from './normalize-children'\nexport * from './resolve-async-component'\nexport * from './get-first-component-child'\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/merge-hook.js",
    "content": "/* @flow */\n\nimport { createFnInvoker } from './update-listeners'\nimport { remove, isDef, isUndef, isTrue } from 'shared/util'\n\nexport function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {\n  let invoker\n  const oldHook = def[hookKey]\n\n  function wrappedHook () {\n    hook.apply(this, arguments)\n    // important: remove merged hook to ensure it's called only once\n    // and prevent memory leak\n    remove(invoker.fns, wrappedHook)\n  }\n\n  if (isUndef(oldHook)) {\n    // no existing hook\n    invoker = createFnInvoker([wrappedHook])\n  } else {\n    /* istanbul ignore if */\n    if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {\n      // already a merged invoker\n      invoker = oldHook\n      invoker.fns.push(wrappedHook)\n    } else {\n      // existing plain hook\n      invoker = createFnInvoker([oldHook, wrappedHook])\n    }\n  }\n\n  invoker.merged = true\n  def[hookKey] = invoker\n}\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/normalize-children.js",
    "content": "/* @flow */\n\nimport VNode, { createTextVNode } from 'core/vdom/vnode'\nimport { isDef, isUndef, isPrimitive } from 'shared/util'\n\n// The template compiler attempts to minimize the need for normalization by\n// statically analyzing the template at compile time.\n//\n// For plain HTML markup, normalization can be completely skipped because the\n// generated render function is guaranteed to return Array<VNode>. There are\n// two cases where extra normalization is needed:\n\n// 1. When the children contains components - because a functional component\n// may return an Array instead of a single root. In this case, just a simple\n// normalization is needed - if any child is an Array, we flatten the whole\n// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep\n// because functional components already normalize their own children.\nexport function simpleNormalizeChildren (children: any) {\n  for (let i = 0; i < children.length; i++) {\n    if (Array.isArray(children[i])) {\n      return Array.prototype.concat.apply([], children)\n    }\n  }\n  return children\n}\n\n// 2. When the children contains constructs that always generated nested Arrays,\n// e.g. <template>, <slot>, v-for, or when the children is provided by user\n// with hand-written render functions / JSX. In such cases a full normalization\n// is needed to cater to all possible types of children values.\nexport function normalizeChildren (children: any): ?Array<VNode> {\n  return isPrimitive(children)\n    ? [createTextVNode(children)]\n    : Array.isArray(children)\n      ? normalizeArrayChildren(children)\n      : undefined\n}\n\nfunction normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {\n  const res = []\n  let i, c, last\n  for (i = 0; i < children.length; i++) {\n    c = children[i]\n    if (isUndef(c) || typeof c === 'boolean') continue\n    last = res[res.length - 1]\n    //  nested\n    if (Array.isArray(c)) {\n      res.push.apply(res, normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`))\n    } else if (isPrimitive(c)) {\n      if (isDef(last) && isDef(last.text)) {\n        last.text += String(c)\n      } else if (c !== '') {\n        // convert primitive to vnode\n        res.push(createTextVNode(c))\n      }\n    } else {\n      if (isDef(c.text) && isDef(last) && isDef(last.text)) {\n        res[res.length - 1] = createTextVNode(last.text + c.text)\n      } else {\n        // default key for nested array children (likely generated by v-for)\n        if (isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) {\n          c.key = `__vlist${nestedIndex}_${i}__`\n        }\n        res.push(c)\n      }\n    }\n  }\n  return res\n}\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/resolve-async-component.js",
    "content": "/* @flow */\n\nimport {\n  warn,\n  once,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject\n} from 'core/util/index'\n\nfunction ensureCtor (comp, base) {\n  return isObject(comp)\n    ? base.extend(comp)\n    : comp\n}\n\nexport function resolveAsyncComponent (\n  factory: Function,\n  baseCtor: Class<Component>,\n  context: Component\n): Class<Component> | void {\n  /*出错组件工厂返回出错组件*/\n  if (isTrue(factory.error) && isDef(factory.errorComp)) {\n    return factory.errorComp\n  }\n\n  /*resoved时候返回resolved组件*/\n  if (isDef(factory.resolved)) {\n    return factory.resolved\n  }\n\n  /*加载组件*/\n  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {\n    return factory.loadingComp\n  }\n\n  if (isDef(factory.contexts)) {\n    // already pending\n    factory.contexts.push(context)\n  } else {\n    const contexts = factory.contexts = [context]\n    let sync = true\n\n    const forceRender = () => {\n      for (let i = 0, l = contexts.length; i < l; i++) {\n        contexts[i].$forceUpdate()\n      }\n    }\n\n    const resolve = once((res: Object | Class<Component>) => {\n      // cache resolved\n      factory.resolved = ensureCtor(res, baseCtor)\n      // invoke callbacks only if this is not a synchronous resolve\n      // (async resolves are shimmed as synchronous during SSR)\n      if (!sync) {\n        forceRender()\n      }\n    })\n\n    const reject = once(reason => {\n      process.env.NODE_ENV !== 'production' && warn(\n        `Failed to resolve async component: ${String(factory)}` +\n        (reason ? `\\nReason: ${reason}` : '')\n      )\n      if (isDef(factory.errorComp)) {\n        factory.error = true\n        forceRender()\n      }\n    })\n\n    const res = factory(resolve, reject)\n\n    if (isObject(res)) {\n      if (typeof res.then === 'function') {\n        // () => Promise\n        if (isUndef(factory.resolved)) {\n          res.then(resolve, reject)\n        }\n      } else if (isDef(res.component) && typeof res.component.then === 'function') {\n        res.component.then(resolve, reject)\n\n        if (isDef(res.error)) {\n          factory.errorComp = ensureCtor(res.error, baseCtor)\n        }\n\n        if (isDef(res.loading)) {\n          factory.loadingComp = ensureCtor(res.loading, baseCtor)\n          if (res.delay === 0) {\n            factory.loading = true\n          } else {\n            setTimeout(() => {\n              if (isUndef(factory.resolved) && isUndef(factory.error)) {\n                factory.loading = true\n                forceRender()\n              }\n            }, res.delay || 200)\n          }\n        }\n\n        if (isDef(res.timeout)) {\n          setTimeout(() => {\n            reject(\n              process.env.NODE_ENV !== 'production'\n                ? `timeout (${res.timeout}ms)`\n                : null\n            )\n          }, res.timeout)\n        }\n      }\n    }\n\n    sync = false\n    // return in case resolved synchronously\n    return factory.loading\n      ? factory.loadingComp\n      : factory.resolved\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/update-listeners.js",
    "content": "/* @flow */\n\nimport { warn } from 'core/util/index'\nimport { cached, isUndef } from 'shared/util'\n\nconst normalizeEvent = cached((name: string): {\n  name: string,\n  once: boolean,\n  capture: boolean,\n  passive: boolean\n} => {\n  const passive = name.charAt(0) === '&'\n  name = passive ? name.slice(1) : name\n  const once = name.charAt(0) === '~' // Prefixed last, checked first\n  name = once ? name.slice(1) : name\n  const capture = name.charAt(0) === '!'\n  name = capture ? name.slice(1) : name\n  return {\n    name,\n    once,\n    capture,\n    passive\n  }\n})\n\n/*返回一个函数，该函数的作用是将生成时的fns执行，如果fns是数组，则便利执行它的每一项*/\nexport function createFnInvoker (fns: Function | Array<Function>): Function {\n  function invoker () {\n    const fns = invoker.fns\n    if (Array.isArray(fns)) {\n      for (let i = 0; i < fns.length; i++) {\n        fns[i].apply(null, arguments)\n      }\n    } else {\n      // return handler return value for single handlers\n      return fns.apply(null, arguments)\n    }\n  }\n  invoker.fns = fns\n  return invoker\n}\n\n/*更新监听事件*/\nexport function updateListeners (\n  on: Object,\n  oldOn: Object,\n  add: Function,\n  remove: Function,\n  vm: Component\n) {\n  let name, cur, old, event\n  /*遍历新事件的所有方法*/\n  for (name in on) {\n    cur = on[name]\n    old = oldOn[name]\n\n    /*取得并去除事件的~、!、&等前缀*/\n    event = normalizeEvent(name)\n    /*isUndef用于判断传入对象不等于undefined或者null*/\n    if (isUndef(cur)) {\n      /*新方法不存在抛出打印*/\n      process.env.NODE_ENV !== 'production' && warn(\n        `Invalid handler for event \"${event.name}\": got ` + String(cur),\n        vm\n      )\n    } else if (isUndef(old)) {\n      if (isUndef(cur.fns)) {\n        /*createFnInvoker返回一个函数，该函数的作用是将生成时的fns执行，如果fns是数组，则便利执行它的每一项*/\n        cur = on[name] = createFnInvoker(cur)\n      }\n      add(event.name, cur, event.once, event.capture, event.passive)\n    } else if (cur !== old) {\n      old.fns = cur\n      on[name] = old\n    }\n  }\n  /*移除所有旧的事件*/\n  for (name in oldOn) {\n    if (isUndef(on[name])) {\n      event = normalizeEvent(name)\n      remove(event.name, oldOn[name], event.capture)\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/modules/directives.js",
    "content": "/* @flow */\n\nimport { emptyNode } from 'core/vdom/patch'\nimport { resolveAsset, handleError } from 'core/util/index'\nimport { mergeVNodeHook } from 'core/vdom/helpers/index'\n\nexport default {\n  create: updateDirectives,\n  update: updateDirectives,\n  destroy: function unbindDirectives (vnode: VNodeWithData) {\n    updateDirectives(vnode, emptyNode)\n  }\n}\n\nfunction updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (oldVnode.data.directives || vnode.data.directives) {\n    _update(oldVnode, vnode)\n  }\n}\n\nfunction _update (oldVnode, vnode) {\n  const isCreate = oldVnode === emptyNode\n  const isDestroy = vnode === emptyNode\n  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)\n  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)\n\n  const dirsWithInsert = []\n  const dirsWithPostpatch = []\n\n  let key, oldDir, dir\n  for (key in newDirs) {\n    oldDir = oldDirs[key]\n    dir = newDirs[key]\n    if (!oldDir) {\n      // new directive, bind\n      callHook(dir, 'bind', vnode, oldVnode)\n      if (dir.def && dir.def.inserted) {\n        dirsWithInsert.push(dir)\n      }\n    } else {\n      // existing directive, update\n      dir.oldValue = oldDir.value\n      callHook(dir, 'update', vnode, oldVnode)\n      if (dir.def && dir.def.componentUpdated) {\n        dirsWithPostpatch.push(dir)\n      }\n    }\n  }\n\n  if (dirsWithInsert.length) {\n    const callInsert = () => {\n      for (let i = 0; i < dirsWithInsert.length; i++) {\n        callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)\n      }\n    }\n    if (isCreate) {\n      mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert)\n    } else {\n      callInsert()\n    }\n  }\n\n  if (dirsWithPostpatch.length) {\n    mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', () => {\n      for (let i = 0; i < dirsWithPostpatch.length; i++) {\n        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)\n      }\n    })\n  }\n\n  if (!isCreate) {\n    for (key in oldDirs) {\n      if (!newDirs[key]) {\n        // no longer present, unbind\n        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)\n      }\n    }\n  }\n}\n\nconst emptyModifiers = Object.create(null)\n\nfunction normalizeDirectives (\n  dirs: ?Array<VNodeDirective>,\n  vm: Component\n): { [key: string]: VNodeDirective } {\n  const res = Object.create(null)\n  if (!dirs) {\n    return res\n  }\n  let i, dir\n  for (i = 0; i < dirs.length; i++) {\n    dir = dirs[i]\n    if (!dir.modifiers) {\n      dir.modifiers = emptyModifiers\n    }\n    res[getRawDirName(dir)] = dir\n    dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)\n  }\n  return res\n}\n\nfunction getRawDirName (dir: VNodeDirective): string {\n  return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}`\n}\n\nfunction callHook (dir, hook, vnode, oldVnode, isDestroy) {\n  const fn = dir.def && dir.def[hook]\n  if (fn) {\n    try {\n      fn(vnode.elm, dir, vnode, oldVnode, isDestroy)\n    } catch (e) {\n      handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/modules/index.js",
    "content": "import directives from './directives'\nimport ref from './ref'\n\nexport default [\n  ref,\n  directives\n]\n"
  },
  {
    "path": "vue-src/core/vdom/modules/ref.js",
    "content": "/* @flow */\n\nimport { remove } from 'shared/util'\n\nexport default {\n  create (_: any, vnode: VNodeWithData) {\n    registerRef(vnode)\n  },\n  update (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n    if (oldVnode.data.ref !== vnode.data.ref) {\n      registerRef(oldVnode, true)\n      registerRef(vnode)\n    }\n  },\n  destroy (vnode: VNodeWithData) {\n    registerRef(vnode, true)\n  }\n}\n\n/*注册一个ref（即在$refs中添加或者删除对应的Dom实例），isRemoval代表是增加还是移除，*/\nexport function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) {\n  const key = vnode.data.ref\n  if (!key) return\n\n  const vm = vnode.context\n  const ref = vnode.componentInstance || vnode.elm\n  const refs = vm.$refs\n  if (isRemoval) {\n    /*移除一个ref*/\n    if (Array.isArray(refs[key])) {\n      remove(refs[key], ref)\n    } else if (refs[key] === ref) {\n      refs[key] = undefined\n    }\n  } else {\n    /*增加一个ref*/\n    if (vnode.data.refInFor) {\n      /*如果是在一个for循环中,则refs中key对应的是一个数组，里面存放了所有ref指向的Dom实例*/\n      if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {\n        refs[key].push(ref)\n      } else {\n        refs[key] = [ref]\n      }\n    } else {\n      /*不在一个for循环中则直接放入refs即可，ref指向Dom实例*/\n      refs[key] = ref\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/patch.js",
    "content": "/**\n * Virtual DOM patching algorithm based on Snabbdom by\n * Simon Friis Vindum (@paldepind)\n * Licensed under the MIT License\n * https://github.com/paldepind/snabbdom/blob/master/LICENSE\n *\n * modified by Evan You (@yyx990803)\n *\n\n/*\n * Not type-checking this because this file is perf-critical and the cost\n * of making flow understand it is not worth it.\n */\n\nimport VNode from './vnode'\nimport config from '../config'\nimport { SSR_ATTR } from 'shared/constants'\nimport { registerRef } from './modules/ref'\nimport { activeInstance } from '../instance/lifecycle'\n\nimport {\n  warn,\n  isDef,\n  isUndef,\n  isTrue,\n  makeMap,\n  isPrimitive\n} from '../util/index'\n/*Github:https://github.com/answershuto*/\nexport const emptyNode = new VNode('', {}, [])\n\nconst hooks = ['create', 'activate', 'update', 'remove', 'destroy']\n\n/*\n  判断两个VNode节点是否是同一个节点，需要满足以下条件\n  key相同\n  tag（当前节点的标签名）相同\n  isComment（是否为注释节点）相同\n  是否data（当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息）都有定义\n  当标签是<input>的时候，type必须相同\n*/\nfunction sameVnode (a, b) {\n  return (\n    a.key === b.key &&\n    a.tag === b.tag &&\n    a.isComment === b.isComment &&\n    isDef(a.data) === isDef(b.data) &&\n    sameInputType(a, b)\n  )\n}\n\n// Some browsers do not support dynamically changing type for <input>\n// so they need to be treated as different nodes\n/*\n  判断当标签是<input>的时候，type是否相同\n  某些浏览器不支持动态修改<input>类型，所以他们被视为不同类型\n*/\nfunction sameInputType (a, b) {\n  if (a.tag !== 'input') return true\n  let i\n  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type\n  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type\n  return typeA === typeB\n}\n\n/*\n  生成一个key与旧VNode的key对应的哈希表\n  比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  \n  结果生成{key0: 0, key1: 1, key2: 2}\n*/\nfunction createKeyToOldIdx (children, beginIdx, endIdx) {\n  let i, key\n  const map = {}\n  for (i = beginIdx; i <= endIdx; ++i) {\n    key = children[i].key\n    if (isDef(key)) map[key] = i\n  }\n  return map\n}\n\n/*创建patch方法*/\nexport function createPatchFunction (backend) {\n  let i, j\n  const cbs = {}\n\n  /*\n    nodeOps  见platforms/web(weex)/runtime/node-ops.js  实际上是操作节点（分平台，比如web上是Dom节点的操作）的方法集合的一个适配层，保证不同平台使用同样的对外接口操作节点。\n    modules  见/platforms/web(weex)/runtime/modules\n  */\n  const { modules, nodeOps } = backend\n\n  /*构建cbs回调函数，web平台上见/platforms/web/runtime/modules*/\n  for (i = 0; i < hooks.length; ++i) {\n    cbs[hooks[i]] = []\n    for (j = 0; j < modules.length; ++j) {\n      if (isDef(modules[j][hooks[i]])) {\n        cbs[hooks[i]].push(modules[j][hooks[i]])\n      }\n    }\n  }\n\n  function emptyNodeAt (elm) {\n    return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)\n  }\n\n  function createRmCb (childElm, listeners) {\n    function remove () {\n      if (--remove.listeners === 0) {\n        removeNode(childElm)\n      }\n    }\n    remove.listeners = listeners\n    return remove\n  }\n\n  /*将某个el节点从文档中移除*/\n  function removeNode (el) {\n    const parent = nodeOps.parentNode(el)\n    // element may have already been removed due to v-html / v-text\n    if (isDef(parent)) {\n      nodeOps.removeChild(parent, el)\n    }\n  }\n\n  let inPre = 0\n  /*创建一个节点*/\n  function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {\n    /*insertedVnodeQueue为空数组[]的时候isRootInsert标志为true*/\n    vnode.isRootInsert = !nested // for transition enter check\n    /*创建一个组件节点*/\n    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {\n      return\n    }\n\n    const data = vnode.data\n    const children = vnode.children\n    const tag = vnode.tag\n    if (isDef(tag)) {\n      if (process.env.NODE_ENV !== 'production') {\n        if (data && data.pre) {\n          inPre++\n        }\n        if (\n          !inPre &&\n          !vnode.ns &&\n          !(config.ignoredElements.length && config.ignoredElements.indexOf(tag) > -1) &&\n          config.isUnknownElement(tag)\n        ) {\n          warn(\n            'Unknown custom element: <' + tag + '> - did you ' +\n            'register the component correctly? For recursive components, ' +\n            'make sure to provide the \"name\" option.',\n            vnode.context\n          )\n        }\n      }\n      vnode.elm = vnode.ns\n        ? nodeOps.createElementNS(vnode.ns, tag)\n        : nodeOps.createElement(tag, vnode)\n      setScope(vnode)\n\n      /* istanbul ignore if */\n      if (__WEEX__) {\n        // in Weex, the default insertion order is parent-first.\n        // List items can be optimized to use children-first insertion\n        // with append=\"tree\".\n        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)\n        if (!appendAsTree) {\n          if (isDef(data)) {\n            invokeCreateHooks(vnode, insertedVnodeQueue)\n          }\n          insert(parentElm, vnode.elm, refElm)\n        }\n        createChildren(vnode, children, insertedVnodeQueue)\n        if (appendAsTree) {\n          if (isDef(data)) {\n            invokeCreateHooks(vnode, insertedVnodeQueue)\n          }\n          insert(parentElm, vnode.elm, refElm)\n        }\n      } else {\n        createChildren(vnode, children, insertedVnodeQueue)\n        if (isDef(data)) {\n          invokeCreateHooks(vnode, insertedVnodeQueue)\n        }\n        insert(parentElm, vnode.elm, refElm)\n      }\n\n      if (process.env.NODE_ENV !== 'production' && data && data.pre) {\n        inPre--\n      }\n    } else if (isTrue(vnode.isComment)) {\n      vnode.elm = nodeOps.createComment(vnode.text)\n      insert(parentElm, vnode.elm, refElm)\n    } else {\n      vnode.elm = nodeOps.createTextNode(vnode.text)\n      insert(parentElm, vnode.elm, refElm)\n    }\n  }\n\n  /*创建一个组件*/\n  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n    let i = vnode.data\n    if (isDef(i)) {\n      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive\n      if (isDef(i = i.hook) && isDef(i = i.init)) {\n        i(vnode, false /* hydrating */, parentElm, refElm)\n      }\n      // after calling the init hook, if the vnode is a child component\n      // it should've created a child instance and mounted it. the child\n      // component also has set the placeholder vnode's elm.\n      // in that case we can just return the element and be done.\n      /*\n        在调用了init钩子以后，如果VNode是一个子组件，它应该已经创建了一个子组件实例并挂载它。\n        子组件也应该设置了一个VNode占位符，我们直接返回组件实例即可。\n        意思就是如果已经存在组件实例，则不需要重新创建一个新的，我们要做的就是初始化组件以及激活组件即可，还是用原来的组件实例。\n      */\n      if (isDef(vnode.componentInstance)) {\n        /*初始化组件*/\n        initComponent(vnode, insertedVnodeQueue)\n        if (isTrue(isReactivated)) {\n          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)\n        }\n        return true\n      }\n    }\n  }\n\n  /*初始化组件*/\n  function initComponent (vnode, insertedVnodeQueue) {\n    /*把之前已经存在的VNode队列合并进去*/\n    if (isDef(vnode.data.pendingInsert)) {\n      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)\n    }\n    vnode.elm = vnode.componentInstance.$el\n    if (isPatchable(vnode)) {\n      /*调用create钩子*/\n      invokeCreateHooks(vnode, insertedVnodeQueue)\n      /*为scoped CSS 设置scoped id*/\n      setScope(vnode)\n    } else {\n      // empty component root.\n      // skip all element-related modules except for ref (#3455)\n      /*注册ref*/\n      registerRef(vnode)\n      // make sure to invoke the insert hook\n      insertedVnodeQueue.push(vnode)\n    }\n  }\n\n  function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n    let i\n    // hack for #4339: a reactivated component with inner transition\n    // does not trigger because the inner node's created hooks are not called\n    // again. It's not ideal to involve module-specific logic in here but\n    // there doesn't seem to be a better way to do it.\n    let innerNode = vnode\n    while (innerNode.componentInstance) {\n      innerNode = innerNode.componentInstance._vnode\n      if (isDef(i = innerNode.data) && isDef(i = i.transition)) {\n        for (i = 0; i < cbs.activate.length; ++i) {\n          cbs.activate[i](emptyNode, innerNode)\n        }\n        insertedVnodeQueue.push(innerNode)\n        break\n      }\n    }\n    // unlike a newly created component,\n    // a reactivated keep-alive component doesn't insert itself\n    insert(parentElm, vnode.elm, refElm)\n  }\n\n  function insert (parent, elm, ref) {\n    if (isDef(parent)) {\n      if (isDef(ref)) {\n        if (ref.parentNode === parent) {\n          nodeOps.insertBefore(parent, elm, ref)\n        }\n      } else {\n        nodeOps.appendChild(parent, elm)\n      }\n    }\n  }\n\n  function createChildren (vnode, children, insertedVnodeQueue) {\n    if (Array.isArray(children)) {\n      for (let i = 0; i < children.length; ++i) {\n        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true)\n      }\n    } else if (isPrimitive(vnode.text)) {\n      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))\n    }\n  }\n\n  /*是否可patch，检测tag是否定义*/\n  function isPatchable (vnode) {\n    while (vnode.componentInstance) {\n      vnode = vnode.componentInstance._vnode\n    }\n    return isDef(vnode.tag)\n  }\n\n  /*调用创建的钩子函数*/\n  function invokeCreateHooks (vnode, insertedVnodeQueue) {\n    /*循环调用modules中的create钩子*/\n    for (let i = 0; i < cbs.create.length; ++i) {\n      cbs.create[i](emptyNode, vnode)\n    }\n    i = vnode.data.hook // Reuse variable\n    if (isDef(i)) {\n      if (isDef(i.create)) i.create(emptyNode, vnode)\n      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)\n    }\n  }\n\n  // set scope id attribute for scoped CSS.\n  // this is implemented as a special case to avoid the overhead\n  // of going through the normal attribute patching process.\n  /*为scoped CSS 设置scoped id*/\n  function setScope (vnode) {\n    let i\n    let ancestor = vnode\n    while (ancestor) {\n      if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {\n        nodeOps.setAttribute(vnode.elm, i, '')\n      }\n      ancestor = ancestor.parent\n    }\n    // for slot content they should also get the scopeId from the host instance.\n    if (isDef(i = activeInstance) &&\n        i !== vnode.context &&\n        isDef(i = i.$options._scopeId)) {\n      nodeOps.setAttribute(vnode.elm, i, '')\n    }\n  }\n\n  /*添加节点*/\n  function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {\n    for (; startIdx <= endIdx; ++startIdx) {\n      createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm)\n    }\n  }\n\n  /*调用销毁的钩子函数*/\n  function invokeDestroyHook (vnode) {\n    let i, j\n    const data = vnode.data\n    /*调用destroy钩子*/\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)\n      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)\n    }\n    /*递归调用*/\n    if (isDef(i = vnode.children)) {\n      for (j = 0; j < vnode.children.length; ++j) {\n        invokeDestroyHook(vnode.children[j])\n      }\n    }\n  }\n\n  /*移除节点*/\n  function removeVnodes (parentElm, vnodes, startIdx, endIdx) {\n    for (; startIdx <= endIdx; ++startIdx) {\n      const ch = vnodes[startIdx]\n      if (isDef(ch)) {\n        if (isDef(ch.tag)) {\n          /*存在tag时*/\n          /*移除节点并调用remove钩子*/\n          removeAndInvokeRemoveHook(ch)\n          /*调用destroy钩子*/\n          invokeDestroyHook(ch)\n        } else { // Text node\n          /*不存在代表是一个text节点，直接移除*/\n          removeNode(ch.elm)\n        }\n      }\n    }\n  }\n\n  function removeAndInvokeRemoveHook (vnode, rm) {\n    if (isDef(rm) || isDef(vnode.data)) {\n      let i\n      const listeners = cbs.remove.length + 1\n      if (isDef(rm)) {\n        // we have a recursively passed down rm callback\n        // increase the listeners count\n        rm.listeners += listeners\n      } else {\n        // directly removing\n        rm = createRmCb(vnode.elm, listeners)\n      }\n      // recursively invoke hooks on child component root node\n      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {\n        removeAndInvokeRemoveHook(i, rm)\n      }\n      for (i = 0; i < cbs.remove.length; ++i) {\n        cbs.remove[i](vnode, rm)\n      }\n      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {\n        i(vnode, rm)\n      } else {\n        rm()\n      }\n    } else {\n      removeNode(vnode.elm)\n    }\n  }\n\n  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n    let oldStartIdx = 0\n    let newStartIdx = 0\n    let oldEndIdx = oldCh.length - 1\n    let oldStartVnode = oldCh[0]\n    let oldEndVnode = oldCh[oldEndIdx]\n    let newEndIdx = newCh.length - 1\n    let newStartVnode = newCh[0]\n    let newEndVnode = newCh[newEndIdx]\n    let oldKeyToIdx, idxInOld, elmToMove, refElm\n\n    // removeOnly is a special flag used only by <transition-group>\n    // to ensure removed elements stay in correct relative positions\n    // during leaving transitions\n    const canMove = !removeOnly\n\n    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n      if (isUndef(oldStartVnode)) {\n        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left\n      } else if (isUndef(oldEndVnode)) {\n        oldEndVnode = oldCh[--oldEndIdx]\n      } else if (sameVnode(oldStartVnode, newStartVnode)) {\n        /*前四种情况其实是指定key的时候，判定为同一个VNode，则直接patchVnode即可，分别比较oldCh以及newCh的两头节点2*2=4种情况*/\n        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)\n        oldStartVnode = oldCh[++oldStartIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else if (sameVnode(oldEndVnode, newEndVnode)) {\n        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))\n        oldStartVnode = oldCh[++oldStartIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else {\n        /*\n          生成一个key与旧VNode的key对应的哈希表（只有第一次进来undefined的时候会生成，也为后面检测重复的key值做铺垫）\n          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  \n          结果生成{key0: 0, key1: 1, key2: 2}\n        */\n        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld（即第几个节点，下标）*/\n        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null\n        if (isUndef(idxInOld)) { // New element\n          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/\n          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n          newStartVnode = newCh[++newStartIdx]\n        } else {\n          /*获取同key的老节点*/\n          elmToMove = oldCh[idxInOld]\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !elmToMove) {\n            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的Dom中，提示可能存在重复的key，确保v-for的时候item有唯一的key值*/\n            warn(\n              'It seems there are duplicate keys that is causing an update error. ' +\n              'Make sure each v-for item has a unique key.'\n            )\n          }\n          if (sameVnode(elmToMove, newStartVnode)) {\n            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/\n            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)\n            /*因为已经patchVnode进去了，所以将这个老节点赋值undefined，之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/\n            oldCh[idxInOld] = undefined\n            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实Dom节点前面*/\n            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)\n            newStartVnode = newCh[++newStartIdx]\n          } else {\n            // same key but different element. treat as new element\n            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候（比如说tag不一样或者是有不一样type的input标签），创建一个新的节点*/\n            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)\n            newStartVnode = newCh[++newStartIdx]\n          }\n        }\n      }\n    }\n    if (oldStartIdx > oldEndIdx) {\n      /*全部比较完成以后，发现oldStartIdx > oldEndIdx的话，说明老节点已经遍历完了，新节点比老节点多，所以这时候多出来的新节点需要一个一个创建出来加入到真实Dom中*/\n      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm\n      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)\n    } else if (newStartIdx > newEndIdx) {\n      /*如果全部比较完成以后发现newStartIdx > newEndIdx，则说明新节点已经遍历完了，老节点多余新节点，这个时候需要将多余的老节点从真实Dom中移除*/\n      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)\n    }\n  }\n\n  /*patch VNode节点*/\n  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {\n    /*两个VNode节点相同则直接返回*/\n    if (oldVnode === vnode) {\n      return\n    }\n    // reuse element for static trees.\n    // note we only do this if the vnode is cloned -\n    // if the new node is not cloned it means the render functions have been\n    // reset by the hot-reload-api and we need to do a proper re-render.\n    /*\n      如果新旧VNode都是静态的，同时它们的key相同（代表同一节点），\n      并且新的VNode是clone或者是标记了once（标记v-once属性，只渲染一次），\n      那么只需要替换elm以及componentInstance即可。\n    */\n    if (isTrue(vnode.isStatic) &&\n        isTrue(oldVnode.isStatic) &&\n        vnode.key === oldVnode.key &&\n        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {\n      vnode.elm = oldVnode.elm\n      vnode.componentInstance = oldVnode.componentInstance\n      return\n    }\n    let i\n    const data = vnode.data\n    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n      /*i = data.hook.prepatch，如果存在的话，见\"./create-component componentVNodeHooks\"。*/\n      i(oldVnode, vnode)\n    }\n    const elm = vnode.elm = oldVnode.elm\n    const oldCh = oldVnode.children\n    const ch = vnode.children\n    if (isDef(data) && isPatchable(vnode)) {\n      /*调用update回调以及update钩子*/\n      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n    }\n    /*如果这个VNode节点没有text文本时*/\n    if (isUndef(vnode.text)) {\n      if (isDef(oldCh) && isDef(ch)) {\n        /*新老节点均有children子节点，则对子节点进行diff操作，调用updateChildren*/\n        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)\n      } else if (isDef(ch)) {\n        /*如果老节点没有子节点而新节点存在子节点，先清空elm的文本内容，然后为当前节点加入子节点*/\n        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')\n        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)\n      } else if (isDef(oldCh)) {\n        /*当新节点没有子节点而老节点有子节点的时候，则移除所有ele的子节点*/\n        removeVnodes(elm, oldCh, 0, oldCh.length - 1)\n      } else if (isDef(oldVnode.text)) {\n        /*当新老节点都无子节点的时候，只是文本的替换，因为这个逻辑中新节点text不存在，所以直接去除ele的文本*/\n        nodeOps.setTextContent(elm, '')\n      }\n    } else if (oldVnode.text !== vnode.text) {\n      /*当新老节点text不一样时，直接替换这段文本*/\n      nodeOps.setTextContent(elm, vnode.text)\n    }\n    /*调用postpatch钩子*/\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)\n    }\n  }\n\n  function invokeInsertHook (vnode, queue, initial) {\n    // delay insert hooks for component root nodes, invoke them after the\n    // element is really inserted\n    if (isTrue(initial) && isDef(vnode.parent)) {\n      vnode.parent.data.pendingInsert = queue\n    } else {\n      for (let i = 0; i < queue.length; ++i) {\n        queue[i].data.hook.insert(queue[i])\n      }\n    }\n  }\n\n  let bailed = false\n  // list of modules that can skip create hook during hydration because they\n  // are already rendered on the client or has no need for initialization\n  /*这些模块是不需要初始化的或者已经在客户端被渲染了*/\n  const isRenderedModule = makeMap('attrs,style,class,staticClass,staticStyle,key')\n\n  // Note: this is a browser-only function so we can assume elms are DOM nodes.\n  /*合并节点到真实Dom上（因为这是一个只有在浏览器中运行的代码块，所以我们需要确认elms是真实的Dom节点）*/\n  function hydrate (elm, vnode, insertedVnodeQueue) {\n    if (process.env.NODE_ENV !== 'production') {\n      if (!assertNodeMatch(elm, vnode)) {\n        return false\n      }\n    }\n    vnode.elm = elm\n    const { tag, data, children } = vnode\n    if (isDef(data)) {\n      /*调用init钩子*/\n      if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)\n      if (isDef(i = vnode.componentInstance)) {\n        // child component. it should have hydrated its own tree.\n        initComponent(vnode, insertedVnodeQueue)\n        return true\n      }\n    }\n    if (isDef(tag)) {\n      if (isDef(children)) {\n        // empty element, allow client to pick up and populate children\n        if (!elm.hasChildNodes()) {\n          /*没有子节点的时候直接创建即可*/\n          createChildren(vnode, children, insertedVnodeQueue)\n        } else {\n          let childrenMatch = true\n          let childNode = elm.firstChild\n          /*遍历子节点进行合并真实Dom*/\n          for (let i = 0; i < children.length; i++) {\n            if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue)) {\n              childrenMatch = false\n              break\n            }\n            childNode = childNode.nextSibling\n          }\n          // if childNode is not null, it means the actual childNodes list is\n          // longer than the virtual children list.\n          /*如果childNode不为null，意味着真实的childNode列表比virtual childNodes更长*/\n          if (!childrenMatch || childNode) {\n            if (process.env.NODE_ENV !== 'production' &&\n                typeof console !== 'undefined' &&\n                !bailed) {\n              bailed = true\n              console.warn('Parent: ', elm)\n              console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)\n            }\n            return false\n          }\n        }\n      }\n      if (isDef(data)) {\n        for (const key in data) {\n          if (!isRenderedModule(key)) {\n            invokeCreateHooks(vnode, insertedVnodeQueue)\n            break\n          }\n        }\n      }\n    } else if (elm.data !== vnode.text) {\n      /*替换文本*/\n      elm.data = vnode.text\n    }\n    return true\n  }\n\n  function assertNodeMatch (node, vnode) {\n    if (isDef(vnode.tag)) {\n      return (\n        vnode.tag.indexOf('vue-component') === 0 ||\n        vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())\n      )\n    } else {\n      return node.nodeType === (vnode.isComment ? 8 : 3)\n    }\n  }\n\n  /*createPatchFunction的返回值，一个patch函数*/\n  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {\n    /*vnode不存在则直接调用销毁钩子*/\n    if (isUndef(vnode)) {\n      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)\n      return\n    }\n\n    let isInitialPatch = false\n    const insertedVnodeQueue = []\n\n    if (isUndef(oldVnode)) {\n      // empty mount (likely as component), create new root element\n      /*oldVnode未定义的时候，其实也就是root节点，创建一个新的节点*/\n      isInitialPatch = true\n      createElm(vnode, insertedVnodeQueue, parentElm, refElm)\n    } else {\n      /*标记旧的VNode是否有nodeType*/\n      const isRealElement = isDef(oldVnode.nodeType)\n      if (!isRealElement && sameVnode(oldVnode, vnode)) {\n        // patch existing root node\n        /*是同一个节点的时候直接修改现有的节点*/\n        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)\n      } else {\n        if (isRealElement) {\n          // mounting to a real element\n          // check if this is server-rendered content and if we can perform\n          // a successful hydration.\n          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n            /*当旧的VNode是服务端渲染的元素，hydrating记为true*/\n            oldVnode.removeAttribute(SSR_ATTR)\n            hydrating = true\n          }\n          if (isTrue(hydrating)) {\n            /*需要合并到真实Dom上*/\n            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n              /*调用insert钩子*/\n              invokeInsertHook(vnode, insertedVnodeQueue, true)\n              return oldVnode\n            } else if (process.env.NODE_ENV !== 'production') {\n              warn(\n                'The client-side rendered virtual DOM tree is not matching ' +\n                'server-rendered content. This is likely caused by incorrect ' +\n                'HTML markup, for example nesting block-level elements inside ' +\n                '<p>, or missing <tbody>. Bailing hydration and performing ' +\n                'full client-side render.'\n              )\n            }\n          }\n          // either not server-rendered, or hydration failed.\n          // create an empty node and replace it\n          /*如果不是服务端渲染或者合并到真实Dom失败，则创建一个空的VNode节点替换它*/\n          oldVnode = emptyNodeAt(oldVnode)\n        }\n        // replacing existing element\n        /*取代现有元素*/\n        const oldElm = oldVnode.elm\n        const parentElm = nodeOps.parentNode(oldElm)\n        createElm(\n          vnode,\n          insertedVnodeQueue,\n          // extremely rare edge case: do not insert if old element is in a\n          // leaving transition. Only happens when combining transition +\n          // keep-alive + HOCs. (#4590)\n          oldElm._leaveCb ? null : parentElm,\n          nodeOps.nextSibling(oldElm)\n        )\n\n        if (isDef(vnode.parent)) {\n          // component root element replaced.\n          // update parent placeholder node element, recursively\n          /*组件根节点被替换，遍历更新父节点element*/\n          let ancestor = vnode.parent\n          while (ancestor) {\n            ancestor.elm = vnode.elm\n            ancestor = ancestor.parent\n          }\n          if (isPatchable(vnode)) {\n            /*调用create回调*/\n            for (let i = 0; i < cbs.create.length; ++i) {\n              cbs.create[i](emptyNode, vnode.parent)\n            }\n          }\n        }\n\n        if (isDef(parentElm)) {\n          /*移除老节点*/\n          removeVnodes(parentElm, [oldVnode], 0, 0)\n        } else if (isDef(oldVnode.tag)) {\n          /*调用destroy钩子*/\n          invokeDestroyHook(oldVnode)\n        }\n      }\n    }\n\n    /*调用insert钩子*/\n    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)\n    return vnode.elm\n  }\n}\n"
  },
  {
    "path": "vue-src/core/vdom/vnode.js",
    "content": "/* @flow */\n\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  functionalContext: Component | void; // only for functional component root nodes\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n\n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions\n  ) {\n    /*当前节点的标签名*/\n    this.tag = tag\n    /*当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息*/\n    this.data = data\n    /*当前节点的子节点，是一个数组*/\n    this.children = children\n    /*当前节点的文本*/\n    this.text = text\n    /*当前虚拟节点对应的真实dom节点*/\n    this.elm = elm\n    /*当前节点的名字空间*/\n    this.ns = undefined\n    /*当前节点的编译作用域*/\n    this.context = context\n    /*函数化组件作用域*/\n    this.functionalContext = undefined\n    /*节点的key属性，被当作节点的标志，用以优化*/\n    this.key = data && data.key\n    /*组件的option选项*/\n    this.componentOptions = componentOptions\n    /*当前节点对应的组件的实例*/\n    this.componentInstance = undefined\n    /*当前节点的父节点*/\n    this.parent = undefined\n    /*简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false*/\n    this.raw = false\n    /*是否为静态节点*/\n    this.isStatic = false\n    /*是否作为跟节点插入*/\n    this.isRootInsert = true\n    /*是否为注释节点*/\n    this.isComment = false\n    /*是否为克隆节点*/\n    this.isCloned = false\n    /*是否有v-once指令*/\n    this.isOnce = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n\n/*创建一个空VNode节点*/\nexport const createEmptyVNode = () => {\n  const node = new VNode()\n  node.text = ''\n  node.isComment = true\n  return node\n}\n\n/*创建一个文本节点*/\nexport function createTextVNode (val: string | number) {\n  return new VNode(undefined, undefined, undefined, String(val))\n}\n\n// optimized shallow clone\n// used for static nodes and slot nodes because they may be reused across\n// multiple renders, cloning them avoids errors when DOM manipulations rely\n// on their elm reference.\n/*克隆一个VNode节点*/\nexport function cloneVNode (vnode: VNode): VNode {\n  const cloned = new VNode(\n    vnode.tag,\n    vnode.data,\n    vnode.children,\n    vnode.text,\n    vnode.elm,\n    vnode.context,\n    vnode.componentOptions\n  )\n  cloned.ns = vnode.ns\n  cloned.isStatic = vnode.isStatic\n  cloned.key = vnode.key\n  cloned.isCloned = true\n  return cloned\n}\n/*Github:https://github.com/answershuto*/\n/*对一个节点数组依次进行clone*/\nexport function cloneVNodes (vnodes: Array<VNode>): Array<VNode> {\n  const len = vnodes.length\n  const res = new Array(len)\n  for (let i = 0; i < len; i++) {\n    res[i] = cloneVNode(vnodes[i])\n  }\n  return res\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/html.js",
    "content": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n/*Github:https://github.com/answershuto*/\nexport default function html (el: ASTElement, dir: ASTDirective) {\n  if (dir.value) {\n    addProp(el, 'innerHTML', `_s(${dir.value})`)\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/index.js",
    "content": "import model from './model'\nimport text from './text'\nimport html from './html'\n\nexport default {\n  model,\n  text,\n  html\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/model.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\nimport { addHandler, addProp, getBindingAttr } from 'compiler/helpers'\nimport { genComponentModel, genAssignmentCode } from 'compiler/directives/model'\n\nlet warn\n\n// in some cases, the event used has to be determined at runtime\n// so we used some reserved tokens during compile.\nexport const RANGE_TOKEN = '__r'\nexport const CHECKBOX_RADIO_TOKEN = '__c'\n\nexport default function model (\n  el: ASTElement,\n  dir: ASTDirective,\n  _warn: Function\n): ?boolean {\n  warn = _warn\n  const value = dir.value\n  const modifiers = dir.modifiers\n  const tag = el.tag\n  const type = el.attrsMap.type\n\n  if (process.env.NODE_ENV !== 'production') {\n    const dynamicType = el.attrsMap['v-bind:type'] || el.attrsMap[':type']\n    if (tag === 'input' && dynamicType) {\n      warn(\n        `<input :type=\"${dynamicType}\" v-model=\"${value}\">:\\n` +\n        `v-model does not support dynamic input types. Use v-if branches instead.`\n      )\n    }\n    // inputs with type=\"file\" are read only and setting the input's\n    // value will throw an error.\n    if (tag === 'input' && type === 'file') {\n      warn(\n        `<${el.tag} v-model=\"${value}\" type=\"file\">:\\n` +\n        `File inputs are read only. Use a v-on:change listener instead.`\n      )\n    }\n  }\n\n  if (tag === 'select') {\n    genSelect(el, value, modifiers)\n  } else if (tag === 'input' && type === 'checkbox') {\n    genCheckboxModel(el, value, modifiers)\n  } else if (tag === 'input' && type === 'radio') {\n    genRadioModel(el, value, modifiers)\n  } else if (tag === 'input' || tag === 'textarea') {\n    genDefaultModel(el, value, modifiers)\n  } else if (!config.isReservedTag(tag)) {\n    genComponentModel(el, value, modifiers)\n    // component v-model doesn't need extra runtime\n    return false\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `<${el.tag} v-model=\"${value}\">: ` +\n      `v-model is not supported on this element type. ` +\n      'If you are working with contenteditable, it\\'s recommended to ' +\n      'wrap a library dedicated for that purpose inside a custom component.'\n    )\n  }\n\n  // ensure runtime directive metadata\n  return true\n}\n\nfunction genCheckboxModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n) {\n  const number = modifiers && modifiers.number\n  const valueBinding = getBindingAttr(el, 'value') || 'null'\n  const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'\n  const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'\n  addProp(el, 'checked',\n    `Array.isArray(${value})` +\n      `?_i(${value},${valueBinding})>-1` + (\n        trueValueBinding === 'true'\n          ? `:(${value})`\n          : `:_q(${value},${trueValueBinding})`\n      )\n  )\n  addHandler(el, CHECKBOX_RADIO_TOKEN,\n    `var $$a=${value},` +\n        '$$el=$event.target,' +\n        `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +\n    'if(Array.isArray($$a)){' +\n      `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +\n          '$$i=_i($$a,$$v);' +\n      `if($$c){$$i<0&&(${value}=$$a.concat($$v))}` +\n      `else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +\n    `}else{${genAssignmentCode(value, '$$c')}}`,\n    null, true\n  )\n}\n\nfunction genRadioModel (\n    el: ASTElement,\n    value: string,\n    modifiers: ?ASTModifiers\n) {\n  const number = modifiers && modifiers.number\n  let valueBinding = getBindingAttr(el, 'value') || 'null'\n  valueBinding = number ? `_n(${valueBinding})` : valueBinding\n  addProp(el, 'checked', `_q(${value},${valueBinding})`)\n  addHandler(el, CHECKBOX_RADIO_TOKEN, genAssignmentCode(value, valueBinding), null, true)\n}\n\nfunction genSelect (\n    el: ASTElement,\n    value: string,\n    modifiers: ?ASTModifiers\n) {\n  const number = modifiers && modifiers.number\n  const selectedVal = `Array.prototype.filter` +\n    `.call($event.target.options,function(o){return o.selected})` +\n    `.map(function(o){var val = \"_value\" in o ? o._value : o.value;` +\n    `return ${number ? '_n(val)' : 'val'}})`\n\n  const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'\n  let code = `var $$selectedVal = ${selectedVal};`\n  code = `${code} ${genAssignmentCode(value, assignment)}`\n  addHandler(el, 'change', code, null, true)\n}\n\nfunction genDefaultModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const type = el.attrsMap.type\n  const { lazy, number, trim } = modifiers || {}\n  const needCompositionGuard = !lazy && type !== 'range'\n  const event = lazy\n    ? 'change'\n    : type === 'range'\n      ? RANGE_TOKEN\n      : 'input'\n\n  let valueExpression = '$event.target.value'\n  if (trim) {\n    valueExpression = `$event.target.value.trim()`\n  }\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n\n  let code = genAssignmentCode(value, valueExpression)\n  if (needCompositionGuard) {\n    code = `if($event.target.composing)return;${code}`\n  }\n\n  addProp(el, 'value', `(${value})`)\n  addHandler(el, event, code, null, true)\n  if (trim || number || type === 'number') {\n    addHandler(el, 'blur', '$forceUpdate()')\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/text.js",
    "content": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n/*Github:https://github.com/answershuto*/\nexport default function text (el: ASTElement, dir: ASTDirective) {\n  if (dir.value) {\n    addProp(el, 'textContent', `_s(${dir.value})`)\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/index.js",
    "content": "/* @flow */\n\nimport { isUnaryTag, canBeLeftOpenTag } from './util'\nimport { genStaticKeys } from 'shared/util'\nimport { createCompiler } from 'compiler/index'\n\nimport modules from './modules/index'\nimport directives from './directives/index'\n\nimport {\n  isPreTag,\n  mustUseProp,\n  isReservedTag,\n  getTagNamespace\n} from '../util/index'\n\nexport const baseOptions: CompilerOptions = {\n  expectHTML: true,\n  modules,\n  directives,\n  isPreTag,\n  isUnaryTag,\n  mustUseProp,\n  canBeLeftOpenTag,\n  isReservedTag,\n  getTagNamespace,\n  staticKeys: genStaticKeys(modules)\n}\n\n/*这里会根据不同平台传递不同的baseOptions创建编译器*/\nconst { compile, compileToFunctions } = createCompiler(baseOptions)\nexport { compile, compileToFunctions }\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/modules/class.js",
    "content": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticClass = getAndRemoveAttr(el, 'class')\n  if (process.env.NODE_ENV !== 'production' && staticClass) {\n    const expression = parseText(staticClass, options.delimiters)\n    if (expression) {\n      warn(\n        `class=\"${staticClass}\": ` +\n        'Interpolation inside attributes has been removed. ' +\n        'Use v-bind or the colon shorthand instead. For example, ' +\n        'instead of <div class=\"{{ val }}\">, use <div :class=\"val\">.'\n      )\n    }\n  }\n  if (staticClass) {\n    el.staticClass = JSON.stringify(staticClass)\n  }\n  const classBinding = getBindingAttr(el, 'class', false /* getStatic */)\n  if (classBinding) {\n    el.classBinding = classBinding\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticClass) {\n    data += `staticClass:${el.staticClass},`\n  }\n  if (el.classBinding) {\n    data += `class:${el.classBinding},`\n  }\n  return data\n}\n\nexport default {\n  staticKeys: ['staticClass'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/modules/index.js",
    "content": "import klass from './class'\nimport style from './style'\n\nexport default [\n  klass,\n  style\n]\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/modules/style.js",
    "content": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport { parseStyleText } from 'web/util/style'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticStyle = getAndRemoveAttr(el, 'style')\n  if (staticStyle) {\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      const expression = parseText(staticStyle, options.delimiters)\n      if (expression) {\n        warn(\n          `style=\"${staticStyle}\": ` +\n          'Interpolation inside attributes has been removed. ' +\n          'Use v-bind or the colon shorthand instead. For example, ' +\n          'instead of <div style=\"{{ val }}\">, use <div :style=\"val\">.'\n        )\n      }\n    }\n    el.staticStyle = JSON.stringify(parseStyleText(staticStyle))\n  }\n\n  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)\n  if (styleBinding) {\n    el.styleBinding = styleBinding\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticStyle) {\n    data += `staticStyle:${el.staticStyle},`\n  }\n  if (el.styleBinding) {\n    data += `style:(${el.styleBinding}),`\n  }\n  return data\n}\n\nexport default {\n  staticKeys: ['staticStyle'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue-src/platforms/web/compiler/util.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n/*Github:https://github.com/answershuto*/\nexport const isUnaryTag = makeMap(\n  'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +\n  'link,meta,param,source,track,wbr'\n)\n\n// Elements that you can, intentionally, leave open\n// (and which close themselves)\nexport const canBeLeftOpenTag = makeMap(\n  'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'\n)\n/*Github:https://github.com/answershuto*/\n// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3\n// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content\nexport const isNonPhrasingTag = makeMap(\n  'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +\n  'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +\n  'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +\n  'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +\n  'title,tr,track'\n)\n"
  },
  {
    "path": "vue-src/platforms/web/compiler.js",
    "content": "/* @flow */\n\nexport { parseComponent } from 'sfc/parser'\nexport { compile, compileToFunctions } from './compiler/index'\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/class-util.js",
    "content": "/* @flow */\n\n/**\n * Add class with compatibility for SVG since classList is not supported on\n * SVG elements in IE\n */\nexport function addClass (el: HTMLElement, cls: ?string) {\n  /* istanbul ignore if */\n  if (!cls || !(cls = cls.trim())) {\n    return\n  }\n\n  /* istanbul ignore else */\n  if (el.classList) {\n    if (cls.indexOf(' ') > -1) {\n      cls.split(/\\s+/).forEach(c => el.classList.add(c))\n    } else {\n      el.classList.add(cls)\n    }\n  } else {\n    const cur = ` ${el.getAttribute('class') || ''} `\n    if (cur.indexOf(' ' + cls + ' ') < 0) {\n      el.setAttribute('class', (cur + cls).trim())\n    }\n  }\n}\n\n/**\n * Remove class with compatibility for SVG since classList is not supported on\n * SVG elements in IE\n */\nexport function removeClass (el: HTMLElement, cls: ?string) {\n  /* istanbul ignore if */\n  if (!cls || !(cls = cls.trim())) {\n    return\n  }\n\n  /* istanbul ignore else */\n  if (el.classList) {\n    if (cls.indexOf(' ') > -1) {\n      cls.split(/\\s+/).forEach(c => el.classList.remove(c))\n    } else {\n      el.classList.remove(cls)\n    }\n  } else {\n    let cur = ` ${el.getAttribute('class') || ''} `\n    const tar = ' ' + cls + ' '\n    while (cur.indexOf(tar) >= 0) {\n      cur = cur.replace(tar, ' ')\n    }\n    el.setAttribute('class', cur.trim())\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/components/index.js",
    "content": "import Transition from './transition'\nimport TransitionGroup from './transition-group'\n\nexport default {\n  Transition,\n  TransitionGroup\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/components/transition-group.js",
    "content": "/* @flow */\n\n// Provides transition support for list items.\n// supports move transitions using the FLIP technique.\n\n// Because the vdom's children update algorithm is \"unstable\" - i.e.\n// it doesn't guarantee the relative positioning of removed elements,\n// we force transition-group to update its children into two passes:\n// in the first pass, we remove all nodes that need to be removed,\n// triggering their leaving transition; in the second pass, we insert/move\n// into the final desired state. This way in the second pass removed\n// nodes will remain where they should be.\n\nimport { warn, extend } from 'core/util/index'\nimport { addClass, removeClass } from '../class-util'\nimport { transitionProps, extractTransitionData } from './transition'\n\nimport {\n  hasTransition,\n  getTransitionInfo,\n  transitionEndEvent,\n  addTransitionClass,\n  removeTransitionClass\n} from '../transition-util'\n\nconst props = extend({\n  tag: String,\n  moveClass: String\n}, transitionProps)\n\ndelete props.mode\n\nexport default {\n  props,\n\n  render (h: Function) {\n    const tag: string = this.tag || this.$vnode.data.tag || 'span'\n    const map: Object = Object.create(null)\n    const prevChildren: Array<VNode> = this.prevChildren = this.children\n    const rawChildren: Array<VNode> = this.$slots.default || []\n    const children: Array<VNode> = this.children = []\n    const transitionData: Object = extractTransitionData(this)\n\n    for (let i = 0; i < rawChildren.length; i++) {\n      const c: VNode = rawChildren[i]\n      if (c.tag) {\n        if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {\n          children.push(c)\n          map[c.key] = c\n          ;(c.data || (c.data = {})).transition = transitionData\n        } else if (process.env.NODE_ENV !== 'production') {\n          const opts: ?VNodeComponentOptions = c.componentOptions\n          const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag\n          warn(`<transition-group> children must be keyed: <${name}>`)\n        }\n      }\n    }\n\n    if (prevChildren) {\n      const kept: Array<VNode> = []\n      const removed: Array<VNode> = []\n      for (let i = 0; i < prevChildren.length; i++) {\n        const c: VNode = prevChildren[i]\n        c.data.transition = transitionData\n        c.data.pos = c.elm.getBoundingClientRect()\n        if (map[c.key]) {\n          kept.push(c)\n        } else {\n          removed.push(c)\n        }\n      }\n      this.kept = h(tag, null, kept)\n      this.removed = removed\n    }\n\n    return h(tag, null, children)\n  },\n\n  beforeUpdate () {\n    // force removing pass\n    this.__patch__(\n      this._vnode,\n      this.kept,\n      false, // hydrating\n      true // removeOnly (!important, avoids unnecessary moves)\n    )\n    this._vnode = this.kept\n  },\n\n  updated () {\n    const children: Array<VNode> = this.prevChildren\n    const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')\n    if (!children.length || !this.hasMove(children[0].elm, moveClass)) {\n      return\n    }\n\n    // we divide the work into three loops to avoid mixing DOM reads and writes\n    // in each iteration - which helps prevent layout thrashing.\n    children.forEach(callPendingCbs)\n    children.forEach(recordPosition)\n    children.forEach(applyTranslation)\n\n    // force reflow to put everything in position\n    const body: any = document.body\n    const f: number = body.offsetHeight // eslint-disable-line\n\n    children.forEach((c: VNode) => {\n      if (c.data.moved) {\n        var el: any = c.elm\n        var s: any = el.style\n        addTransitionClass(el, moveClass)\n        s.transform = s.WebkitTransform = s.transitionDuration = ''\n        el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {\n          if (!e || /transform$/.test(e.propertyName)) {\n            el.removeEventListener(transitionEndEvent, cb)\n            el._moveCb = null\n            removeTransitionClass(el, moveClass)\n          }\n        })\n      }\n    })\n  },\n\n  methods: {\n    hasMove (el: any, moveClass: string): boolean {\n      /* istanbul ignore if */\n      if (!hasTransition) {\n        return false\n      }\n      if (this._hasMove != null) {\n        return this._hasMove\n      }\n      // Detect whether an element with the move class applied has\n      // CSS transitions. Since the element may be inside an entering\n      // transition at this very moment, we make a clone of it and remove\n      // all other transition classes applied to ensure only the move class\n      // is applied.\n      const clone: HTMLElement = el.cloneNode()\n      if (el._transitionClasses) {\n        el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) })\n      }\n      addClass(clone, moveClass)\n      clone.style.display = 'none'\n      this.$el.appendChild(clone)\n      const info: Object = getTransitionInfo(clone)\n      this.$el.removeChild(clone)\n      return (this._hasMove = info.hasTransform)\n    }\n  }\n}\n\nfunction callPendingCbs (c: VNode) {\n  /* istanbul ignore if */\n  if (c.elm._moveCb) {\n    c.elm._moveCb()\n  }\n  /* istanbul ignore if */\n  if (c.elm._enterCb) {\n    c.elm._enterCb()\n  }\n}\n\nfunction recordPosition (c: VNode) {\n  c.data.newPos = c.elm.getBoundingClientRect()\n}\n\nfunction applyTranslation (c: VNode) {\n  const oldPos = c.data.pos\n  const newPos = c.data.newPos\n  const dx = oldPos.left - newPos.left\n  const dy = oldPos.top - newPos.top\n  if (dx || dy) {\n    c.data.moved = true\n    const s = c.elm.style\n    s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`\n    s.transitionDuration = '0s'\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/components/transition.js",
    "content": "/* @flow */\n\n// Provides transition support for a single element/component.\n// supports transition mode (out-in / in-out)\n\nimport { warn } from 'core/util/index'\nimport { camelize, extend, isPrimitive } from 'shared/util'\nimport { mergeVNodeHook, getFirstComponentChild } from 'core/vdom/helpers/index'\n\nexport const transitionProps = {\n  name: String,\n  appear: Boolean,\n  css: Boolean,\n  mode: String,\n  type: String,\n  enterClass: String,\n  leaveClass: String,\n  enterToClass: String,\n  leaveToClass: String,\n  enterActiveClass: String,\n  leaveActiveClass: String,\n  appearClass: String,\n  appearActiveClass: String,\n  appearToClass: String,\n  duration: [Number, String, Object]\n}\n\n// in case the child is also an abstract component, e.g. <keep-alive>\n// we want to recursively retrieve the real component to be rendered\nfunction getRealChild (vnode: ?VNode): ?VNode {\n  const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n  if (compOptions && compOptions.Ctor.options.abstract) {\n    return getRealChild(getFirstComponentChild(compOptions.children))\n  } else {\n    return vnode\n  }\n}\n\nexport function extractTransitionData (comp: Component): Object {\n  const data = {}\n  const options: ComponentOptions = comp.$options\n  // props\n  for (const key in options.propsData) {\n    data[key] = comp[key]\n  }\n  // events.\n  // extract listeners and pass them directly to the transition methods\n  const listeners: ?Object = options._parentListeners\n  for (const key in listeners) {\n    data[camelize(key)] = listeners[key]\n  }\n  return data\n}\n\nfunction placeholder (h: Function, rawChild: VNode): ?VNode {\n  if (/\\d-keep-alive$/.test(rawChild.tag)) {\n    return h('keep-alive', {\n      props: rawChild.componentOptions.propsData\n    })\n  }\n}\n\nfunction hasParentTransition (vnode: VNode): ?boolean {\n  while ((vnode = vnode.parent)) {\n    if (vnode.data.transition) {\n      return true\n    }\n  }\n}\n\nfunction isSameChild (child: VNode, oldChild: VNode): boolean {\n  return oldChild.key === child.key && oldChild.tag === child.tag\n}\n\nexport default {\n  name: 'transition',\n  props: transitionProps,\n  abstract: true,\n\n  render (h: Function) {\n    let children: ?Array<VNode> = this.$slots.default\n    if (!children) {\n      return\n    }\n\n    // filter out text nodes (possible whitespaces)\n    children = children.filter((c: VNode) => c.tag)\n    /* istanbul ignore if */\n    if (!children.length) {\n      return\n    }\n\n    // warn multiple elements\n    if (process.env.NODE_ENV !== 'production' && children.length > 1) {\n      warn(\n        '<transition> can only be used on a single element. Use ' +\n        '<transition-group> for lists.',\n        this.$parent\n      )\n    }\n\n    const mode: string = this.mode\n\n    // warn invalid mode\n    if (process.env.NODE_ENV !== 'production' &&\n        mode && mode !== 'in-out' && mode !== 'out-in') {\n      warn(\n        'invalid <transition> mode: ' + mode,\n        this.$parent\n      )\n    }\n\n    const rawChild: VNode = children[0]\n\n    // if this is a component root node and the component's\n    // parent container node also has transition, skip.\n    if (hasParentTransition(this.$vnode)) {\n      return rawChild\n    }\n\n    // apply transition data to child\n    // use getRealChild() to ignore abstract components e.g. keep-alive\n    const child: ?VNode = getRealChild(rawChild)\n    /* istanbul ignore if */\n    if (!child) {\n      return rawChild\n    }\n\n    if (this._leaving) {\n      return placeholder(h, rawChild)\n    }\n\n    // ensure a key that is unique to the vnode type and to this transition\n    // component instance. This key will be used to remove pending leaving nodes\n    // during entering.\n    const id: string = `__transition-${this._uid}-`\n    child.key = child.key == null\n      ? id + child.tag\n      : isPrimitive(child.key)\n        ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)\n        : child.key\n\n    const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)\n    const oldRawChild: VNode = this._vnode\n    const oldChild: VNode = getRealChild(oldRawChild)\n\n    // mark v-show\n    // so that the transition module can hand over the control to the directive\n    if (child.data.directives && child.data.directives.some(d => d.name === 'show')) {\n      child.data.show = true\n    }\n\n    if (oldChild && oldChild.data && !isSameChild(child, oldChild)) {\n      // replace old child transition data with fresh one\n      // important for dynamic transitions!\n      const oldData: Object = oldChild && (oldChild.data.transition = extend({}, data))\n      // handle transition mode\n      if (mode === 'out-in') {\n        // return placeholder node and queue update when leave finishes\n        this._leaving = true\n        mergeVNodeHook(oldData, 'afterLeave', () => {\n          this._leaving = false\n          this.$forceUpdate()\n        })\n        return placeholder(h, rawChild)\n      } else if (mode === 'in-out') {\n        let delayedLeave\n        const performLeave = () => { delayedLeave() }\n        mergeVNodeHook(data, 'afterEnter', performLeave)\n        mergeVNodeHook(data, 'enterCancelled', performLeave)\n        mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })\n      }\n    }\n\n    return rawChild\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/directives/index.js",
    "content": "import model from './model'\nimport show from './show'\n\nexport default {\n  model,\n  show\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/directives/model.js",
    "content": "/**\n * Not type checking this file because flow doesn't like attaching\n * properties to Elements.\n */\n\nimport { looseEqual, looseIndexOf } from 'shared/util'\nimport { warn, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'\n\n/* istanbul ignore if */\nif (isIE9) {\n  // http://www.matts411.com/post/internet-explorer-9-oninput/\n  document.addEventListener('selectionchange', () => {\n    const el = document.activeElement\n    if (el && el.vmodel) {\n      trigger(el, 'input')\n    }\n  })\n}\n\nexport default {\n  inserted (el, binding, vnode) {\n    if (vnode.tag === 'select') {\n      const cb = () => {\n        setSelected(el, binding, vnode.context)\n      }\n      cb()\n      /* istanbul ignore if */\n      if (isIE || isEdge) {\n        setTimeout(cb, 0)\n      }\n    } else if (vnode.tag === 'textarea' || el.type === 'text' || el.type === 'password') {\n      el._vModifiers = binding.modifiers\n      if (!binding.modifiers.lazy) {\n        // Safari < 10.2 & UIWebView doesn't fire compositionend when\n        // switching focus before confirming composition choice\n        // this also fixes the issue where some browsers e.g. iOS Chrome\n        // fires \"change\" instead of \"input\" on autocomplete.\n        el.addEventListener('change', onCompositionEnd)\n        if (!isAndroid) {\n          el.addEventListener('compositionstart', onCompositionStart)\n          el.addEventListener('compositionend', onCompositionEnd)\n        }\n        /* istanbul ignore if */\n        if (isIE9) {\n          el.vmodel = true\n        }\n      }\n    }\n  },\n  componentUpdated (el, binding, vnode) {\n    if (vnode.tag === 'select') {\n      setSelected(el, binding, vnode.context)\n      // in case the options rendered by v-for have changed,\n      // it's possible that the value is out-of-sync with the rendered options.\n      // detect such cases and filter out values that no longer has a matching\n      // option in the DOM.\n      const needReset = el.multiple\n        ? binding.value.some(v => hasNoMatchingOption(v, el.options))\n        : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, el.options)\n      if (needReset) {\n        trigger(el, 'change')\n      }\n    }\n  }\n}\n\nfunction setSelected (el, binding, vm) {\n  const value = binding.value\n  const isMultiple = el.multiple\n  if (isMultiple && !Array.isArray(value)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `<select multiple v-model=\"${binding.expression}\"> ` +\n      `expects an Array value for its binding, but got ${\n        Object.prototype.toString.call(value).slice(8, -1)\n      }`,\n      vm\n    )\n    return\n  }\n  let selected, option\n  for (let i = 0, l = el.options.length; i < l; i++) {\n    option = el.options[i]\n    if (isMultiple) {\n      selected = looseIndexOf(value, getValue(option)) > -1\n      if (option.selected !== selected) {\n        option.selected = selected\n      }\n    } else {\n      if (looseEqual(getValue(option), value)) {\n        if (el.selectedIndex !== i) {\n          el.selectedIndex = i\n        }\n        return\n      }\n    }\n  }\n  if (!isMultiple) {\n    el.selectedIndex = -1\n  }\n}\n\nfunction hasNoMatchingOption (value, options) {\n  for (let i = 0, l = options.length; i < l; i++) {\n    if (looseEqual(getValue(options[i]), value)) {\n      return false\n    }\n  }\n  return true\n}\n\nfunction getValue (option) {\n  return '_value' in option\n    ? option._value\n    : option.value\n}\n\nfunction onCompositionStart (e) {\n  e.target.composing = true\n}\n\nfunction onCompositionEnd (e) {\n  e.target.composing = false\n  trigger(e.target, 'input')\n}\n\nfunction trigger (el, type) {\n  const e = document.createEvent('HTMLEvents')\n  e.initEvent(type, true, true)\n  el.dispatchEvent(e)\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/directives/show.js",
    "content": "/* @flow */\n\nimport { isIE9 } from 'core/util/env'\nimport { enter, leave } from '../modules/transition'\n\n// recursively search for possible transition defined inside the component root\nfunction locateNode (vnode: VNode): VNodeWithData {\n  return vnode.componentInstance && (!vnode.data || !vnode.data.transition)\n    ? locateNode(vnode.componentInstance._vnode)\n    : vnode\n}\n\nexport default {\n  bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {\n    vnode = locateNode(vnode)\n    const transition = vnode.data && vnode.data.transition\n    const originalDisplay = el.__vOriginalDisplay =\n      el.style.display === 'none' ? '' : el.style.display\n    if (value && transition && !isIE9) {\n      vnode.data.show = true\n      enter(vnode, () => {\n        el.style.display = originalDisplay\n      })\n    } else {\n      el.style.display = value ? originalDisplay : 'none'\n    }\n  },\n\n  update (el: any, { value, oldValue }: VNodeDirective, vnode: VNodeWithData) {\n    /* istanbul ignore if */\n    if (value === oldValue) return\n    vnode = locateNode(vnode)\n    const transition = vnode.data && vnode.data.transition\n    if (transition && !isIE9) {\n      vnode.data.show = true\n      if (value) {\n        enter(vnode, () => {\n          el.style.display = el.__vOriginalDisplay\n        })\n      } else {\n        leave(vnode, () => {\n          el.style.display = 'none'\n        })\n      }\n    } else {\n      el.style.display = value ? el.__vOriginalDisplay : 'none'\n    }\n  },\n\n  unbind (\n    el: any,\n    binding: VNodeDirective,\n    vnode: VNodeWithData,\n    oldVnode: VNodeWithData,\n    isDestroy: boolean\n  ) {\n    if (!isDestroy) {\n      el.style.display = el.__vOriginalDisplay\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/index.js",
    "content": "/* @flow */\n\nimport Vue from 'core/index'\nimport config from 'core/config'\nimport { extend, noop } from 'shared/util'\nimport { mountComponent } from 'core/instance/lifecycle'\nimport { devtools, inBrowser, isChrome } from 'core/util/index'\n\nimport {\n  query,\n  mustUseProp,\n  isReservedTag,\n  isReservedAttr,\n  getTagNamespace,\n  isUnknownElement\n} from 'web/util/index'\n\nimport { patch } from './patch'\nimport platformDirectives from './directives/index'\nimport platformComponents from './components/index'\n\n// install platform specific utils\nVue.config.mustUseProp = mustUseProp\nVue.config.isReservedTag = isReservedTag\nVue.config.isReservedAttr = isReservedAttr\nVue.config.getTagNamespace = getTagNamespace\nVue.config.isUnknownElement = isUnknownElement\n\n// install platform runtime directives & components\nextend(Vue.options.directives, platformDirectives)\nextend(Vue.options.components, platformComponents)\n\n// install platform patch function\nVue.prototype.__patch__ = inBrowser ? patch : noop\n\n// public mount method\n/*组件挂载方法*/\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  /*获取DOM实例对象*/\n  el = el && inBrowser ? query(el) : undefined\n  /*挂载组件*/\n  return mountComponent(this, el, hydrating)\n}\n\n// devtools global hook\n/* istanbul ignore next */\nsetTimeout(() => {\n  if (config.devtools) {\n    if (devtools) {\n      devtools.emit('init', Vue)\n    } else if (process.env.NODE_ENV !== 'production' && isChrome) {\n      console[console.info ? 'info' : 'log'](\n        'Download the Vue Devtools extension for a better development experience:\\n' +\n        'https://github.com/vuejs/vue-devtools'\n      )\n    }\n  }\n  if (process.env.NODE_ENV !== 'production' &&\n      config.productionTip !== false &&\n      inBrowser && typeof console !== 'undefined') {\n    console[console.info ? 'info' : 'log'](\n      `You are running Vue in development mode.\\n` +\n      `Make sure to turn on production mode when deploying for production.\\n` +\n      `See more tips at https://vuejs.org/guide/deployment.html`\n    )\n  }\n}, 0)\n\nexport default Vue\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/attrs.js",
    "content": "/* @flow */\n\nimport { isIE9 } from 'core/util/env'\n\nimport {\n  extend,\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  isXlink,\n  xlinkNS,\n  getXlinkProp,\n  isBooleanAttr,\n  isEnumeratedAttr,\n  isFalsyAttrValue\n} from 'web/util/index'\n\n/*更新attr*/\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  /*如果旧的以及新的VNode节点均没有attr属性，则直接返回*/\n  if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {\n    return\n  }\n  let key, cur, old\n  /*VNode节点对应的Dom实例*/\n  const elm = vnode.elm\n  /*旧VNode节点的attr*/\n  const oldAttrs = oldVnode.data.attrs || {}\n  /*新VNode节点的attr*/\n  let attrs: any = vnode.data.attrs || {}\n  // clone observed objects, as the user probably wants to mutate it\n  /*如果新的VNode的attr已经有__ob__（代表已经被Observe处理过了）， 进行深拷贝*/\n  if (isDef(attrs.__ob__)) {\n    attrs = vnode.data.attrs = extend({}, attrs)\n  }\n\n  /*遍历attr，不一致则替换*/\n  for (key in attrs) {\n    cur = attrs[key]\n    old = oldAttrs[key]\n    if (old !== cur) {\n      setAttr(elm, key, cur)\n    }\n  }\n  // #4391: in IE9, setting type can reset value for input[type=radio]\n  /* istanbul ignore if */\n  if (isIE9 && attrs.value !== oldAttrs.value) {\n    setAttr(elm, 'value', attrs.value)\n  }\n  for (key in oldAttrs) {\n    if (isUndef(attrs[key])) {\n      if (isXlink(key)) {\n        elm.removeAttributeNS(xlinkNS, getXlinkProp(key))\n      } else if (!isEnumeratedAttr(key)) {\n        elm.removeAttribute(key)\n      }\n    }\n  }\n}\n\n/*设置attr*/\nfunction setAttr (el: Element, key: string, value: any) {\n  if (isBooleanAttr(key)) {\n    // set attribute for blank value\n    // e.g. <option disabled>Select one</option>\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      el.setAttribute(key, key)\n    }\n  } else if (isEnumeratedAttr(key)) {\n    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')\n  } else if (isXlink(key)) {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttributeNS(xlinkNS, getXlinkProp(key))\n    } else {\n      el.setAttributeNS(xlinkNS, key, value)\n    }\n  } else {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      el.setAttribute(key, value)\n    }\n  }\n}\n\nexport default {\n  create: updateAttrs,\n  update: updateAttrs\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/class.js",
    "content": "/* @flow */\n\nimport {\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  concat,\n  stringifyClass,\n  genClassForVnode\n} from 'web/util/index'\n\n/*更新VNode的class*/\nfunction updateClass (oldVnode: any, vnode: any) {\n  const el = vnode.elm\n  const data: VNodeData = vnode.data\n  const oldData: VNodeData = oldVnode.data\n  if (\n    isUndef(data.staticClass) &&\n    isUndef(data.class) && (\n      isUndef(oldData) || (\n        isUndef(oldData.staticClass) &&\n        isUndef(oldData.class)\n      )\n    )\n  ) {\n    return\n  }\n\n  let cls = genClassForVnode(vnode)\n\n  // handle transition classes\n  const transitionClass = el._transitionClasses\n  if (isDef(transitionClass)) {\n    cls = concat(cls, stringifyClass(transitionClass))\n  }\n\n  // set the class\n  if (cls !== el._prevClass) {\n    el.setAttribute('class', cls)\n    el._prevClass = cls\n  }\n}\n\nexport default {\n  create: updateClass,\n  update: updateClass\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/dom-props.js",
    "content": "/* @flow */\n\nimport { isDef, isUndef, extend, toNumber } from 'shared/util'\n\nfunction updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {\n    return\n  }\n  let key, cur\n  const elm: any = vnode.elm\n  const oldProps = oldVnode.data.domProps || {}\n  let props = vnode.data.domProps || {}\n  // clone observed objects, as the user probably wants to mutate it\n  if (isDef(props.__ob__)) {\n    props = vnode.data.domProps = extend({}, props)\n  }\n\n  for (key in oldProps) {\n    if (isUndef(props[key])) {\n      elm[key] = ''\n    }\n  }\n  for (key in props) {\n    cur = props[key]\n    // ignore children if the node has textContent or innerHTML,\n    // as these will throw away existing DOM nodes and cause removal errors\n    // on subsequent patches (#3360)\n    if (key === 'textContent' || key === 'innerHTML') {\n      if (vnode.children) vnode.children.length = 0\n      if (cur === oldProps[key]) continue\n    }\n\n    if (key === 'value') {\n      // store value as _value as well since\n      // non-string values will be stringified\n      elm._value = cur\n      // avoid resetting cursor position when value is the same\n      const strCur = isUndef(cur) ? '' : String(cur)\n      if (shouldUpdateValue(elm, vnode, strCur)) {\n        elm.value = strCur\n      }\n    } else {\n      elm[key] = cur\n    }\n  }\n}\n\n// check platforms/web/util/attrs.js acceptValue\ntype acceptValueElm = HTMLInputElement | HTMLSelectElement | HTMLOptionElement;\n\nfunction shouldUpdateValue (\n  elm: acceptValueElm,\n  vnode: VNodeWithData,\n  checkVal: string\n): boolean {\n  return (!elm.composing && (\n    vnode.tag === 'option' ||\n    isDirty(elm, checkVal) ||\n    isInputChanged(elm, checkVal)\n  ))\n}\n\nfunction isDirty (elm: acceptValueElm, checkVal: string): boolean {\n  // return true when textbox (.number and .trim) loses focus and its value is not equal to the updated value\n  return document.activeElement !== elm && elm.value !== checkVal\n}\n\nfunction isInputChanged (elm: any, newVal: string): boolean {\n  const value = elm.value\n  const modifiers = elm._vModifiers // injected by v-model runtime\n  if ((isDef(modifiers) && modifiers.number) || elm.type === 'number') {\n    return toNumber(value) !== toNumber(newVal)\n  }\n  if (isDef(modifiers) && modifiers.trim) {\n    return value.trim() !== newVal.trim()\n  }\n  return value !== newVal\n}\n\nexport default {\n  create: updateDOMProps,\n  update: updateDOMProps\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/events.js",
    "content": "/* @flow */\n\nimport { isDef, isUndef } from 'shared/util'\nimport { updateListeners } from 'core/vdom/helpers/index'\nimport { isChrome, isIE, supportsPassive } from 'core/util/env'\nimport { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'\n\n// normalize v-model event tokens that can only be determined at runtime.\n// it's important to place the event as the first in the array because\n// the whole point is ensuring the v-model callback gets called before\n// user-attached handlers.\nfunction normalizeEvents (on) {\n  let event\n  /* istanbul ignore if */\n  if (isDef(on[RANGE_TOKEN])) {\n    // IE input[type=range] only supports `change` event\n    event = isIE ? 'change' : 'input'\n    on[event] = [].concat(on[RANGE_TOKEN], on[event] || [])\n    delete on[RANGE_TOKEN]\n  }\n  if (isDef(on[CHECKBOX_RADIO_TOKEN])) {\n    // Chrome fires microtasks in between click/change, leads to #4521\n    event = isChrome ? 'click' : 'change'\n    on[event] = [].concat(on[CHECKBOX_RADIO_TOKEN], on[event] || [])\n    delete on[CHECKBOX_RADIO_TOKEN]\n  }\n}\n\nlet target: HTMLElement\n\nfunction add (\n  event: string,\n  handler: Function,\n  once: boolean,\n  capture: boolean,\n  passive: boolean\n) {\n  if (once) {\n    const oldHandler = handler\n    const _target = target // save current target element in closure\n    handler = function (ev) {\n      const res = arguments.length === 1\n        ? oldHandler(ev)\n        : oldHandler.apply(null, arguments)\n      if (res !== null) {\n        remove(event, handler, capture, _target)\n      }\n    }\n  }\n  target.addEventListener(\n    event,\n    handler,\n    supportsPassive\n      ? { capture, passive }\n      : capture\n  )\n}\n\nfunction remove (\n  event: string,\n  handler: Function,\n  capture: boolean,\n  _target?: HTMLElement\n) {\n  (_target || target).removeEventListener(event, handler, capture)\n}\n\nfunction updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {\n    return\n  }\n  const on = vnode.data.on || {}\n  const oldOn = oldVnode.data.on || {}\n  target = vnode.elm\n  normalizeEvents(on)\n  updateListeners(on, oldOn, add, remove, vnode.context)\n}\n\nexport default {\n  create: updateDOMListeners,\n  update: updateDOMListeners\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/index.js",
    "content": "import attrs from './attrs'\nimport klass from './class'\nimport events from './events'\nimport domProps from './dom-props'\nimport style from './style'\nimport transition from './transition'\n\nexport default [\n  attrs,\n  klass,\n  events,\n  domProps,\n  style,\n  transition\n]\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/style.js",
    "content": "/* @flow */\n\nimport { getStyle, normalizeStyleBinding } from 'web/util/style'\nimport { cached, camelize, extend, isDef, isUndef } from 'shared/util'\n\nconst cssVarRE = /^--/\nconst importantRE = /\\s*!important$/\nconst setProp = (el, name, val) => {\n  /* istanbul ignore if */\n  if (cssVarRE.test(name)) {\n    el.style.setProperty(name, val)\n  } else if (importantRE.test(val)) {\n    el.style.setProperty(name, val.replace(importantRE, ''), 'important')\n  } else {\n    const normalizedName = normalize(name)\n    if (Array.isArray(val)) {\n      // Support values array created by autoprefixer, e.g.\n      // {display: [\"-webkit-box\", \"-ms-flexbox\", \"flex\"]}\n      // Set them one by one, and the browser will only set those it can recognize\n      for (let i = 0, len = val.length; i < len; i++) {\n        el.style[normalizedName] = val[i]\n      }\n    } else {\n      el.style[normalizedName] = val\n    }\n  }\n}\n\nconst prefixes = ['Webkit', 'Moz', 'ms']\n\nlet testEl\nconst normalize = cached(function (prop) {\n  testEl = testEl || document.createElement('div')\n  prop = camelize(prop)\n  if (prop !== 'filter' && (prop in testEl.style)) {\n    return prop\n  }\n  const upper = prop.charAt(0).toUpperCase() + prop.slice(1)\n  for (let i = 0; i < prefixes.length; i++) {\n    const prefixed = prefixes[i] + upper\n    if (prefixed in testEl.style) {\n      return prefixed\n    }\n  }\n})\n\nfunction updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  const data = vnode.data\n  const oldData = oldVnode.data\n\n  if (isUndef(data.staticStyle) && isUndef(data.style) &&\n      isUndef(oldData.staticStyle) && isUndef(oldData.style)) {\n    return\n  }\n\n  let cur, name\n  const el: any = vnode.elm\n  const oldStaticStyle: any = oldData.staticStyle\n  const oldStyleBinding: any = oldData.normalizedStyle || oldData.style || {}\n\n  // if static style exists, stylebinding already merged into it when doing normalizeStyleData\n  const oldStyle = oldStaticStyle || oldStyleBinding\n\n  const style = normalizeStyleBinding(vnode.data.style) || {}\n\n  // store normalized style under a different key for next diff\n  // make sure to clone it if it's reactive, since the user likley wants\n  // to mutate it.\n  vnode.data.normalizedStyle = isDef(style.__ob__)\n    ? extend({}, style)\n    : style\n\n  const newStyle = getStyle(vnode, true)\n\n  for (name in oldStyle) {\n    if (isUndef(newStyle[name])) {\n      setProp(el, name, '')\n    }\n  }\n  for (name in newStyle) {\n    cur = newStyle[name]\n    if (cur !== oldStyle[name]) {\n      // ie9 setting to null has no effect, must use empty string\n      setProp(el, name, cur == null ? '' : cur)\n    }\n  }\n}\n\nexport default {\n  create: updateStyle,\n  update: updateStyle\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/transition.js",
    "content": "/* @flow */\n\nimport { inBrowser, isIE9, warn } from 'core/util/index'\nimport { mergeVNodeHook } from 'core/vdom/helpers/index'\nimport { activeInstance } from 'core/instance/lifecycle'\n\nimport {\n  once,\n  isDef,\n  isUndef,\n  isObject,\n  toNumber\n} from 'shared/util'\n\nimport {\n  nextFrame,\n  resolveTransition,\n  whenTransitionEnds,\n  addTransitionClass,\n  removeTransitionClass\n} from '../transition-util'\n\nexport function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {\n  const el: any = vnode.elm\n\n  // call leave callback now\n  if (isDef(el._leaveCb)) {\n    el._leaveCb.cancelled = true\n    el._leaveCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (isUndef(data)) {\n    return\n  }\n\n  /* istanbul ignore if */\n  if (isDef(el._enterCb) || el.nodeType !== 1) {\n    return\n  }\n\n  const {\n    css,\n    type,\n    enterClass,\n    enterToClass,\n    enterActiveClass,\n    appearClass,\n    appearToClass,\n    appearActiveClass,\n    beforeEnter,\n    enter,\n    afterEnter,\n    enterCancelled,\n    beforeAppear,\n    appear,\n    afterAppear,\n    appearCancelled,\n    duration\n  } = data\n\n  // activeInstance will always be the <transition> component managing this\n  // transition. One edge case to check is when the <transition> is placed\n  // as the root node of a child component. In that case we need to check\n  // <transition>'s parent for appear check.\n  let context = activeInstance\n  let transitionNode = activeInstance.$vnode\n  while (transitionNode && transitionNode.parent) {\n    transitionNode = transitionNode.parent\n    context = transitionNode.context\n  }\n\n  const isAppear = !context._isMounted || !vnode.isRootInsert\n\n  if (isAppear && !appear && appear !== '') {\n    return\n  }\n\n  const startClass = isAppear && appearClass\n    ? appearClass\n    : enterClass\n  const activeClass = isAppear && appearActiveClass\n    ? appearActiveClass\n    : enterActiveClass\n  const toClass = isAppear && appearToClass\n    ? appearToClass\n    : enterToClass\n\n  const beforeEnterHook = isAppear\n    ? (beforeAppear || beforeEnter)\n    : beforeEnter\n  const enterHook = isAppear\n    ? (typeof appear === 'function' ? appear : enter)\n    : enter\n  const afterEnterHook = isAppear\n    ? (afterAppear || afterEnter)\n    : afterEnter\n  const enterCancelledHook = isAppear\n    ? (appearCancelled || enterCancelled)\n    : enterCancelled\n\n  const explicitEnterDuration: any = toNumber(\n    isObject(duration)\n      ? duration.enter\n      : duration\n  )\n\n  if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {\n    checkDuration(explicitEnterDuration, 'enter', vnode)\n  }\n\n  const expectsCSS = css !== false && !isIE9\n  const userWantsControl = getHookArgumentsLength(enterHook)\n\n  const cb = el._enterCb = once(() => {\n    if (expectsCSS) {\n      removeTransitionClass(el, toClass)\n      removeTransitionClass(el, activeClass)\n    }\n    if (cb.cancelled) {\n      if (expectsCSS) {\n        removeTransitionClass(el, startClass)\n      }\n      enterCancelledHook && enterCancelledHook(el)\n    } else {\n      afterEnterHook && afterEnterHook(el)\n    }\n    el._enterCb = null\n  })\n\n  if (!vnode.data.show) {\n    // remove pending leave element on enter by injecting an insert hook\n    mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', () => {\n      const parent = el.parentNode\n      const pendingNode = parent && parent._pending && parent._pending[vnode.key]\n      if (pendingNode &&\n          pendingNode.tag === vnode.tag &&\n          pendingNode.elm._leaveCb) {\n        pendingNode.elm._leaveCb()\n      }\n      enterHook && enterHook(el, cb)\n    })\n  }\n\n  // start enter transition\n  beforeEnterHook && beforeEnterHook(el)\n  if (expectsCSS) {\n    addTransitionClass(el, startClass)\n    addTransitionClass(el, activeClass)\n    nextFrame(() => {\n      addTransitionClass(el, toClass)\n      removeTransitionClass(el, startClass)\n      if (!cb.cancelled && !userWantsControl) {\n        if (isValidDuration(explicitEnterDuration)) {\n          setTimeout(cb, explicitEnterDuration)\n        } else {\n          whenTransitionEnds(el, type, cb)\n        }\n      }\n    })\n  }\n\n  if (vnode.data.show) {\n    toggleDisplay && toggleDisplay()\n    enterHook && enterHook(el, cb)\n  }\n\n  if (!expectsCSS && !userWantsControl) {\n    cb()\n  }\n}\n\nexport function leave (vnode: VNodeWithData, rm: Function) {\n  const el: any = vnode.elm\n\n  // call enter callback now\n  if (isDef(el._enterCb)) {\n    el._enterCb.cancelled = true\n    el._enterCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (isUndef(data)) {\n    return rm()\n  }\n\n  /* istanbul ignore if */\n  if (isDef(el._leaveCb) || el.nodeType !== 1) {\n    return\n  }\n\n  const {\n    css,\n    type,\n    leaveClass,\n    leaveToClass,\n    leaveActiveClass,\n    beforeLeave,\n    leave,\n    afterLeave,\n    leaveCancelled,\n    delayLeave,\n    duration\n  } = data\n\n  const expectsCSS = css !== false && !isIE9\n  const userWantsControl = getHookArgumentsLength(leave)\n\n  const explicitLeaveDuration: any = toNumber(\n    isObject(duration)\n      ? duration.leave\n      : duration\n  )\n\n  if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {\n    checkDuration(explicitLeaveDuration, 'leave', vnode)\n  }\n\n  const cb = el._leaveCb = once(() => {\n    if (el.parentNode && el.parentNode._pending) {\n      el.parentNode._pending[vnode.key] = null\n    }\n    if (expectsCSS) {\n      removeTransitionClass(el, leaveToClass)\n      removeTransitionClass(el, leaveActiveClass)\n    }\n    if (cb.cancelled) {\n      if (expectsCSS) {\n        removeTransitionClass(el, leaveClass)\n      }\n      leaveCancelled && leaveCancelled(el)\n    } else {\n      rm()\n      afterLeave && afterLeave(el)\n    }\n    el._leaveCb = null\n  })\n\n  if (delayLeave) {\n    delayLeave(performLeave)\n  } else {\n    performLeave()\n  }\n\n  function performLeave () {\n    // the delayed leave may have already been cancelled\n    if (cb.cancelled) {\n      return\n    }\n    // record leaving element\n    if (!vnode.data.show) {\n      (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode\n    }\n    beforeLeave && beforeLeave(el)\n    if (expectsCSS) {\n      addTransitionClass(el, leaveClass)\n      addTransitionClass(el, leaveActiveClass)\n      nextFrame(() => {\n        addTransitionClass(el, leaveToClass)\n        removeTransitionClass(el, leaveClass)\n        if (!cb.cancelled && !userWantsControl) {\n          if (isValidDuration(explicitLeaveDuration)) {\n            setTimeout(cb, explicitLeaveDuration)\n          } else {\n            whenTransitionEnds(el, type, cb)\n          }\n        }\n      })\n    }\n    leave && leave(el, cb)\n    if (!expectsCSS && !userWantsControl) {\n      cb()\n    }\n  }\n}\n\n// only used in dev mode\nfunction checkDuration (val, name, vnode) {\n  if (typeof val !== 'number') {\n    warn(\n      `<transition> explicit ${name} duration is not a valid number - ` +\n      `got ${JSON.stringify(val)}.`,\n      vnode.context\n    )\n  } else if (isNaN(val)) {\n    warn(\n      `<transition> explicit ${name} duration is NaN - ` +\n      'the duration expression might be incorrect.',\n      vnode.context\n    )\n  }\n}\n\nfunction isValidDuration (val) {\n  return typeof val === 'number' && !isNaN(val)\n}\n\n/**\n * Normalize a transition hook's argument length. The hook may be:\n * - a merged hook (invoker) with the original in .fns\n * - a wrapped component method (check ._length)\n * - a plain function (.length)\n */\nfunction getHookArgumentsLength (fn: Function): boolean {\n  if (isUndef(fn)) {\n    return false\n  }\n  const invokerFns = fn.fns\n  if (isDef(invokerFns)) {\n    // invoker\n    return getHookArgumentsLength(\n      Array.isArray(invokerFns)\n        ? invokerFns[0]\n        : invokerFns\n    )\n  } else {\n    return (fn._length || fn.length) > 1\n  }\n}\n\nfunction _enter (_: any, vnode: VNodeWithData) {\n  if (vnode.data.show !== true) {\n    enter(vnode)\n  }\n}\n\nexport default inBrowser ? {\n  create: _enter,\n  activate: _enter,\n  remove (vnode: VNode, rm: Function) {\n    /* istanbul ignore else */\n    if (vnode.data.show !== true) {\n      leave(vnode, rm)\n    } else {\n      rm()\n    }\n  }\n} : {}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/node-ops.js",
    "content": "/* @flow */\n\n/*这个文件是根据平台（web或者weex）操作真实节点（如web上的Dom）的操作函数进行了一层适配，对外可以统一提供操作真实节点的接口，内部实现根据平台的变化而变化*/\n\nimport { namespaceMap } from 'web/util/index'\n\nexport function createElement (tagName: string, vnode: VNode): Element {\n  const elm = document.createElement(tagName)\n  if (tagName !== 'select') {\n    return elm\n  }\n  // false or null will remove the attribute but undefined will not\n  if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {\n    elm.setAttribute('multiple', 'multiple')\n  }\n  return elm\n}\n\nexport function createElementNS (namespace: string, tagName: string): Element {\n  return document.createElementNS(namespaceMap[namespace], tagName)\n}\n\nexport function createTextNode (text: string): Text {\n  return document.createTextNode(text)\n}\n\nexport function createComment (text: string): Comment {\n  return document.createComment(text)\n}\n\nexport function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {\n  parentNode.insertBefore(newNode, referenceNode)\n}\n\nexport function removeChild (node: Node, child: Node) {\n  node.removeChild(child)\n}\n\nexport function appendChild (node: Node, child: Node) {\n  node.appendChild(child)\n}\n\nexport function parentNode (node: Node): ?Node {\n  return node.parentNode\n}\n\nexport function nextSibling (node: Node): ?Node {\n  return node.nextSibling\n}\n\nexport function tagName (node: Element): string {\n  return node.tagName\n}\n\nexport function setTextContent (node: Node, text: string) {\n  node.textContent = text\n}\n\nexport function setAttribute (node: Element, key: string, val: string) {\n  node.setAttribute(key, val)\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/patch.js",
    "content": "/* @flow */\n\nimport * as nodeOps from 'web/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimport baseModules from 'core/vdom/modules/index'\nimport platformModules from 'web/runtime/modules/index'\n\n// the directive module should be applied last, after all\n// built-in modules have been applied.\nconst modules = platformModules.concat(baseModules)\n\nexport const patch: Function = createPatchFunction({ nodeOps, modules })\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/transition-util.js",
    "content": "/* @flow */\n\nimport { inBrowser, isIE9 } from 'core/util/index'\nimport { addClass, removeClass } from './class-util'\nimport { remove, extend, cached } from 'shared/util'\n\nexport function resolveTransition (def?: string | Object): ?Object {\n  if (!def) {\n    return\n  }\n  /* istanbul ignore else */\n  if (typeof def === 'object') {\n    const res = {}\n    if (def.css !== false) {\n      extend(res, autoCssTransition(def.name || 'v'))\n    }\n    extend(res, def)\n    return res\n  } else if (typeof def === 'string') {\n    return autoCssTransition(def)\n  }\n}\n\nconst autoCssTransition: (name: string) => Object = cached(name => {\n  return {\n    enterClass: `${name}-enter`,\n    enterToClass: `${name}-enter-to`,\n    enterActiveClass: `${name}-enter-active`,\n    leaveClass: `${name}-leave`,\n    leaveToClass: `${name}-leave-to`,\n    leaveActiveClass: `${name}-leave-active`\n  }\n})\n\nexport const hasTransition = inBrowser && !isIE9\nconst TRANSITION = 'transition'\nconst ANIMATION = 'animation'\n\n// Transition property/event sniffing\nexport let transitionProp = 'transition'\nexport let transitionEndEvent = 'transitionend'\nexport let animationProp = 'animation'\nexport let animationEndEvent = 'animationend'\nif (hasTransition) {\n  /* istanbul ignore if */\n  if (window.ontransitionend === undefined &&\n    window.onwebkittransitionend !== undefined) {\n    transitionProp = 'WebkitTransition'\n    transitionEndEvent = 'webkitTransitionEnd'\n  }\n  if (window.onanimationend === undefined &&\n    window.onwebkitanimationend !== undefined) {\n    animationProp = 'WebkitAnimation'\n    animationEndEvent = 'webkitAnimationEnd'\n  }\n}\n\n// binding to window is necessary to make hot reload work in IE in strict mode\nconst raf = inBrowser && window.requestAnimationFrame\n  ? window.requestAnimationFrame.bind(window)\n  : setTimeout\n\nexport function nextFrame (fn: Function) {\n  raf(() => {\n    raf(fn)\n  })\n}\n\nexport function addTransitionClass (el: any, cls: string) {\n  (el._transitionClasses || (el._transitionClasses = [])).push(cls)\n  addClass(el, cls)\n}\n\nexport function removeTransitionClass (el: any, cls: string) {\n  if (el._transitionClasses) {\n    remove(el._transitionClasses, cls)\n  }\n  removeClass(el, cls)\n}\n\nexport function whenTransitionEnds (\n  el: Element,\n  expectedType: ?string,\n  cb: Function\n) {\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType)\n  if (!type) return cb()\n  const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent\n  let ended = 0\n  const end = () => {\n    el.removeEventListener(event, onEnd)\n    cb()\n  }\n  const onEnd = e => {\n    if (e.target === el) {\n      if (++ended >= propCount) {\n        end()\n      }\n    }\n  }\n  setTimeout(() => {\n    if (ended < propCount) {\n      end()\n    }\n  }, timeout + 1)\n  el.addEventListener(event, onEnd)\n}\n\nconst transformRE = /\\b(transform|all)(,|$)/\n\nexport function getTransitionInfo (el: Element, expectedType?: ?string): {\n  type: ?string;\n  propCount: number;\n  timeout: number;\n  hasTransform: boolean;\n} {\n  const styles: any = window.getComputedStyle(el)\n  const transitionDelays: Array<string> = styles[transitionProp + 'Delay'].split(', ')\n  const transitionDurations: Array<string> = styles[transitionProp + 'Duration'].split(', ')\n  const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)\n  const animationDelays: Array<string> = styles[animationProp + 'Delay'].split(', ')\n  const animationDurations: Array<string> = styles[animationProp + 'Duration'].split(', ')\n  const animationTimeout: number = getTimeout(animationDelays, animationDurations)\n\n  let type: ?string\n  let timeout = 0\n  let propCount = 0\n  /* istanbul ignore if */\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION\n      timeout = transitionTimeout\n      propCount = transitionDurations.length\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION\n      timeout = animationTimeout\n      propCount = animationDurations.length\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout)\n    type = timeout > 0\n      ? transitionTimeout > animationTimeout\n        ? TRANSITION\n        : ANIMATION\n      : null\n    propCount = type\n      ? type === TRANSITION\n        ? transitionDurations.length\n        : animationDurations.length\n      : 0\n  }\n  const hasTransform: boolean =\n    type === TRANSITION &&\n    transformRE.test(styles[transitionProp + 'Property'])\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform\n  }\n}\n\nfunction getTimeout (delays: Array<string>, durations: Array<string>): number {\n  /* istanbul ignore next */\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays)\n  }\n\n  return Math.max.apply(null, durations.map((d, i) => {\n    return toMs(d) + toMs(delays[i])\n  }))\n}\n\nfunction toMs (s: string): number {\n  return Number(s.slice(0, -1)) * 1000\n}\n"
  },
  {
    "path": "vue-src/platforms/web/runtime-with-compiler.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\nimport { warn, cached } from 'core/util/index'\nimport { mark, measure } from 'core/util/perf'\n\nimport Vue from './runtime/index'\nimport { query } from './util/index'\nimport { shouldDecodeNewlines } from './util/compat'\nimport { compileToFunctions } from './compiler/index'\n\n/*根据id获取templete，即获取id对应的DOM，然后访问其innerHTML*/\nconst idToTemplate = cached(id => {\n  const el = query(id)\n  return el && el.innerHTML\n})\n\n/*把原本不带编译的$mount方法保存下来，在最后会调用。*/\nconst mount = Vue.prototype.$mount\n/*挂载组件，带模板编译*/\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  /*处理模板templete，编译成render函数，render不存在的时候才会编译template，否则优先使用render*/\n  if (!options.render) {\n    let template = options.template\n    /*template存在的时候取template，不存在的时候取el的outerHTML*/\n    if (template) {\n      /*当template是字符串的时候*/\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        /*当template为DOM节点的时候*/\n        template = template.innerHTML\n      } else {\n        /*报错*/\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      /*获取element的outerHTML*/\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n\n      /*将template编译成render函数，这里会有render以及staticRenderFns两个返回，这是vue的编译时优化，static静态不需要在VNode更新时进行patch，优化性能*/\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        shouldDecodeNewlines,\n        delimiters: options.delimiters\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  /*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/\n  return mount.call(this, el, hydrating)\n}\n\n/**\n * Get outerHTML of elements, taking care\n * of SVG elements in IE as well.\n */\n /*获取element的outerHTML*/\nfunction getOuterHTML (el: Element): string {\n  if (el.outerHTML) {\n    return el.outerHTML\n  } else {\n    const container = document.createElement('div')\n    container.appendChild(el.cloneNode(true))\n    return container.innerHTML\n  }\n}\n\nVue.compile = compileToFunctions\n/*Github:https://github.com/answershuto*/\nexport default Vue\n"
  },
  {
    "path": "vue-src/platforms/web/runtime.js",
    "content": "/* @flow */\n\nimport Vue from './runtime/index'\n/*Github:https://github.com/answershuto*/\nexport default Vue\n"
  },
  {
    "path": "vue-src/platforms/web/server/directives/index.js",
    "content": "import show from './show'\n\nexport default {\n  show\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/directives/show.js",
    "content": "/* @flow */\n/*Github:https://github.com/answershuto*/\nexport default function show (node: VNodeWithData, dir: VNodeDirective) {\n  if (!dir.value) {\n    const style: any = node.data.style || (node.data.style = {})\n    style.display = 'none'\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/modules/attrs.js",
    "content": "/* @flow */\n\nimport { escape } from 'he'\n\nimport {\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  isBooleanAttr,\n  isEnumeratedAttr,\n  isFalsyAttrValue\n} from 'web/util/attrs'\n\nexport default function renderAttrs (node: VNodeWithData): string {\n  let attrs = node.data.attrs\n  let res = ''\n\n  let parent = node.parent\n  while (isDef(parent)) {\n    if (isDef(parent.data) && isDef(parent.data.attrs)) {\n      attrs = Object.assign({}, attrs, parent.data.attrs)\n    }\n    parent = parent.parent\n  }\n\n  if (isUndef(attrs)) {\n    return res\n  }\n\n  for (const key in attrs) {\n    if (key === 'style') {\n      // leave it to the style module\n      continue\n    }\n    res += renderAttr(key, attrs[key])\n  }\n  return res\n}\n\nexport function renderAttr (key: string, value: string): string {\n  if (isBooleanAttr(key)) {\n    if (!isFalsyAttrValue(value)) {\n      return ` ${key}=\"${key}\"`\n    }\n  } else if (isEnumeratedAttr(key)) {\n    return ` ${key}=\"${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}\"`\n  } else if (!isFalsyAttrValue(value)) {\n    return ` ${key}=\"${typeof value === 'string' ? escape(value) : value}\"`\n  }\n  return ''\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/modules/class.js",
    "content": "/* @flow */\n\nimport { escape } from 'he'\nimport { genClassForVnode } from 'web/util/index'\n\nexport default function renderClass (node: VNodeWithData): ?string {\n  const classList = genClassForVnode(node)\n  if (classList !== '') {\n    return ` class=\"${escape(classList)}\"`\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/modules/dom-props.js",
    "content": "/* @flow */\n\nimport VNode from 'core/vdom/vnode'\nimport { renderAttr } from './attrs'\nimport { isDef, isUndef } from 'shared/util'\nimport { propsToAttrMap, isRenderableAttr } from '../util'\n\nexport default function renderDOMProps (node: VNodeWithData): string {\n  let props = node.data.domProps\n  let res = ''\n\n  let parent = node.parent\n  while (isDef(parent)) {\n    if (parent.data && parent.data.domProps) {\n      props = Object.assign({}, props, parent.data.domProps)\n    }\n    parent = parent.parent\n  }\n\n  if (isUndef(props)) {\n    return res\n  }\n\n  const attrs = node.data.attrs\n  for (const key in props) {\n    if (key === 'innerHTML') {\n      setText(node, props[key], true)\n    } else if (key === 'textContent') {\n      setText(node, props[key], false)\n    } else {\n      const attr = propsToAttrMap[key] || key.toLowerCase()\n      if (isRenderableAttr(attr) &&\n          // avoid rendering double-bound props/attrs twice\n          !(isDef(attrs) && isDef(attrs[attr]))) {\n        res += renderAttr(attr, props[key])\n      }\n    }\n  }\n  return res\n}\n\nfunction setText (node, text, raw) {\n  const child = new VNode(undefined, undefined, undefined, text)\n  child.raw = raw\n  node.children = [child]\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/modules/index.js",
    "content": "import attrs from './attrs'\nimport domProps from './dom-props'\nimport klass from './class'\nimport style from './style'\n\nexport default [\n  attrs,\n  domProps,\n  klass,\n  style\n]\n"
  },
  {
    "path": "vue-src/platforms/web/server/modules/style.js",
    "content": "/* @flow */\n\nimport { escape } from 'he'\nimport { hyphenate } from 'shared/util'\nimport { getStyle } from 'web/util/style'\n\nfunction genStyleText (vnode: VNode): string {\n  let styleText = ''\n  const style = getStyle(vnode, false)\n  for (const key in style) {\n    const value = style[key]\n    const hyphenatedKey = hyphenate(key)\n    if (Array.isArray(value)) {\n      for (let i = 0, len = value.length; i < len; i++) {\n        styleText += `${hyphenatedKey}:${value[i]};`\n      }\n    } else {\n      styleText += `${hyphenatedKey}:${value};`\n    }\n  }\n  return styleText\n}\n\nexport default function renderStyle (vnode: VNodeWithData): ?string {\n  const styleText = genStyleText(vnode)\n  if (styleText !== '') {\n    return ` style=${JSON.stringify(escape(styleText))}`\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/util.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\nconst isAttr = makeMap(\n  'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' +\n  'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' +\n  'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' +\n  'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' +\n  'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' +\n  'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' +\n  'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' +\n  'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' +\n  'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' +\n  'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' +\n  'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' +\n  'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' +\n  'target,title,type,usemap,value,width,wrap'\n)\n\n/* istanbul ignore next */\nconst isRenderableAttr = (name: string): boolean => {\n  return (\n    isAttr(name) ||\n    name.indexOf('data-') === 0 ||\n    name.indexOf('aria-') === 0\n  )\n}\nexport { isRenderableAttr }\n\nexport const propsToAttrMap = {\n  acceptCharset: 'accept-charset',\n  className: 'class',\n  htmlFor: 'for',\n  httpEquiv: 'http-equiv'\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server-renderer.js",
    "content": "/* @flow */\n\nprocess.env.VUE_ENV = 'server'\n\nimport modules from './server/modules/index'\nimport baseDirectives from './server/directives/index'\nimport { isUnaryTag, canBeLeftOpenTag } from './compiler/util'\n\nimport { createRenderer as _createRenderer } from 'server/create-renderer'\nimport { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer'\n\nexport function createRenderer (options?: Object = {}): {\n  renderToString: Function,\n  renderToStream: Function\n} {\n  return _createRenderer(Object.assign({}, options, {\n    isUnaryTag,\n    canBeLeftOpenTag,\n    modules,\n    // user can provide server-side implementations for custom directives\n    // when creating the renderer.\n    directives: Object.assign(baseDirectives, options.directives)\n  }))\n}\n\nexport const createBundleRenderer = createBundleRendererCreator(createRenderer)\n"
  },
  {
    "path": "vue-src/platforms/web/util/attrs.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\n// these are reserved for web because they are directly compiled away\n// during template compilation\nexport const isReservedAttr = makeMap('style,class')\n\n// attributes that should be using props for binding\nconst acceptValue = makeMap('input,textarea,option,select')\nexport const mustUseProp = (tag: string, type: ?string, attr: string): boolean => {\n  return (\n    (attr === 'value' && acceptValue(tag)) && type !== 'button' ||\n    (attr === 'selected' && tag === 'option') ||\n    (attr === 'checked' && tag === 'input') ||\n    (attr === 'muted' && tag === 'video')\n  )\n}\n\nexport const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck')\n\nexport const isBooleanAttr = makeMap(\n  'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +\n  'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +\n  'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +\n  'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +\n  'required,reversed,scoped,seamless,selected,sortable,translate,' +\n  'truespeed,typemustmatch,visible'\n)\n\nexport const xlinkNS = 'http://www.w3.org/1999/xlink'\n\nexport const isXlink = (name: string): boolean => {\n  return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'\n}\n\nexport const getXlinkProp = (name: string): string => {\n  return isXlink(name) ? name.slice(6, name.length) : ''\n}\n\nexport const isFalsyAttrValue = (val: any): boolean => {\n  return val == null || val === false\n}\n"
  },
  {
    "path": "vue-src/platforms/web/util/class.js",
    "content": "/* @flow */\n\nimport { isDef, isUndef, isObject } from 'shared/util'\n\nexport function genClassForVnode (vnode: VNode): string {\n  let data = vnode.data\n  let parentNode = vnode\n  let childNode = vnode\n  while (isDef(childNode.componentInstance)) {\n    childNode = childNode.componentInstance._vnode\n    if (childNode.data) {\n      data = mergeClassData(childNode.data, data)\n    }\n  }\n  while (isDef(parentNode = parentNode.parent)) {\n    if (parentNode.data) {\n      data = mergeClassData(data, parentNode.data)\n    }\n  }\n  return genClassFromData(data)\n}\n\nfunction mergeClassData (child: VNodeData, parent: VNodeData): {\n  staticClass: string,\n  class: any\n} {\n  return {\n    staticClass: concat(child.staticClass, parent.staticClass),\n    class: isDef(child.class)\n      ? [child.class, parent.class]\n      : parent.class\n  }\n}\n\nfunction genClassFromData (data: Object): string {\n  const dynamicClass = data.class\n  const staticClass = data.staticClass\n  if (isDef(staticClass) || isDef(dynamicClass)) {\n    return concat(staticClass, stringifyClass(dynamicClass))\n  }\n  /* istanbul ignore next */\n  return ''\n}\n\nexport function concat (a: ?string, b: ?string): string {\n  return a ? b ? (a + ' ' + b) : a : (b || '')\n}\n\nexport function stringifyClass (value: any): string {\n  if (isUndef(value)) {\n    return ''\n  }\n  if (typeof value === 'string') {\n    return value\n  }\n  let res = ''\n  if (Array.isArray(value)) {\n    let stringified\n    for (let i = 0, l = value.length; i < l; i++) {\n      if (isDef(value[i])) {\n        if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {\n          res += stringified + ' '\n        }\n      }\n    }\n    return res.slice(0, -1)\n  }\n  if (isObject(value)) {\n    for (const key in value) {\n      if (value[key]) res += key + ' '\n    }\n    return res.slice(0, -1)\n  }\n  /* istanbul ignore next */\n  return res\n}\n"
  },
  {
    "path": "vue-src/platforms/web/util/compat.js",
    "content": "/* @flow */\n\nimport { inBrowser } from 'core/util/index'\n\n// check whether current browser encodes a char inside attribute values\nfunction shouldDecode (content: string, encoded: string): boolean {\n  const div = document.createElement('div')\n  div.innerHTML = `<div a=\"${content}\">`\n  return div.innerHTML.indexOf(encoded) > 0\n}\n\n// #3663\n// IE encodes newlines inside attribute values while other browsers don't\nexport const shouldDecodeNewlines = inBrowser ? shouldDecode('\\n', '&#10;') : false\n"
  },
  {
    "path": "vue-src/platforms/web/util/element.js",
    "content": "/* @flow */\n\nimport { inBrowser } from 'core/util/env'\nimport { makeMap } from 'shared/util'\n\nexport const namespaceMap = {\n  svg: 'http://www.w3.org/2000/svg',\n  math: 'http://www.w3.org/1998/Math/MathML'\n}\n\nexport const isHTMLTag = makeMap(\n  'html,body,base,head,link,meta,style,title,' +\n  'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +\n  'div,dd,dl,dt,figcaption,figure,hr,img,li,main,ol,p,pre,ul,' +\n  'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +\n  's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +\n  'embed,object,param,source,canvas,script,noscript,del,ins,' +\n  'caption,col,colgroup,table,thead,tbody,td,th,tr,' +\n  'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +\n  'output,progress,select,textarea,' +\n  'details,dialog,menu,menuitem,summary,' +\n  'content,element,shadow,template'\n)\n\n// this map is intentionally selective, only covering SVG elements that may\n// contain child elements.\nexport const isSVG = makeMap(\n  'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +\n  'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +\n  'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',\n  true\n)\n\nexport const isPreTag = (tag: ?string): boolean => tag === 'pre'\n\nexport const isReservedTag = (tag: string): ?boolean => {\n  return isHTMLTag(tag) || isSVG(tag)\n}\n\nexport function getTagNamespace (tag: string): ?string {\n  if (isSVG(tag)) {\n    return 'svg'\n  }\n  // basic support for MathML\n  // note it doesn't support other MathML elements being component roots\n  if (tag === 'math') {\n    return 'math'\n  }\n}\n\nconst unknownElementCache = Object.create(null)\nexport function isUnknownElement (tag: string): boolean {\n  /* istanbul ignore if */\n  if (!inBrowser) {\n    return true\n  }\n  if (isReservedTag(tag)) {\n    return false\n  }\n  tag = tag.toLowerCase()\n  /* istanbul ignore if */\n  if (unknownElementCache[tag] != null) {\n    return unknownElementCache[tag]\n  }\n  const el = document.createElement(tag)\n  if (tag.indexOf('-') > -1) {\n    // http://stackoverflow.com/a/28210364/1070244\n    return (unknownElementCache[tag] = (\n      el.constructor === window.HTMLUnknownElement ||\n      el.constructor === window.HTMLElement\n    ))\n  } else {\n    return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/util/index.js",
    "content": "/* @flow */\n\nimport { warn } from 'core/util/index'\n\nexport * from './attrs'\nexport * from './class'\nexport * from './element'\n\n/**\n * Query an element selector if it's not an element already.\n */\n /*返回一个元素的DOM实例对象*/\nexport function query (el: string | Element): Element {\n  if (typeof el === 'string') {\n    const selected = document.querySelector(el)\n    if (!selected) {\n      process.env.NODE_ENV !== 'production' && warn(\n        'Cannot find element: ' + el\n      )\n      return document.createElement('div')\n    }\n    return selected\n  } else {\n    return el\n  }\n}\n"
  },
  {
    "path": "vue-src/platforms/web/util/style.js",
    "content": "/* @flow */\n\nimport { cached, extend, toObject } from 'shared/util'\n\nexport const parseStyleText = cached(function (cssText) {\n  const res = {}\n  const listDelimiter = /;(?![^(]*\\))/g\n  const propertyDelimiter = /:(.+)/\n  cssText.split(listDelimiter).forEach(function (item) {\n    if (item) {\n      var tmp = item.split(propertyDelimiter)\n      tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())\n    }\n  })\n  return res\n})\n\n// merge static and dynamic style data on the same vnode\nfunction normalizeStyleData (data: VNodeData): ?Object {\n  const style = normalizeStyleBinding(data.style)\n  // static style is pre-processed into an object during compilation\n  // and is always a fresh object, so it's safe to merge into it\n  return data.staticStyle\n    ? extend(data.staticStyle, style)\n    : style\n}\n\n// normalize possible array / string values into Object\nexport function normalizeStyleBinding (bindingStyle: any): ?Object {\n  if (Array.isArray(bindingStyle)) {\n    return toObject(bindingStyle)\n  }\n  if (typeof bindingStyle === 'string') {\n    return parseStyleText(bindingStyle)\n  }\n  return bindingStyle\n}\n\n/**\n * parent component style should be after child's\n * so that parent component's style could override it\n */\nexport function getStyle (vnode: VNode, checkChild: boolean): Object {\n  const res = {}\n  let styleData\n\n  if (checkChild) {\n    let childNode = vnode\n    while (childNode.componentInstance) {\n      childNode = childNode.componentInstance._vnode\n      if (childNode.data && (styleData = normalizeStyleData(childNode.data))) {\n        extend(res, styleData)\n      }\n    }\n  }\n\n  if ((styleData = normalizeStyleData(vnode.data))) {\n    extend(res, styleData)\n  }\n\n  let parentNode = vnode\n  while ((parentNode = parentNode.parent)) {\n    if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {\n      extend(res, styleData)\n    }\n  }\n  return res\n}\n\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/directives/index.js",
    "content": "import model from './model'\n\nexport default {\n  model\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/directives/model.js",
    "content": "/* @flow */\n\nimport { addHandler, addAttr } from 'compiler/helpers'\nimport { genComponentModel, genAssignmentCode } from 'compiler/directives/model'\n\nexport default function model (\n  el: ASTElement,\n  dir: ASTDirective,\n  _warn: Function\n): ?boolean {\n  if (el.tag === 'input' || el.tag === 'textarea') {\n    genDefaultModel(el, dir.value, dir.modifiers)\n  } else {\n    genComponentModel(el, dir.value, dir.modifiers)\n  }\n}\n\nfunction genDefaultModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const { lazy, trim, number } = modifiers || {}\n  const event = lazy ? 'change' : 'input'\n\n  let valueExpression = `$event.target.attr.value${trim ? '.trim()' : ''}`\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n\n  const code = genAssignmentCode(value, valueExpression)\n  addAttr(el, 'value', `(${value})`)\n  addHandler(el, event, code, null, true)\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/index.js",
    "content": "/* @flow */\n\nimport { genStaticKeys } from 'shared/util'\nimport { createCompiler } from 'compiler/index'\n\nimport modules from './modules/index'\nimport directives from './directives/index'\n\nimport {\n  isUnaryTag,\n  mustUseProp,\n  isReservedTag,\n  canBeLeftOpenTag,\n  getTagNamespace\n} from '../util/index'\n\nexport const baseOptions: CompilerOptions = {\n  modules,\n  directives,\n  isUnaryTag,\n  mustUseProp,\n  canBeLeftOpenTag,\n  isReservedTag,\n  getTagNamespace,\n  preserveWhitespace: false,\n  staticKeys: genStaticKeys(modules)\n}\n\nconst { compile, compileToFunctions } = createCompiler(baseOptions)\nexport { compile, compileToFunctions }\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/append.js",
    "content": "/* @flow */\n\nfunction preTransformNode (el: ASTElement, options: CompilerOptions) {\n  if (el.tag === 'cell' && !el.attrsList.some(item => item.name === 'append')) {\n    el.attrsMap.append = 'tree'\n    el.attrsList.push({ name: 'append', value: 'tree' })\n  }\n  if (el.attrsMap.append === 'tree') {\n    el.appendAsTree = true\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  return el.appendAsTree ? `appendAsTree:true,` : ''\n}\n\nexport default {\n  staticKeys: ['appendAsTree'],\n  preTransformNode,\n  genData\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/class.js",
    "content": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\ntype StaticClassResult = {\n  dynamic: boolean,\n  classResult: string\n};\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticClass = getAndRemoveAttr(el, 'class')\n  const { dynamic, classResult } = parseStaticClass(staticClass, options)\n  if (process.env.NODE_ENV !== 'production' && dynamic && staticClass) {\n    warn(\n      `class=\"${staticClass}\": ` +\n      'Interpolation inside attributes has been deprecated. ' +\n      'Use v-bind or the colon shorthand instead.'\n    )\n  }\n  if (!dynamic && classResult) {\n    el.staticClass = classResult\n  }\n  const classBinding = getBindingAttr(el, 'class', false /* getStatic */)\n  if (classBinding) {\n    el.classBinding = classBinding\n  } else if (dynamic) {\n    el.classBinding = classResult\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticClass) {\n    data += `staticClass:${el.staticClass},`\n  }\n  if (el.classBinding) {\n    data += `class:${el.classBinding},`\n  }\n  return data\n}\n\nfunction parseStaticClass (staticClass: ?string, options: CompilerOptions): StaticClassResult {\n  // \"a b c\" -> [\"a\", \"b\", \"c\"] => staticClass: [\"a\", \"b\", \"c\"]\n  // \"a {{x}} c\" -> [\"a\", x, \"c\"] => classBinding: '[\"a\", x, \"c\"]'\n  let dynamic = false\n  let classResult = ''\n  if (staticClass) {\n    const classList = staticClass.trim().split(' ').map(name => {\n      const result = parseText(name, options.delimiters)\n      if (result) {\n        dynamic = true\n        return result\n      }\n      return JSON.stringify(name)\n    })\n    if (classList.length) {\n      classResult = '[' + classList.join(',') + ']'\n    }\n  }\n  return { dynamic, classResult }\n}\n\nexport default {\n  staticKeys: ['staticClass'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/index.js",
    "content": "import klass from './class'\nimport style from './style'\nimport props from './props'\nimport append from './append'\n\nexport default [\n  klass,\n  style,\n  props,\n  append\n]\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/props.js",
    "content": "/* @flow */\n\nimport { cached, camelize } from 'shared/util'\n\nconst normalize = cached(camelize)\n\nfunction normalizeKeyName (str: string) : string {\n  if (str.match(/^v\\-/)) {\n    return str.replace(/(v-[a-z\\-]+\\:)([a-z\\-]+)$/i, ($, directive, prop) => {\n      return directive + normalize(prop)\n    })\n  }\n  return normalize(str)\n}\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  if (Array.isArray(el.attrsList)) {\n    el.attrsList.forEach(attr => {\n      if (attr.name && attr.name.match(/\\-/)) {\n        const realName = normalizeKeyName(attr.name)\n        if (el.attrsMap) {\n          el.attrsMap[realName] = el.attrsMap[attr.name]\n          delete el.attrsMap[attr.name]\n        }\n        attr.name = realName\n      }\n    })\n  }\n}\nexport default {\n  transformNode\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/style.js",
    "content": "/* @flow */\n\nimport { cached, camelize } from 'shared/util'\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\ntype StaticStyleResult = {\n  dynamic: boolean,\n  styleResult: string\n};\n\nconst normalize = cached(camelize)\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticStyle = getAndRemoveAttr(el, 'style')\n  const { dynamic, styleResult } = parseStaticStyle(staticStyle, options)\n  if (process.env.NODE_ENV !== 'production' && dynamic) {\n    warn(\n      `style=\"${String(staticStyle)}\": ` +\n      'Interpolation inside attributes has been deprecated. ' +\n      'Use v-bind or the colon shorthand instead.'\n    )\n  }\n  if (!dynamic && styleResult) {\n    el.staticStyle = styleResult\n  }\n  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)\n  if (styleBinding) {\n    el.styleBinding = styleBinding\n  } else if (dynamic) {\n    el.styleBinding = styleResult\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticStyle) {\n    data += `staticStyle:${el.staticStyle},`\n  }\n  if (el.styleBinding) {\n    data += `style:${el.styleBinding},`\n  }\n  return data\n}\n\nfunction parseStaticStyle (staticStyle: ?string, options: CompilerOptions): StaticStyleResult {\n  // \"width: 200px; height: 200px;\" -> {width: 200, height: 200}\n  // \"width: 200px; height: {{y}}\" -> {width: 200, height: y}\n  let dynamic = false\n  let styleResult = ''\n  if (staticStyle) {\n    const styleList = staticStyle.trim().split(';').map(style => {\n      const result = style.trim().split(':')\n      if (result.length !== 2) {\n        return\n      }\n      const key = normalize(result[0].trim())\n      const value = result[1].trim()\n      const dynamicValue = parseText(value, options.delimiters)\n      if (dynamicValue) {\n        dynamic = true\n        return key + ':' + dynamicValue\n      }\n      return key + ':' + JSON.stringify(value)\n    }).filter(result => result)\n    if (styleList.length) {\n      styleResult = '{' + styleList.join(',') + '}'\n    }\n  }\n  return { dynamic, styleResult }\n}\n\nexport default {\n  staticKeys: ['staticStyle'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler.js",
    "content": "export { compile } from 'weex/compiler/index'\n"
  },
  {
    "path": "vue-src/platforms/weex/framework.js",
    "content": "import TextNode from 'weex/runtime/text-node'\n\n// this will be preserved during build\nconst VueFactory = require('./factory')\n\nconst instances = {}\nconst modules = {}\nconst components = {}\n\nconst renderer = {\n  TextNode,\n  instances,\n  modules,\n  components\n}\n\n/**\n * Prepare framework config, basically about the virtual-DOM and JS bridge.\n * @param {object} cfg\n */\nexport function init (cfg) {\n  renderer.Document = cfg.Document\n  renderer.Element = cfg.Element\n  renderer.Comment = cfg.Comment\n  renderer.sendTasks = cfg.sendTasks\n}\n\n/**\n * Reset framework config and clear all registrations.\n */\nexport function reset () {\n  clear(instances)\n  clear(modules)\n  clear(components)\n  delete renderer.Document\n  delete renderer.Element\n  delete renderer.Comment\n  delete renderer.sendTasks\n}\n\n/**\n * Delete all keys of an object.\n * @param {object} obj\n */\nfunction clear (obj) {\n  for (const key in obj) {\n    delete obj[key]\n  }\n}\n\n/**\n * Create an instance with id, code, config and external data.\n * @param {string} instanceId\n * @param {string} appCode\n * @param {object} config\n * @param {object} data\n * @param {object} env { info, config, services }\n */\nexport function createInstance (\n  instanceId,\n  appCode = '',\n  config = {},\n  data,\n  env = {}\n) {\n  // Virtual-DOM object.\n  const document = new renderer.Document(instanceId, config.bundleUrl)\n\n  // All function/callback of parameters before sent to native\n  // will be converted as an id. So `callbacks` is used to store\n  // these real functions. When a callback invoked and won't be\n  // called again, it should be removed from here automatically.\n  const callbacks = []\n\n  // The latest callback id, incremental.\n  const callbackId = 1\n\n  const instance = instances[instanceId] = {\n    instanceId, config, data,\n    document, callbacks, callbackId\n  }\n\n  // Prepare native module getter and HTML5 Timer APIs.\n  const moduleGetter = genModuleGetter(instanceId)\n  const timerAPIs = getInstanceTimer(instanceId, moduleGetter)\n\n  // Prepare `weex` instance variable.\n  const weexInstanceVar = {\n    config,\n    document,\n    requireModule: moduleGetter\n  }\n  Object.freeze(weexInstanceVar)\n\n  // Each instance has a independent `Vue` module instance\n  const Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter)\n\n  // The function which create a closure the JS Bundle will run in.\n  // It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.\n  const instanceVars = Object.assign({\n    Vue,\n    weex: weexInstanceVar,\n    // deprecated\n    __weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line\n  }, timerAPIs)\n  callFunction(instanceVars, appCode)\n\n  // Send `createFinish` signal to native.\n  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1)\n}\n\n/**\n * Destroy an instance with id. It will make sure all memory of\n * this instance released and no more leaks.\n * @param {string} instanceId\n */\nexport function destroyInstance (instanceId) {\n  const instance = instances[instanceId]\n  if (instance && instance.app instanceof instance.Vue) {\n    instance.app.$destroy()\n  }\n  delete instances[instanceId]\n}\n\n/**\n * Refresh an instance with id and new top-level component data.\n * It will use `Vue.set` on all keys of the new data. So it's better\n * define all possible meaningful keys when instance created.\n * @param {string} instanceId\n * @param {object} data\n */\nexport function refreshInstance (instanceId, data) {\n  const instance = instances[instanceId]\n  if (!instance || !(instance.app instanceof instance.Vue)) {\n    return new Error(`refreshInstance: instance ${instanceId} not found!`)\n  }\n  for (const key in data) {\n    instance.Vue.set(instance.app, key, data[key])\n  }\n  // Finally `refreshFinish` signal needed.\n  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1)\n}\n\n/**\n * Get the JSON object of the root element.\n * @param {string} instanceId\n */\nexport function getRoot (instanceId) {\n  const instance = instances[instanceId]\n  if (!instance || !(instance.app instanceof instance.Vue)) {\n    return new Error(`getRoot: instance ${instanceId} not found!`)\n  }\n  return instance.app.$el.toJSON()\n}\n\n/**\n * Receive tasks from native. Generally there are two types of tasks:\n * 1. `fireEvent`: an device actions or user actions from native.\n * 2. `callback`: invoke function which sent to native as a parameter before.\n * @param {string} instanceId\n * @param {array}  tasks\n */\nexport function receiveTasks (instanceId, tasks) {\n  const instance = instances[instanceId]\n  if (!instance || !(instance.app instanceof instance.Vue)) {\n    return new Error(`receiveTasks: instance ${instanceId} not found!`)\n  }\n  const { callbacks, document } = instance\n  tasks.forEach(task => {\n    // `fireEvent` case: find the event target and fire.\n    if (task.method === 'fireEvent') {\n      const [nodeId, type, e, domChanges] = task.args\n      const el = document.getRef(nodeId)\n      document.fireEvent(el, type, e, domChanges)\n    }\n    // `callback` case: find the callback by id and call it.\n    if (task.method === 'callback') {\n      const [callbackId, data, ifKeepAlive] = task.args\n      const callback = callbacks[callbackId]\n      if (typeof callback === 'function') {\n        callback(data)\n        // Remove the callback from `callbacks` if it won't called again.\n        if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) {\n          callbacks[callbackId] = undefined\n        }\n      }\n    }\n  })\n  // Finally `updateFinish` signal needed.\n  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1)\n}\n\n/**\n * Register native modules information.\n * @param {object} newModules\n */\nexport function registerModules (newModules) {\n  for (const name in newModules) {\n    if (!modules[name]) {\n      modules[name] = {}\n    }\n    newModules[name].forEach(method => {\n      if (typeof method === 'string') {\n        modules[name][method] = true\n      } else {\n        modules[name][method.name] = method.args\n      }\n    })\n  }\n}\n\n/**\n * Register native components information.\n * @param {array} newComponents\n */\nexport function registerComponents (newComponents) {\n  if (Array.isArray(newComponents)) {\n    newComponents.forEach(component => {\n      if (!component) {\n        return\n      }\n      if (typeof component === 'string') {\n        components[component] = true\n      } else if (typeof component === 'object' && typeof component.type === 'string') {\n        components[component.type] = component\n      }\n    })\n  }\n}\n\n/**\n * Create a fresh instance of Vue for each Weex instance.\n */\nfunction createVueModuleInstance (instanceId, moduleGetter) {\n  const exports = {}\n  VueFactory(exports, renderer)\n  const Vue = exports.Vue\n\n  const instance = instances[instanceId]\n\n  // patch reserved tag detection to account for dynamically registered\n  // components\n  const isReservedTag = Vue.config.isReservedTag || (() => false)\n  Vue.config.isReservedTag = name => {\n    return components[name] || isReservedTag(name)\n  }\n\n  // expose weex-specific info\n  Vue.prototype.$instanceId = instanceId\n  Vue.prototype.$document = instance.document\n\n  // expose weex native module getter on subVue prototype so that\n  // vdom runtime modules can access native modules via vnode.context\n  Vue.prototype.$requireWeexModule = moduleGetter\n\n  // Hack `Vue` behavior to handle instance information and data\n  // before root component created.\n  Vue.mixin({\n    beforeCreate () {\n      const options = this.$options\n      // root component (vm)\n      if (options.el) {\n        // set external data of instance\n        const dataOption = options.data\n        const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {}\n        options.data = Object.assign(internalData, instance.data)\n        // record instance by id\n        instance.app = this\n      }\n    }\n  })\n\n  /**\n   * @deprecated Just instance variable `weex.config`\n   * Get instance config.\n   * @return {object}\n   */\n  Vue.prototype.$getConfig = function () {\n    if (instance.app instanceof Vue) {\n      return instance.config\n    }\n  }\n\n  return Vue\n}\n\n/**\n * Generate native module getter. Each native module has several\n * methods to call. And all the behaviors is instance-related. So\n * this getter will return a set of methods which additionally\n * send current instance id to native when called. Also the args\n * will be normalized into \"safe\" value. For example function arg\n * will be converted into a callback id.\n * @param  {string}  instanceId\n * @return {function}\n */\nfunction genModuleGetter (instanceId) {\n  const instance = instances[instanceId]\n  return function (name) {\n    const nativeModule = modules[name] || []\n    const output = {}\n    for (const methodName in nativeModule) {\n      output[methodName] = (...args) => {\n        const finalArgs = args.map(value => {\n          return normalize(value, instance)\n        })\n        renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1)\n      }\n    }\n    return output\n  }\n}\n\n/**\n * Generate HTML5 Timer APIs. An important point is that the callback\n * will be converted into callback id when sent to native. So the\n * framework can make sure no side effect of the callback happened after\n * an instance destroyed.\n * @param  {[type]} instanceId   [description]\n * @param  {[type]} moduleGetter [description]\n * @return {[type]}              [description]\n */\nfunction getInstanceTimer (instanceId, moduleGetter) {\n  const instance = instances[instanceId]\n  const timer = moduleGetter('timer')\n  const timerAPIs = {\n    setTimeout: (...args) => {\n      const handler = function () {\n        args[0](...args.slice(2))\n      }\n      timer.setTimeout(handler, args[1])\n      return instance.callbackId.toString()\n    },\n    setInterval: (...args) => {\n      const handler = function () {\n        args[0](...args.slice(2))\n      }\n      timer.setInterval(handler, args[1])\n      return instance.callbackId.toString()\n    },\n    clearTimeout: (n) => {\n      timer.clearTimeout(n)\n    },\n    clearInterval: (n) => {\n      timer.clearInterval(n)\n    }\n  }\n  return timerAPIs\n}\n\n/**\n * Call a new function body with some global objects.\n * @param  {object} globalObjects\n * @param  {string} code\n * @return {any}\n */\nfunction callFunction (globalObjects, body) {\n  const globalKeys = []\n  const globalValues = []\n  for (const key in globalObjects) {\n    globalKeys.push(key)\n    globalValues.push(globalObjects[key])\n  }\n  globalKeys.push(body)\n\n  const result = new Function(...globalKeys)\n  return result(...globalValues)\n}\n\n/**\n * Convert all type of values into \"safe\" format to send to native.\n * 1. A `function` will be converted into callback id.\n * 2. An `Element` object will be converted into `ref`.\n * The `instance` param is used to generate callback id and store\n * function if necessary.\n * @param  {any}    v\n * @param  {object} instance\n * @return {any}\n */\nfunction normalize (v, instance) {\n  const type = typof(v)\n\n  switch (type) {\n    case 'undefined':\n    case 'null':\n      return ''\n    case 'regexp':\n      return v.toString()\n    case 'date':\n      return v.toISOString()\n    case 'number':\n    case 'string':\n    case 'boolean':\n    case 'array':\n    case 'object':\n      if (v instanceof renderer.Element) {\n        return v.ref\n      }\n      return v\n    case 'function':\n      instance.callbacks[++instance.callbackId] = v\n      return instance.callbackId.toString()\n    default:\n      return JSON.stringify(v)\n  }\n}\n\n/**\n * Get the exact type of an object by `toString()`. For example call\n * `toString()` on an array will be returned `[object Array]`.\n * @param  {any}    v\n * @return {string}\n */\nfunction typof (v) {\n  const s = Object.prototype.toString.call(v)\n  return s.substring(8, s.length - 1).toLowerCase()\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/components/index.js",
    "content": "import Transition from './transition'\nimport TransitionGroup from './transition-group'\n\nexport default {\n  Transition,\n  TransitionGroup\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/components/transition-group.js",
    "content": "import { warn, extend } from 'core/util/index'\nimport { transitionProps, extractTransitionData } from './transition'\n\nconst props = extend({\n  tag: String,\n  moveClass: String\n}, transitionProps)\n\ndelete props.mode\n\nexport default {\n  props,\n\n  created () {\n    const dom = this.$requireWeexModule('dom')\n    this.getPosition = el => new Promise((resolve, reject) => {\n      dom.getComponentRect(el.ref, res => {\n        if (!res.result) {\n          reject(new Error(`failed to get rect for element: ${el.tag}`))\n        } else {\n          resolve(res.size)\n        }\n      })\n    })\n\n    const animation = this.$requireWeexModule('animation')\n    this.animate = (el, options) => new Promise(resolve => {\n      animation.transition(el.ref, options, resolve)\n    })\n  },\n\n  render (h) {\n    const tag = this.tag || this.$vnode.data.tag || 'span'\n    const map = Object.create(null)\n    const prevChildren = this.prevChildren = this.children\n    const rawChildren = this.$slots.default || []\n    const children = this.children = []\n    const transitionData = extractTransitionData(this)\n\n    for (let i = 0; i < rawChildren.length; i++) {\n      const c = rawChildren[i]\n      if (c.tag) {\n        if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {\n          children.push(c)\n          map[c.key] = c\n          ;(c.data || (c.data = {})).transition = transitionData\n        } else if (process.env.NODE_ENV !== 'production') {\n          const opts = c.componentOptions\n          const name = opts\n            ? (opts.Ctor.options.name || opts.tag)\n            : c.tag\n          warn(`<transition-group> children must be keyed: <${name}>`)\n        }\n      }\n    }\n\n    if (prevChildren) {\n      const kept = []\n      const removed = []\n      prevChildren.forEach(c => {\n        c.data.transition = transitionData\n\n        // TODO: record before patch positions\n\n        if (map[c.key]) {\n          kept.push(c)\n        } else {\n          removed.push(c)\n        }\n      })\n      this.kept = h(tag, null, kept)\n      this.removed = removed\n    }\n\n    return h(tag, null, children)\n  },\n\n  beforeUpdate () {\n    // force removing pass\n    this.__patch__(\n      this._vnode,\n      this.kept,\n      false, // hydrating\n      true // removeOnly (!important, avoids unnecessary moves)\n    )\n    this._vnode = this.kept\n  },\n\n  updated () {\n    const children = this.prevChildren\n    const moveClass = this.moveClass || ((this.name || 'v') + '-move')\n    const moveData = children.length && this.getMoveData(children[0].context, moveClass)\n    if (!moveData) {\n      return\n    }\n\n    // TODO: finish implementing move animations once\n    // we have access to sync getComponentRect()\n\n    // children.forEach(callPendingCbs)\n\n    // Promise.all(children.map(c => {\n    //   const oldPos = c.data.pos\n    //   const newPos = c.data.newPos\n    //   const dx = oldPos.left - newPos.left\n    //   const dy = oldPos.top - newPos.top\n    //   if (dx || dy) {\n    //     c.data.moved = true\n    //     return this.animate(c.elm, {\n    //       styles: {\n    //         transform: `translate(${dx}px,${dy}px)`\n    //       }\n    //     })\n    //   }\n    // })).then(() => {\n    //   children.forEach(c => {\n    //     if (c.data.moved) {\n    //       this.animate(c.elm, {\n    //         styles: {\n    //           transform: ''\n    //         },\n    //         duration: moveData.duration || 0,\n    //         delay: moveData.delay || 0,\n    //         timingFunction: moveData.timingFunction || 'linear'\n    //       })\n    //     }\n    //   })\n    // })\n  },\n\n  methods: {\n    getMoveData (context, moveClass) {\n      const stylesheet = context.$options.style || {}\n      return stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][moveClass]\n    }\n  }\n}\n\n// function callPendingCbs (c) {\n//   /* istanbul ignore if */\n//   if (c.elm._moveCb) {\n//     c.elm._moveCb()\n//   }\n//   /* istanbul ignore if */\n//   if (c.elm._enterCb) {\n//     c.elm._enterCb()\n//   }\n// }\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/components/transition.js",
    "content": "// reuse same transition component logic from web\nexport {\n  transitionProps,\n  extractTransitionData\n} from 'web/runtime/components/transition'\n\nimport Transition from 'web/runtime/components/transition'\n\nexport default Transition\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/directives/index.js",
    "content": "export default {\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/index.js",
    "content": "/* @flow */\n\nimport Vue from 'core/index'\nimport { patch } from 'weex/runtime/patch'\nimport { mountComponent } from 'core/instance/lifecycle'\nimport platformDirectives from 'weex/runtime/directives/index'\nimport platformComponents from 'weex/runtime/components/index'\n\nimport {\n  query,\n  mustUseProp,\n  isReservedTag,\n  isUnknownElement\n} from 'weex/util/index'\n\n// install platform specific utils\nVue.config.mustUseProp = mustUseProp\nVue.config.isReservedTag = isReservedTag\nVue.config.isUnknownElement = isUnknownElement\n\n// install platform runtime directives and components\nVue.options.directives = platformDirectives\nVue.options.components = platformComponents\n\n// install platform patch function\nVue.prototype.__patch__ = patch\n\n// wrap mount\nVue.prototype.$mount = function (\n  el?: any,\n  hydrating?: boolean\n): Component {\n  return mountComponent(\n    this,\n    el && query(el, this.$document),\n    hydrating\n  )\n}\n\nexport default Vue\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/attrs.js",
    "content": "/* @flow */\n\nimport { extend } from 'shared/util'\n\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!oldVnode.data.attrs && !vnode.data.attrs) {\n    return\n  }\n  let key, cur, old\n  const elm = vnode.elm\n  const oldAttrs = oldVnode.data.attrs || {}\n  let attrs = vnode.data.attrs || {}\n  // clone observed objects, as the user probably wants to mutate it\n  if (attrs.__ob__) {\n    attrs = vnode.data.attrs = extend({}, attrs)\n  }\n\n  for (key in attrs) {\n    cur = attrs[key]\n    old = oldAttrs[key]\n    if (old !== cur) {\n      elm.setAttr(key, cur)\n    }\n  }\n  for (key in oldAttrs) {\n    if (attrs[key] == null) {\n      elm.setAttr(key)\n    }\n  }\n}\n\nexport default {\n  create: updateAttrs,\n  update: updateAttrs\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/class.js",
    "content": "/* @flow */\n\nimport { extend } from 'shared/util'\n\nfunction updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  const el = vnode.elm\n  const ctx = vnode.context\n\n  const data: VNodeData = vnode.data\n  const oldData: VNodeData = oldVnode.data\n  if (!data.staticClass && !data.class &&\n      (!oldData || (!oldData.staticClass && !oldData.class))) {\n    return\n  }\n\n  const oldClassList = []\n  // unlike web, weex vnode staticClass is an Array\n  const oldStaticClass: any = oldData.staticClass\n  if (oldStaticClass) {\n    oldClassList.push.apply(oldClassList, oldStaticClass)\n  }\n  if (oldData.class) {\n    oldClassList.push.apply(oldClassList, oldData.class)\n  }\n\n  const classList = []\n  // unlike web, weex vnode staticClass is an Array\n  const staticClass: any = data.staticClass\n  if (staticClass) {\n    classList.push.apply(classList, staticClass)\n  }\n  if (data.class) {\n    classList.push.apply(classList, data.class)\n  }\n\n  const style = getStyle(oldClassList, classList, ctx)\n  for (const key in style) {\n    el.setStyle(key, style[key])\n  }\n}\n\nfunction getStyle (oldClassList: Array<string>, classList: Array<string>, ctx: Component): Object {\n  // style is a weex-only injected object\n  // compiled from <style> tags in weex files\n  const stylesheet: any = ctx.$options.style || {}\n  const result = {}\n  classList.forEach(name => {\n    const style = stylesheet[name]\n    extend(result, style)\n  })\n  oldClassList.forEach(name => {\n    const style = stylesheet[name]\n    for (const key in style) {\n      if (!result.hasOwnProperty(key)) {\n        result[key] = ''\n      }\n    }\n  })\n  return result\n}\n\nexport default {\n  create: updateClass,\n  update: updateClass\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/events.js",
    "content": "/* @flow */\n\nimport { updateListeners } from 'core/vdom/helpers/update-listeners'\n\nlet target: any\n\nfunction add (\n  event: string,\n  handler: Function,\n  once: boolean,\n  capture: boolean\n) {\n  if (capture) {\n    console.log('Weex do not support event in bubble phase.')\n    return\n  }\n  if (once) {\n    const oldHandler = handler\n    const _target = target // save current target element in closure\n    handler = function (ev) {\n      const res = arguments.length === 1\n        ? oldHandler(ev)\n        : oldHandler.apply(null, arguments)\n      if (res !== null) {\n        remove(event, null, null, _target)\n      }\n    }\n  }\n  target.addEvent(event, handler)\n}\n\nfunction remove (\n  event: string,\n  handler: any,\n  capture: any,\n  _target?: any\n) {\n  (_target || target).removeEvent(event)\n}\n\nfunction updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!oldVnode.data.on && !vnode.data.on) {\n    return\n  }\n  const on = vnode.data.on || {}\n  const oldOn = oldVnode.data.on || {}\n  target = vnode.elm\n  updateListeners(on, oldOn, add, remove, vnode.context)\n}\n\nexport default {\n  create: updateDOMListeners,\n  update: updateDOMListeners\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/index.js",
    "content": "import attrs from './attrs'\nimport klass from './class'\nimport events from './events'\nimport style from './style'\nimport transition from './transition'\n\nexport default [\n  attrs,\n  klass,\n  events,\n  style,\n  transition\n]\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/style.js",
    "content": "/* @flow */\n\nimport { extend, cached, camelize } from 'shared/util'\n\nconst normalize = cached(camelize)\n\nfunction createStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!vnode.data.staticStyle) {\n    updateStyle(oldVnode, vnode)\n    return\n  }\n  const elm = vnode.elm\n  const staticStyle = vnode.data.staticStyle\n  for (const name in staticStyle) {\n    if (staticStyle[name]) {\n      elm.setStyle(normalize(name), staticStyle[name])\n    }\n  }\n  updateStyle(oldVnode, vnode)\n}\n\nfunction updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!oldVnode.data.style && !vnode.data.style) {\n    return\n  }\n  let cur, name\n  const elm = vnode.elm\n  const oldStyle: any = oldVnode.data.style || {}\n  let style: any = vnode.data.style || {}\n\n  const needClone = style.__ob__\n\n  // handle array syntax\n  if (Array.isArray(style)) {\n    style = vnode.data.style = toObject(style)\n  }\n\n  // clone the style for future updates,\n  // in case the user mutates the style object in-place.\n  if (needClone) {\n    style = vnode.data.style = extend({}, style)\n  }\n\n  for (name in oldStyle) {\n    if (!style[name]) {\n      elm.setStyle(normalize(name), '')\n    }\n  }\n  for (name in style) {\n    cur = style[name]\n    elm.setStyle(normalize(name), cur)\n  }\n}\n\nfunction toObject (arr) {\n  const res = {}\n  for (var i = 0; i < arr.length; i++) {\n    if (arr[i]) {\n      extend(res, arr[i])\n    }\n  }\n  return res\n}\n\nexport default {\n  create: createStyle,\n  update: updateStyle\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/transition.js",
    "content": "import { warn } from 'core/util/debug'\nimport { extend, once, noop } from 'shared/util'\nimport { activeInstance } from 'core/instance/lifecycle'\nimport { resolveTransition } from 'web/runtime/transition-util'\n\nexport default {\n  create: enter,\n  activate: enter,\n  remove: leave\n}\n\nfunction enter (_, vnode) {\n  const el = vnode.elm\n\n  // call leave callback now\n  if (el._leaveCb) {\n    el._leaveCb.cancelled = true\n    el._leaveCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (!data) {\n    return\n  }\n\n  /* istanbul ignore if */\n  if (el._enterCb) {\n    return\n  }\n\n  const {\n    enterClass,\n    enterToClass,\n    enterActiveClass,\n    appearClass,\n    appearToClass,\n    appearActiveClass,\n    beforeEnter,\n    enter,\n    afterEnter,\n    enterCancelled,\n    beforeAppear,\n    appear,\n    afterAppear,\n    appearCancelled\n  } = data\n\n  let context = activeInstance\n  let transitionNode = activeInstance.$vnode\n  while (transitionNode && transitionNode.parent) {\n    transitionNode = transitionNode.parent\n    context = transitionNode.context\n  }\n\n  const isAppear = !context._isMounted || !vnode.isRootInsert\n\n  if (isAppear && !appear && appear !== '') {\n    return\n  }\n\n  const startClass = isAppear ? appearClass : enterClass\n  const toClass = isAppear ? appearToClass : enterToClass\n  const activeClass = isAppear ? appearActiveClass : enterActiveClass\n  const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter\n  const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter\n  const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter\n  const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled\n\n  const userWantsControl =\n    enterHook &&\n    // enterHook may be a bound method which exposes\n    // the length of original fn as _length\n    (enterHook._length || enterHook.length) > 1\n\n  const stylesheet = vnode.context.$options.style || {}\n  const startState = stylesheet[startClass]\n  const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {}\n  const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass, vnode.context)\n  const needAnimation = Object.keys(endState).length > 0\n\n  const cb = el._enterCb = once(() => {\n    if (cb.cancelled) {\n      enterCancelledHook && enterCancelledHook(el)\n    } else {\n      afterEnterHook && afterEnterHook(el)\n    }\n    el._enterCb = null\n  })\n\n  // We need to wait until the native element has been inserted, but currently\n  // there's no API to do that. So we have to wait \"one frame\" - not entirely\n  // sure if this is guaranteed to be enough (e.g. on slow devices?)\n  setTimeout(() => {\n    const parent = el.parentNode\n    const pendingNode = parent && parent._pending && parent._pending[vnode.key]\n    if (pendingNode &&\n        pendingNode.context === vnode.context &&\n        pendingNode.tag === vnode.tag &&\n        pendingNode.elm._leaveCb) {\n      pendingNode.elm._leaveCb()\n    }\n    enterHook && enterHook(el, cb)\n\n    if (needAnimation) {\n      const animation = vnode.context.$requireWeexModule('animation')\n      animation.transition(el.ref, {\n        styles: endState,\n        duration: transitionProperties.duration || 0,\n        delay: transitionProperties.delay || 0,\n        timingFunction: transitionProperties.timingFunction || 'linear'\n      }, userWantsControl ? noop : cb)\n    } else if (!userWantsControl) {\n      cb()\n    }\n  }, 16)\n\n  // start enter transition\n  beforeEnterHook && beforeEnterHook(el)\n\n  if (startState) {\n    for (const key in startState) {\n      el.setStyle(key, startState[key])\n    }\n  }\n\n  if (!needAnimation && !userWantsControl) {\n    cb()\n  }\n}\n\nfunction leave (vnode, rm) {\n  const el = vnode.elm\n\n  // call enter callback now\n  if (el._enterCb) {\n    el._enterCb.cancelled = true\n    el._enterCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (!data) {\n    return rm()\n  }\n\n  if (el._leaveCb) {\n    return\n  }\n\n  const {\n    leaveClass,\n    leaveToClass,\n    leaveActiveClass,\n    beforeLeave,\n    leave,\n    afterLeave,\n    leaveCancelled,\n    delayLeave\n  } = data\n\n  const userWantsControl =\n    leave &&\n    // leave hook may be a bound method which exposes\n    // the length of original fn as _length\n    (leave._length || leave.length) > 1\n\n  const stylesheet = vnode.context.$options.style || {}\n  const startState = stylesheet[leaveClass]\n  const endState = stylesheet[leaveToClass] || stylesheet[leaveActiveClass]\n  const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][leaveActiveClass]) || {}\n\n  const cb = el._leaveCb = once(() => {\n    if (el.parentNode && el.parentNode._pending) {\n      el.parentNode._pending[vnode.key] = null\n    }\n    if (cb.cancelled) {\n      leaveCancelled && leaveCancelled(el)\n    } else {\n      rm()\n      afterLeave && afterLeave(el)\n    }\n    el._leaveCb = null\n  })\n\n  if (delayLeave) {\n    delayLeave(performLeave)\n  } else {\n    performLeave()\n  }\n\n  function performLeave () {\n    const animation = vnode.context.$requireWeexModule('animation')\n    // the delayed leave may have already been cancelled\n    if (cb.cancelled) {\n      return\n    }\n    // record leaving element\n    if (!vnode.data.show) {\n      (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode\n    }\n    beforeLeave && beforeLeave(el)\n\n    if (startState) {\n      animation.transition(el.ref, {\n        styles: startState\n      }, next)\n    } else {\n      next()\n    }\n\n    function next () {\n      animation.transition(el.ref, {\n        styles: endState,\n        duration: transitionProperties.duration || 0,\n        delay: transitionProperties.delay || 0,\n        timingFunction: transitionProperties.timingFunction || 'linear'\n      }, userWantsControl ? noop : cb)\n    }\n\n    leave && leave(el, cb)\n    if (!endState && !userWantsControl) {\n      cb()\n    }\n  }\n}\n\n// determine the target animation style for an entering transition.\nfunction getEnterTargetState (el, stylesheet, startClass, endClass, activeClass, vm) {\n  const targetState = {}\n  const startState = stylesheet[startClass]\n  const endState = stylesheet[endClass]\n  const activeState = stylesheet[activeClass]\n  // 1. fallback to element's default styling\n  if (startState) {\n    for (const key in startState) {\n      targetState[key] = el.style[key]\n      if (\n        process.env.NODE_ENV !== 'production' &&\n        targetState[key] == null &&\n        (!activeState || activeState[key] == null) &&\n        (!endState || endState[key] == null)\n      ) {\n        warn(\n          `transition property \"${key}\" is declared in enter starting class (.${startClass}), ` +\n          `but not declared anywhere in enter ending class (.${endClass}), ` +\n          `enter active cass (.${activeClass}) or the element's default styling. ` +\n          `Note in Weex, CSS properties need explicit values to be transitionable.`\n        )\n      }\n    }\n  }\n  // 2. if state is mixed in active state, extract them while excluding\n  //    transition properties\n  if (activeState) {\n    for (const key in activeState) {\n      if (key.indexOf('transition') !== 0) {\n        targetState[key] = activeState[key]\n      }\n    }\n  }\n  // 3. explicit endState has highest priority\n  if (endState) {\n    extend(targetState, endState)\n  }\n  return targetState\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/node-ops.js",
    "content": "/* globals renderer */\n// renderer is injected by weex factory wrapper\n\n/*这个文件是根据平台（web或者weex）操作真实节点（如web上的Dom）的操作函数进行了一层适配，对外可以统一提供操作真实节点的接口，内部实现根据平台的变化而变化*/\n\nexport const namespaceMap = {}\n\nexport function createElement (tagName) {\n  return new renderer.Element(tagName)\n}\n\nexport function createElementNS (namespace, tagName) {\n  return new renderer.Element(namespace + ':' + tagName)\n}\n\nexport function createTextNode (text) {\n  return new renderer.TextNode(text)\n}\n\nexport function createComment (text) {\n  return new renderer.Comment(text)\n}\n\nexport function insertBefore (node, target, before) {\n  if (target.nodeType === 3) {\n    if (node.type === 'text') {\n      node.setAttr('value', target.text)\n      target.parentNode = node\n    } else {\n      const text = createElement('text')\n      text.setAttr('value', target.text)\n      node.insertBefore(text, before)\n    }\n    return\n  }\n  node.insertBefore(target, before)\n}\n\nexport function removeChild (node, child) {\n  if (child.nodeType === 3) {\n    node.setAttr('value', '')\n    return\n  }\n  node.removeChild(child)\n}\n\nexport function appendChild (node, child) {\n  if (child.nodeType === 3) {\n    if (node.type === 'text') {\n      node.setAttr('value', child.text)\n      child.parentNode = node\n    } else {\n      const text = createElement('text')\n      text.setAttr('value', child.text)\n      node.appendChild(text)\n    }\n    return\n  }\n\n  node.appendChild(child)\n}\n\nexport function parentNode (node) {\n  return node.parentNode\n}\n\nexport function nextSibling (node) {\n  return node.nextSibling\n}\n\nexport function tagName (node) {\n  return node.type\n}\n\nexport function setTextContent (node, text) {\n  node.parentNode.setAttr('value', text)\n}\n\nexport function setAttribute (node, key, val) {\n  node.setAttr(key, val)\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/patch.js",
    "content": "/* @flow */\n\nimport * as nodeOps from 'weex/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimport baseModules from 'core/vdom/modules/index'\nimport platformModules from 'weex/runtime/modules/index'\n\n// the directive module should be applied last, after all\n// built-in modules have been applied.\nconst modules = platformModules.concat(baseModules)\n\nexport const patch: Function = createPatchFunction({\n  nodeOps,\n  modules,\n  LONG_LIST_THRESHOLD: 10\n})\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/text-node.js",
    "content": "let latestNodeId = 1\n\nexport default function TextNode (text) {\n  this.instanceId = ''\n  this.nodeId = latestNodeId++\n  this.parentNode = null\n  this.nodeType = 3\n  this.text = text\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime-factory.js",
    "content": "// this entry is built and wrapped with a factory function\n// used to generate a fresh copy of Vue for every Weex instance.\n\nimport Vue from './runtime/index'\n\nexports.Vue = Vue\n"
  },
  {
    "path": "vue-src/platforms/weex/util/index.js",
    "content": "/* globals renderer */\n\nimport { makeMap } from 'shared/util'\n\n/*判断是否是保留的标签*/\nexport const isReservedTag = makeMap(\n  'template,script,style,element,content,slot,link,meta,svg,view,' +\n  'a,div,img,image,text,span,richtext,input,switch,textarea,spinner,select,' +\n  'slider,slider-neighbor,indicator,trisition,trisition-group,canvas,' +\n  'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' +\n  'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown',\n  true\n)\n\n// Elements that you can, intentionally, leave open (and which close themselves)\n// more flexable than web\nexport const canBeLeftOpenTag = makeMap(\n  'web,spinner,switch,video,textarea,canvas,' +\n  'indicator,marquee,countdown',\n  true\n)\n\nexport const isUnaryTag = makeMap(\n  'embed,img,image,input,link,meta',\n  true\n)\n\nexport function mustUseProp () { /* console.log('mustUseProp') */ }\nexport function getTagNamespace () { /* console.log('getTagNamespace') */ }\nexport function isUnknownElement () { /* console.log('isUnknownElement') */ }\n\nexport function query (el, document) {\n  // renderer is injected by weex factory wrapper\n  const placeholder = new renderer.Comment('root')\n  placeholder.hasAttribute = placeholder.removeAttribute = function () {} // hack for patch\n  document.documentElement.appendChild(placeholder)\n  return placeholder\n}\n"
  },
  {
    "path": "vue-src/server/bundle-renderer/create-bundle-renderer.js",
    "content": "/* @flow */\n\nimport { createBundleRunner } from './create-bundle-runner'\nimport type { Renderer, RenderOptions } from '../create-renderer'\nimport { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'\n\nconst fs = require('fs')\nconst path = require('path')\nconst PassThrough = require('stream').PassThrough\n\nconst INVALID_MSG =\n  'Invalid server-rendering bundle format. Should be a string ' +\n  'or a bundle Object of type:\\n\\n' +\n`{\n  entry: string;\n  files: { [filename: string]: string; };\n  maps: { [filename: string]: string; };\n}\\n`\n\n// The render bundle can either be a string (single bundled file)\n// or a bundle manifest object generated by vue-ssr-webpack-plugin.\ntype RenderBundle = {\n  basedir?: string;\n  entry: string;\n  files: { [filename: string]: string; };\n  maps: { [filename: string]: string; };\n  modules?: { [filename: string]: Array<string> };\n};\n\nexport function createBundleRendererCreator (createRenderer: () => Renderer) {\n  return function createBundleRenderer (\n    bundle: string | RenderBundle,\n    rendererOptions?: RenderOptions = {}\n  ) {\n    let files, entry, maps\n    let basedir = rendererOptions.basedir\n    const runInNewContext = rendererOptions.runInNewContext !== false\n\n    // load bundle if given filepath\n    if (\n      typeof bundle === 'string' &&\n      /\\.js(on)?$/.test(bundle) &&\n      path.isAbsolute(bundle)\n    ) {\n      if (fs.existsSync(bundle)) {\n        const isJSON = /\\.json$/.test(bundle)\n        basedir = basedir || path.dirname(bundle)\n        bundle = fs.readFileSync(bundle, 'utf-8')\n        if (isJSON) {\n          try {\n            bundle = JSON.parse(bundle)\n          } catch (e) {\n            throw new Error(`Invalid JSON bundle file: ${bundle}`)\n          }\n        }\n      } else {\n        throw new Error(`Cannot locate bundle file: ${bundle}`)\n      }\n    }\n\n    if (typeof bundle === 'object') {\n      entry = bundle.entry\n      files = bundle.files\n      basedir = basedir || bundle.basedir\n      maps = createSourceMapConsumers(bundle.maps)\n      if (typeof entry !== 'string' || typeof files !== 'object') {\n        throw new Error(INVALID_MSG)\n      }\n    } else if (typeof bundle === 'string') {\n      entry = '__vue_ssr_bundle__'\n      files = { '__vue_ssr_bundle__': bundle }\n      maps = {}\n    } else {\n      throw new Error(INVALID_MSG)\n    }\n\n    const renderer = createRenderer(rendererOptions)\n\n    const run = createBundleRunner(entry, files, basedir, runInNewContext)\n\n    return {\n      renderToString: (context?: Object, cb: (err: ?Error, res: ?string) => void) => {\n        if (typeof context === 'function') {\n          cb = context\n          context = {}\n        }\n        run(context).catch(err => {\n          rewriteErrorTrace(err, maps)\n          cb(err)\n        }).then(app => {\n          if (app) {\n            renderer.renderToString(app, context, (err, res) => {\n              rewriteErrorTrace(err, maps)\n              cb(err, res)\n            })\n          }\n        })\n      },\n\n      renderToStream: (context?: Object) => {\n        const res = new PassThrough()\n        run(context).catch(err => {\n          rewriteErrorTrace(err, maps)\n          // avoid emitting synchronously before user can\n          // attach error listener\n          process.nextTick(() => {\n            res.emit('error', err)\n          })\n        }).then(app => {\n          if (app) {\n            const renderStream = renderer.renderToStream(app, context)\n\n            renderStream.on('error', err => {\n              rewriteErrorTrace(err, maps)\n              res.emit('error', err)\n            })\n\n            // relay HTMLStream special events\n            if (rendererOptions && rendererOptions.template) {\n              renderStream.on('beforeStart', () => {\n                res.emit('beforeStart')\n              })\n              renderStream.on('beforeEnd', () => {\n                res.emit('beforeEnd')\n              })\n            }\n\n            renderStream.pipe(res)\n          }\n        })\n\n        return res\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/server/bundle-renderer/create-bundle-runner.js",
    "content": "import { isPlainObject } from 'shared/util'\n\nconst vm = require('vm')\nconst path = require('path')\nconst resolve = require('resolve')\nconst NativeModule = require('module')\n\nfunction createContext (context) {\n  const sandbox = {\n    Buffer,\n    console,\n    process,\n    setTimeout,\n    setInterval,\n    setImmediate,\n    clearTimeout,\n    clearInterval,\n    clearImmediate,\n    __VUE_SSR_CONTEXT__: context\n  }\n  sandbox.global = sandbox\n  return sandbox\n}\n\nfunction compileModule (files, basedir) {\n  const compiledScripts = {}\n  const resolvedModules = {}\n\n  function getCompiledScript (filename) {\n    if (compiledScripts[filename]) {\n      return compiledScripts[filename]\n    }\n    const code = files[filename]\n    const wrapper = NativeModule.wrap(code)\n    const script = new vm.Script(wrapper, {\n      filename,\n      displayErrors: true\n    })\n    compiledScripts[filename] = script\n    return script\n  }\n\n  function evaluateModule (filename, context, evaluatedFiles = {}) {\n    if (evaluatedFiles[filename]) {\n      return evaluatedFiles[filename]\n    }\n\n    const script = getCompiledScript(filename)\n    const compiledWrapper = script.runInNewContext(context)\n    const m = { exports: {}}\n    const r = file => {\n      file = path.join('.', file)\n      if (files[file]) {\n        return evaluateModule(file, context, evaluatedFiles)\n      } else if (basedir) {\n        return require(\n          resolvedModules[file] ||\n          (resolvedModules[file] = resolve.sync(file, { basedir }))\n        )\n      } else {\n        return require(file)\n      }\n    }\n    compiledWrapper.call(m.exports, m.exports, r, m)\n\n    const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')\n      ? m.exports.default\n      : m.exports\n    evaluatedFiles[filename] = res\n    return res\n  }\n  return evaluateModule\n}\n\nfunction deepClone (val) {\n  if (isPlainObject(val)) {\n    const res = {}\n    for (const key in val) {\n      res[key] = deepClone(val[key])\n    }\n    return res\n  } else if (Array.isArray(val)) {\n    return val.slice()\n  } else {\n    return val\n  }\n}\n\nexport function createBundleRunner (entry, files, basedir, runInNewContext) {\n  const evaluate = compileModule(files, basedir)\n  if (runInNewContext) {\n    // new context mode: creates a fresh context and re-evaluate the bundle\n    // on each render. Ensures entire application state is fresh for each\n    // render, but incurs extra evaluation cost.\n    return (userContext = {}) => new Promise(resolve => {\n      userContext._registeredComponents = new Set()\n      const res = evaluate(entry, createContext(userContext))\n      resolve(typeof res === 'function' ? res(userContext) : res)\n    })\n  } else {\n    // direct mode: instead of re-evaluating the whole bundle on\n    // each render, it simply calls the exported function. This avoids the\n    // module evaluation costs but requires the source code to be structured\n    // slightly differently.\n\n    // the initial context is only used for collecting possible non-component\n    // styles injected by vue-style-loader.\n    const initialContext = {}\n    const sharedContext = createContext(initialContext)\n\n    let runner // lazy creation so that errors can be caught by user\n    return (userContext = {}) => new Promise(resolve => {\n      if (!runner) {\n        runner = evaluate(entry, sharedContext)\n        // On subsequent renders, __VUE_SSR_CONTEXT__ will not be avaialbe\n        // to prevent cross-request pollution.\n        delete sharedContext.__VUE_SSR_CONTEXT__\n        if (typeof runner !== 'function') {\n          throw new Error(\n            'bundle export should be a function when using ' +\n            '{ runInNewContext: false }.'\n          )\n        }\n      }\n      userContext._registeredComponents = new Set()\n      // vue-style-loader styles imported outside of component lifecycle hooks\n      if (initialContext._styles) {\n        userContext._styles = deepClone(initialContext._styles)\n      }\n      resolve(runner(userContext))\n    })\n  }\n}\n"
  },
  {
    "path": "vue-src/server/bundle-renderer/source-map-support.js",
    "content": "/* @flow */\n\nconst SourceMapConsumer = require('source-map').SourceMapConsumer\n\nconst filenameRE = /\\(([^)]+\\.js):(\\d+):(\\d+)\\)$/\n\nexport function createSourceMapConsumers (rawMaps: Object) {\n  const maps = {}\n  Object.keys(rawMaps).forEach(file => {\n    maps[file] = new SourceMapConsumer(rawMaps[file])\n  })\n  return maps\n}\n\nexport function rewriteErrorTrace (e: any, mapConsumers: {\n  [key: string]: SourceMapConsumer\n}) {\n  if (e && typeof e.stack === 'string') {\n    e.stack = e.stack.split('\\n').map(line => {\n      return rewriteTraceLine(line, mapConsumers)\n    }).join('\\n')\n  }\n}\n\nfunction rewriteTraceLine (trace: string, mapConsumers: {\n  [key: string]: SourceMapConsumer\n}) {\n  const m = trace.match(filenameRE)\n  const map = m && mapConsumers[m[1]]\n  if (m != null && map) {\n    const originalPosition = map.originalPositionFor({\n      line: Number(m[2]),\n      column: Number(m[3])\n    })\n    if (originalPosition.source != null) {\n      const { source, line, column } = originalPosition\n      const mappedPosition = `(${source.replace(/^webpack:\\/\\/\\//, '')}:${String(line)}:${String(column)})`\n      return trace.replace(filenameRE, mappedPosition)\n    } else {\n      return trace\n    }\n  } else {\n    return trace\n  }\n}\n"
  },
  {
    "path": "vue-src/server/create-renderer.js",
    "content": "/* @flow */\n\nimport RenderStream from './render-stream'\nimport TemplateRenderer from './template-renderer/index'\nimport { createWriteFunction } from './write'\nimport { createRenderFunction } from './render'\nimport type { ClientManifest } from './template-renderer/index'\n\nexport type Renderer = {\n  renderToString: (component: Component, context: any, cb: any) => void;\n  renderToStream: (component: Component, context?: Object) => stream$Readable;\n};\n\ntype RenderCache = {\n  get: (key: string, cb?: Function) => string | void;\n  set: (key: string, val: string) => void;\n  has?: (key: string, cb?: Function) => boolean | void;\n};\n\nexport type RenderOptions = {\n  modules?: Array<(vnode: VNode) => string>;\n  directives?: Object;\n  isUnaryTag?: Function;\n  cache?: RenderCache;\n  template?: string;\n  inject?: boolean;\n  basedir?: string;\n  shouldPreload?: Function;\n  clientManifest?: ClientManifest;\n  runInNewContext?: boolean;\n};\n\nexport function createRenderer ({\n  modules = [],\n  directives = {},\n  isUnaryTag = (() => false),\n  template,\n  inject,\n  cache,\n  shouldPreload,\n  clientManifest\n}: RenderOptions = {}): Renderer {\n  const render = createRenderFunction(modules, directives, isUnaryTag, cache)\n  const templateRenderer = new TemplateRenderer({\n    template,\n    inject,\n    shouldPreload,\n    clientManifest\n  })\n\n  return {\n    renderToString (\n      component: Component,\n      context: any,\n      done: any\n    ): void {\n      if (typeof context === 'function') {\n        done = context\n        context = {}\n      }\n      if (context) {\n        templateRenderer.bindRenderFns(context)\n      }\n      let result = ''\n      const write = createWriteFunction(text => {\n        result += text\n        return false\n      }, done)\n      try {\n        render(component, write, context, () => {\n          if (template) {\n            result = templateRenderer.renderSync(result, context)\n          }\n          done(null, result)\n        })\n      } catch (e) {\n        done(e)\n      }\n    },\n\n    renderToStream (\n      component: Component,\n      context?: Object\n    ): stream$Readable {\n      if (context) {\n        templateRenderer.bindRenderFns(context)\n      }\n      const renderStream = new RenderStream((write, done) => {\n        render(component, write, context, done)\n      })\n      if (!template) {\n        return renderStream\n      } else {\n        const templateStream = templateRenderer.createStream(context)\n        renderStream.on('error', err => {\n          templateStream.emit('error', err)\n        })\n        renderStream.pipe(templateStream)\n        return templateStream\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/server/render-context.js",
    "content": "/* @flow */\n\nimport { isUndef } from 'shared/util'\n\ntype RenderState = {\n  type: 'Element';\n  rendered: number;\n  total: number;\n  endTag: string;\n  children: Array<VNode>;\n} | {\n  type: 'Component';\n  prevActive: Component;\n} | {\n  type: 'ComponentWithCache';\n  buffer: Array<string>;\n  bufferIndex: number;\n  componentBuffer: Array<Set<Class<Component>>>;\n  key: string;\n};\n\nexport class RenderContext {\n  userContext: ?Object;\n  activeInstance: Component;\n  renderStates: Array<RenderState>;\n  write: (text: string, next: Function) => void;\n  renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void;\n  next: () => void;\n  done: () => void;\n\n  modules: Array<() => ?string>;\n  directives: Object;\n  isUnaryTag: (tag: string) => boolean;\n\n  cache: any;\n  get: ?(key: string, cb: Function) => void;\n  has: ?(key: string, cb: Function) => void;\n\n  constructor (options: Object) {\n    this.userContext = options.userContext\n    this.activeInstance = options.activeInstance\n    this.renderStates = []\n\n    this.write = options.write\n    this.done = options.done\n    this.renderNode = options.renderNode\n\n    this.isUnaryTag = options.isUnaryTag\n    this.modules = options.modules\n    this.directives = options.directives\n\n    const cache = options.cache\n    if (cache && (!cache.get || !cache.set)) {\n      throw new Error('renderer cache must implement at least get & set.')\n    }\n    this.cache = cache\n    this.get = cache && normalizeAsync(cache, 'get')\n    this.has = cache && normalizeAsync(cache, 'has')\n\n    this.next = this.next.bind(this)\n  }\n\n  next () {\n    const lastState = this.renderStates[this.renderStates.length - 1]\n    if (isUndef(lastState)) {\n      return this.done()\n    }\n    switch (lastState.type) {\n      case 'Element':\n        const { children, total } = lastState\n        const rendered = lastState.rendered++\n        if (rendered < total) {\n          this.renderNode(children[rendered], false, this)\n        } else {\n          this.renderStates.pop()\n          this.write(lastState.endTag, this.next)\n        }\n        break\n      case 'Component':\n        this.renderStates.pop()\n        this.activeInstance = lastState.prevActive\n        this.next()\n        break\n      case 'ComponentWithCache':\n        this.renderStates.pop()\n        const { buffer, bufferIndex, componentBuffer, key } = lastState\n        const result = {\n          html: buffer[bufferIndex],\n          components: componentBuffer[bufferIndex]\n        }\n        this.cache.set(key, result)\n        if (bufferIndex === 0) {\n          // this is a top-level cached component,\n          // exit caching mode.\n          this.write.caching = false\n        } else {\n          // parent component is also being cached,\n          // merge self into parent's result\n          buffer[bufferIndex - 1] += result.html\n          const prev = componentBuffer[bufferIndex - 1]\n          result.components.forEach(c => prev.add(c))\n        }\n        buffer.length = bufferIndex\n        componentBuffer.length = bufferIndex\n        this.next()\n        break\n    }\n  }\n}\n\nfunction normalizeAsync (cache, method) {\n  const fn = cache[method]\n  if (isUndef(fn)) {\n    return\n  } else if (fn.length > 1) {\n    return (key, cb) => fn.call(cache, key, cb)\n  } else {\n    return (key, cb) => cb(fn.call(cache, key))\n  }\n}\n"
  },
  {
    "path": "vue-src/server/render-stream.js",
    "content": "/* @flow */\n\n/**\n * Original RenderStream implementation by Sasha Aickin (@aickin)\n * Licensed under the Apache License, Version 2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Modified by Evan You (@yyx990803)\n */\n\nconst stream = require('stream')\n\nimport { isTrue, isUndef } from 'shared/util'\nimport { createWriteFunction } from './write'\n\nexport default class RenderStream extends stream.Readable {\n  buffer: string;\n  render: (write: Function, done: Function) => void;\n  expectedSize: number;\n  write: Function;\n  next: Function;\n  end: Function;\n  done: boolean;\n\n  constructor (render: Function) {\n    super()\n    this.buffer = ''\n    this.render = render\n    this.expectedSize = 0\n\n    this.write = createWriteFunction((text, next) => {\n      const n = this.expectedSize\n      this.buffer += text\n      if (this.buffer.length >= n) {\n        this.next = next\n        this.pushBySize(n)\n        return true // we will decide when to call next\n      }\n      return false\n    }, err => {\n      this.emit('error', err)\n    })\n\n    this.end = () => {\n      // the rendering is finished; we should push out the last of the buffer.\n      this.done = true\n      this.push(this.buffer)\n    }\n  }\n\n  pushBySize (n: number) {\n    const bufferToPush = this.buffer.substring(0, n)\n    this.buffer = this.buffer.substring(n)\n    this.push(bufferToPush)\n  }\n\n  tryRender () {\n    try {\n      this.render(this.write, this.end)\n    } catch (e) {\n      this.emit('error', e)\n    }\n  }\n\n  tryNext () {\n    try {\n      this.next()\n    } catch (e) {\n      this.emit('error', e)\n    }\n  }\n\n  _read (n: number) {\n    this.expectedSize = n\n    // it's possible that the last chunk added bumped the buffer up to > 2 * n,\n    // which means we will need to go through multiple read calls to drain it\n    // down to < n.\n    if (isTrue(this.done)) {\n      this.push(null)\n      return\n    }\n    if (this.buffer.length >= n) {\n      this.pushBySize(n)\n      return\n    }\n    if (isUndef(this.next)) {\n      // start the rendering chain.\n      this.tryRender()\n    } else {\n      // continue with the rendering.\n      this.tryNext()\n    }\n  }\n}\n"
  },
  {
    "path": "vue-src/server/render.js",
    "content": "/* @flow */\n\nconst { escape } = require('he')\n\nimport { SSR_ATTR } from 'shared/constants'\nimport { RenderContext } from './render-context'\nimport { compileToFunctions } from 'web/compiler/index'\nimport { createComponentInstanceForVnode } from 'core/vdom/create-component'\n\nimport { isDef, isUndef, isTrue } from 'shared/util'\n\nlet warned = Object.create(null)\nconst warnOnce = msg => {\n  if (!warned[msg]) {\n    warned[msg] = true\n    console.warn(`\\n\\u001b[31m${msg}\\u001b[39m\\n`)\n  }\n}\n\n/*从来存放模板template与template的编译结果的缓存器，避免重复编译*/\nconst compilationCache = Object.create(null)\n/*将template模板编译成render函数*/\nconst normalizeRender = vm => {\n  const { render, template } = vm.$options\n  if (isUndef(render)) {\n    if (template) {\n      const renderFns = (\n        compilationCache[template] ||\n        (compilationCache[template] = compileToFunctions(template))\n      )\n      Object.assign(vm.$options, renderFns)\n    } else {\n      throw new Error(\n        `render function or template not defined in component: ${\n          vm.$options.name || vm.$options._componentTag || 'anonymous'\n        }`\n      )\n    }\n  }\n}\n\nfunction renderNode (node, isRoot, context) {\n  if (isDef(node.componentOptions)) {\n    renderComponent(node, isRoot, context)\n  } else {\n    if (isDef(node.tag)) {\n      renderElement(node, isRoot, context)\n    } else if (isTrue(node.isComment)) {\n      context.write(\n        `<!--${node.text}-->`,\n        context.next\n      )\n    } else {\n      context.write(\n        node.raw ? node.text : escape(String(node.text)),\n        context.next\n      )\n    }\n  }\n}\n\nfunction renderComponent (node, isRoot, context) {\n  const { write, next, userContext } = context\n\n  // check cache hit\n  const Ctor = node.componentOptions.Ctor\n  const getKey = Ctor.options.serverCacheKey\n  const name = Ctor.options.name\n\n  // exposed by vue-loader, need to call this if cache hit because\n  // component lifecycle hooks will not be called.\n  const registerComponent = Ctor.options._ssrRegister\n  if (write.caching && isDef(registerComponent)) {\n    write.componentBuffer[write.componentBuffer.length - 1].add(registerComponent)\n  }\n\n  const cache = context.cache\n  if (isDef(getKey) && isDef(cache) && isDef(name)) {\n    const key = name + '::' + getKey(node.componentOptions.propsData)\n    const { has, get } = context\n    if (isDef(has)) {\n      has(key, hit => {\n        if (hit === true && isDef(get)) {\n          get(key, res => {\n            if (isDef(registerComponent)) {\n              registerComponent(userContext)\n            }\n            res.components.forEach(register => register(userContext))\n            write(res.html, next)\n          })\n        } else {\n          renderComponentWithCache(node, isRoot, key, context)\n        }\n      })\n    } else if (isDef(get)) {\n      get(key, res => {\n        if (isDef(res)) {\n          if (isDef(registerComponent)) {\n            registerComponent(userContext)\n          }\n          res.components.forEach(register => register(userContext))\n          write(res.html, next)\n        } else {\n          renderComponentWithCache(node, isRoot, key, context)\n        }\n      })\n    }\n  } else {\n    if (isDef(getKey) && isUndef(cache)) {\n      warnOnce(\n        `[vue-server-renderer] Component ${\n          Ctor.options.name || '(anonymous)'\n        } implemented serverCacheKey, ` +\n        'but no cache was provided to the renderer.'\n      )\n    }\n    if (isDef(getKey) && isUndef(name)) {\n      warnOnce(\n        `[vue-server-renderer] Components that implement \"serverCacheKey\" ` +\n        `must also define a unique \"name\" option.`\n      )\n    }\n    renderComponentInner(node, isRoot, context)\n  }\n}\n\nfunction renderComponentWithCache (node, isRoot, key, context) {\n  const write = context.write\n  write.caching = true\n  const buffer = write.cacheBuffer\n  const bufferIndex = buffer.push('') - 1\n  const componentBuffer = write.componentBuffer\n  componentBuffer.push(new Set())\n  context.renderStates.push({\n    type: 'ComponentWithCache',\n    key,\n    buffer,\n    bufferIndex,\n    componentBuffer\n  })\n  renderComponentInner(node, isRoot, context)\n}\n\nfunction renderComponentInner (node, isRoot, context) {\n  const prevActive = context.activeInstance\n  // expose userContext on vnode\n  node.ssrContext = context.userContext\n  const child = context.activeInstance = createComponentInstanceForVnode(\n    node,\n    context.activeInstance\n  )\n  node.ssrContext = null\n  normalizeRender(child)\n  const childNode = child._render()\n  childNode.parent = node\n  context.renderStates.push({\n    type: 'Component',\n    prevActive\n  })\n  renderNode(childNode, isRoot, context)\n}\n\nfunction renderElement (el, isRoot, context) {\n  if (isTrue(isRoot)) {\n    if (!el.data) el.data = {}\n    if (!el.data.attrs) el.data.attrs = {}\n    el.data.attrs[SSR_ATTR] = 'true'\n  }\n  const startTag = renderStartingTag(el, context)\n  const endTag = `</${el.tag}>`\n  const { write, next } = context\n  if (context.isUnaryTag(el.tag)) {\n    write(startTag, next)\n  } else if (isUndef(el.children) || el.children.length === 0) {\n    write(startTag + endTag, next)\n  } else {\n    const children: Array<VNode> = el.children\n    context.renderStates.push({\n      type: 'Element',\n      rendered: 0,\n      total: children.length,\n      endTag, children\n    })\n    write(startTag, next)\n  }\n}\n\nfunction hasAncestorData (node: VNode) {\n  const parentNode = node.parent\n  return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))\n}\n\nfunction getVShowDirectiveInfo (node: VNode): ?VNodeDirective {\n  let dir: VNodeDirective\n  let tmp\n\n  while (isDef(node)) {\n    if (node.data && node.data.directives) {\n      tmp = node.data.directives.find(dir => dir.name === 'show')\n      if (tmp) {\n        dir = tmp\n      }\n    }\n    node = node.parent\n  }\n  return dir\n}\n\nfunction renderStartingTag (node: VNode, context) {\n  let markup = `<${node.tag}`\n  const { directives, modules } = context\n\n  // construct synthetic data for module processing\n  // because modules like style also produce code by parent VNode data\n  if (isUndef(node.data) && hasAncestorData(node)) {\n    node.data = {}\n  }\n  if (isDef(node.data)) {\n    // check directives\n    const dirs = node.data.directives\n    if (dirs) {\n      for (let i = 0; i < dirs.length; i++) {\n        const name = dirs[i].name\n        const dirRenderer = directives[name]\n        if (dirRenderer && name !== 'show') {\n          // directives mutate the node's data\n          // which then gets rendered by modules\n          dirRenderer(node, dirs[i])\n        }\n      }\n    }\n\n    // v-show directive needs to be merged from parent to child\n    const vshowDirectiveInfo = getVShowDirectiveInfo(node)\n    if (vshowDirectiveInfo) {\n      directives.show(node, vshowDirectiveInfo)\n    }\n\n    // apply other modules\n    for (let i = 0; i < modules.length; i++) {\n      const res = modules[i](node)\n      if (res) {\n        markup += res\n      }\n    }\n  }\n  // attach scoped CSS ID\n  let scopeId\n  const activeInstance = context.activeInstance\n  if (isDef(activeInstance) &&\n      activeInstance !== node.context &&\n      isDef(scopeId = activeInstance.$options._scopeId)) {\n    markup += ` ${(scopeId: any)}`\n  }\n  while (isDef(node)) {\n    if (isDef(scopeId = node.context.$options._scopeId)) {\n      markup += ` ${scopeId}`\n    }\n    node = node.parent\n  }\n  return markup + '>'\n}\n\n/*创建render函数*/\nexport function createRenderFunction (\n  modules: Array<Function>,\n  directives: Object,\n  isUnaryTag: Function,\n  cache: any\n) {\n  return function render (\n    component: Component,\n    write: (text: string, next: Function) => void,\n    userContext: ?Object,\n    done: Function\n  ) {\n    warned = Object.create(null)\n    const context = new RenderContext({\n      activeInstance: component,\n      userContext,\n      write, done, renderNode,\n      isUnaryTag, modules, directives,\n      cache\n    })\n    /*将template模板编译成render函数*/\n    normalizeRender(component)\n    renderNode(component._render(), true, context)\n  }\n}\n"
  },
  {
    "path": "vue-src/server/template-renderer/create-async-file-mapper.js",
    "content": "/* @flow */\n\n/**\n * Creates a mapper that maps components used during a server-side render\n * to async chunk files in the client-side build, so that we can inline them\n * directly in the rendered HTML to avoid waterfall requests.\n */\n\nimport type { ClientManifest } from './index'\n\nexport type AsyncFileMapper = (files: Array<string>) => Array<string>;\n\nexport function createMapper (\n  clientManifest: ClientManifest\n): AsyncFileMapper {\n  const map = createMap(clientManifest)\n  // map server-side moduleIds to client-side files\n  return function mapper (moduleIds: Array<string>): Array<string> {\n    const res = new Set()\n    for (let i = 0; i < moduleIds.length; i++) {\n      const mapped = map.get(moduleIds[i])\n      if (mapped) {\n        for (let j = 0; j < mapped.length; j++) {\n          res.add(mapped[j])\n        }\n      }\n    }\n    return Array.from(res)\n  }\n}\n\nfunction createMap (clientManifest) {\n  const map = new Map()\n  Object.keys(clientManifest.modules).forEach(id => {\n    map.set(id, mapIdToFile(id, clientManifest))\n  })\n  return map\n}\n\nfunction mapIdToFile (id, clientManifest) {\n  const files = []\n  const fileIndices = clientManifest.modules[id]\n  if (fileIndices) {\n    fileIndices.forEach(index => {\n      const file = clientManifest.all[index]\n      // only include async files or non-js assets\n      if (clientManifest.async.indexOf(file) > -1 || !(/\\.js($|\\?)/.test(file))) {\n        files.push(file)\n      }\n    })\n  }\n  return files\n}\n"
  },
  {
    "path": "vue-src/server/template-renderer/index.js",
    "content": "/* @flow */\n\nconst path = require('path')\nconst serialize = require('serialize-javascript')\n\nimport { isJS, isCSS } from '../util'\nimport TemplateStream from './template-stream'\nimport { parseTemplate } from './parse-template'\nimport { createMapper } from './create-async-file-mapper'\nimport type { ParsedTemplate } from './parse-template'\nimport type { AsyncFileMapper } from './create-async-file-mapper'\n\ntype TemplateRendererOptions = {\n  template: ?string;\n  inject?: boolean;\n  clientManifest?: ClientManifest;\n  shouldPreload?: (file: string, type: string) => boolean;\n};\n\nexport type ClientManifest = {\n  publicPath: string;\n  all: Array<string>;\n  initial: Array<string>;\n  async: Array<string>;\n  modules: {\n    [id: string]: Array<number>;\n  },\n  hasNoCssVersion?: {\n    [file: string]: boolean;\n  }\n};\n\ntype PreloadFile = {\n  file: string;\n  extension: string;\n  fileWithoutQuery: string;\n  asType: string;\n};\n\nexport default class TemplateRenderer {\n  options: TemplateRendererOptions;\n  inject: boolean;\n  parsedTemplate: ParsedTemplate | null;\n  publicPath: string;\n  clientManifest: ClientManifest;\n  preloadFiles: Array<string>;\n  prefetchFiles: Array<string>;\n  mapFiles: AsyncFileMapper;\n\n  constructor (options: TemplateRendererOptions) {\n    this.options = options\n    this.inject = options.inject !== false\n    // if no template option is provided, the renderer is created\n    // as a utility object for rendering assets like preload links and scripts.\n    this.parsedTemplate = options.template\n      ? parseTemplate(options.template)\n      : null\n\n    // extra functionality with client manifest\n    if (options.clientManifest) {\n      const clientManifest = this.clientManifest = options.clientManifest\n      this.publicPath = clientManifest.publicPath.replace(/\\/$/, '')\n      // preload/prefetch drectives\n      this.preloadFiles = clientManifest.initial\n      this.prefetchFiles = clientManifest.async\n      // initial async chunk mapping\n      this.mapFiles = createMapper(clientManifest)\n    }\n  }\n\n  bindRenderFns (context: Object) {\n    const renderer: any = this\n    ;['ResourceHints', 'State', 'Scripts', 'Styles'].forEach(type => {\n      context[`render${type}`] = renderer[`render${type}`].bind(renderer, context)\n    })\n    // also expose getPreloadFiles, useful for HTTP/2 push\n    context.getPreloadFiles = renderer.getPreloadFiles.bind(renderer, context)\n  }\n\n  // render synchronously given rendered app content and render context\n  renderSync (content: string, context: ?Object) {\n    const template = this.parsedTemplate\n    if (!template) {\n      throw new Error('renderSync cannot be called without a template.')\n    }\n    context = context || {}\n    if (this.inject) {\n      return (\n        template.head(context) +\n        (context.head || '') +\n        this.renderResourceHints(context) +\n        this.renderStyles(context) +\n        template.neck(context) +\n        content +\n        this.renderState(context) +\n        this.renderScripts(context) +\n        template.tail(context)\n      )\n    } else {\n      return (\n        template.head(context) +\n        template.neck(context) +\n        content +\n        template.tail(context)\n      )\n    }\n  }\n\n  renderStyles (context: Object): string {\n    const cssFiles = this.clientManifest\n      ? this.clientManifest.all.filter(isCSS)\n      : []\n    return (\n      // render links for css files\n      (cssFiles.length\n        ? cssFiles.map(file => `<link rel=\"stylesheet\" href=\"${this.publicPath}/${file}\">`).join('')\n        : '') +\n      // context.styles is a getter exposed by vue-style-loader which contains\n      // the inline component styles collected during SSR\n      (context.styles || '')\n    )\n  }\n\n  renderResourceHints (context: Object): string {\n    return this.renderPreloadLinks(context) + this.renderPrefetchLinks(context)\n  }\n\n  getPreloadFiles (context: Object): Array<PreloadFile> {\n    const usedAsyncFiles = this.getUsedAsyncFiles(context)\n    if (this.preloadFiles || usedAsyncFiles) {\n      return (this.preloadFiles || []).concat(usedAsyncFiles || []).map(file => {\n        const withoutQuery = file.replace(/\\?.*/, '')\n        const extension = path.extname(withoutQuery).slice(1)\n        return {\n          file,\n          extension,\n          fileWithoutQuery: withoutQuery,\n          asType: getPreloadType(extension)\n        }\n      })\n    } else {\n      return []\n    }\n  }\n\n  renderPreloadLinks (context: Object): string {\n    const files = this.getPreloadFiles(context)\n    if (files.length) {\n      return files.map(({ file, extension, fileWithoutQuery, asType }) => {\n        let extra = ''\n        const shouldPreload = this.options.shouldPreload\n        // by default, we only preload scripts or css\n        if (!shouldPreload && asType !== 'script' && asType !== 'style') {\n          return ''\n        }\n        // user wants to explicitly control what to preload\n        if (shouldPreload && !shouldPreload(fileWithoutQuery, asType)) {\n          return ''\n        }\n        if (asType === 'font') {\n          extra = ` type=\"font/${extension}\" crossorigin`\n        }\n        return `<link rel=\"preload\" href=\"${\n          this.publicPath}/${file\n        }\"${\n          asType !== '' ? ` as=\"${asType}\"` : ''\n        }${\n          extra\n        }>`\n      }).join('')\n    } else {\n      return ''\n    }\n  }\n\n  renderPrefetchLinks (context: Object): string {\n    if (this.prefetchFiles) {\n      const usedAsyncFiles = this.getUsedAsyncFiles(context)\n      const alreadyRendered = file => {\n        return usedAsyncFiles && usedAsyncFiles.some(f => f === file)\n      }\n      return this.prefetchFiles.map(file => {\n        if (!alreadyRendered(file)) {\n          return `<link rel=\"prefetch\" href=\"${this.publicPath}/${file}\" as=\"script\">`\n        } else {\n          return ''\n        }\n      }).join('')\n    } else {\n      return ''\n    }\n  }\n\n  renderState (context: Object, options?: Object): string {\n    const {\n      contextKey = 'state',\n      windowKey = '__INITIAL_STATE__'\n    } = options || {}\n    return context[contextKey]\n      ? `<script>window.${windowKey}=${\n          serialize(context[contextKey], { isJSON: true })\n        }</script>`\n      : ''\n  }\n\n  renderScripts (context: Object): string {\n    if (this.clientManifest) {\n      const initial = this.clientManifest.initial\n      const async = this.getUsedAsyncFiles(context)\n      const needed = [initial[0]].concat(async || [], initial.slice(1))\n      return needed.filter(isJS).map(file => {\n        return `<script src=\"${this.publicPath}/${file}\"></script>`\n      }).join('')\n    } else {\n      return ''\n    }\n  }\n\n  getUsedAsyncFiles (context: Object): ?Array<string> {\n    if (!context._mappedfiles && context._registeredComponents && this.mapFiles) {\n      context._mappedFiles = this.mapFiles(Array.from(context._registeredComponents))\n    }\n    return context._mappedFiles\n  }\n\n  // create a transform stream\n  createStream (context: ?Object): TemplateStream {\n    if (!this.parsedTemplate) {\n      throw new Error('createStream cannot be called without a template.')\n    }\n    return new TemplateStream(this, this.parsedTemplate, context || {})\n  }\n}\n\nfunction getPreloadType (ext: string): string {\n  if (ext === 'js') {\n    return 'script'\n  } else if (ext === 'css') {\n    return 'style'\n  } else if (/jpe?g|png|svg|gif|webp|ico/.test(ext)) {\n    return 'image'\n  } else if (/woff2?|ttf|otf|eot/.test(ext)) {\n    return 'font'\n  } else {\n    // not exhausting all possbilities here, but above covers common cases\n    return ''\n  }\n}\n"
  },
  {
    "path": "vue-src/server/template-renderer/parse-template.js",
    "content": "/* @flow */\n\nconst compile = require('lodash.template')\nconst compileOptions = {\n  escape: /{{[^{]([\\s\\S]+?)[^}]}}/g,\n  interpolate: /{{{([\\s\\S]+?)}}}/g\n}\n\nexport type ParsedTemplate = {\n  head: (data: any) => string;\n  neck: (data: any) => string;\n  tail: (data: any) => string;\n};\n\nexport function parseTemplate (\n  template: string,\n  contentPlaceholder?: string = '<!--vue-ssr-outlet-->'\n): ParsedTemplate {\n  if (typeof template === 'object') {\n    return template\n  }\n\n  let i = template.indexOf('</head>')\n  const j = template.indexOf(contentPlaceholder)\n\n  if (j < 0) {\n    throw new Error(`Content placeholder not found in template.`)\n  }\n\n  if (i < 0) {\n    i = template.indexOf('<body>')\n    if (i < 0) {\n      i = j\n    }\n  }\n\n  return {\n    head: compile(template.slice(0, i), compileOptions),\n    neck: compile(template.slice(i, j), compileOptions),\n    tail: compile(template.slice(j + contentPlaceholder.length), compileOptions)\n  }\n}\n"
  },
  {
    "path": "vue-src/server/template-renderer/template-stream.js",
    "content": "/* @flow */\n\nconst Transform = require('stream').Transform\nimport type TemplateRenderer from './index'\nimport type { ParsedTemplate } from './parse-template'\n\nexport default class TemplateStream extends Transform {\n  started: boolean;\n  renderer: TemplateRenderer;\n  template: ParsedTemplate;\n  context: Object;\n  inject: boolean;\n\n  constructor (\n    renderer: TemplateRenderer,\n    template: ParsedTemplate,\n    context: Object\n  ) {\n    super()\n    this.started = false\n    this.renderer = renderer\n    this.template = template\n    this.context = context || {}\n    this.inject = renderer.inject\n  }\n\n  _transform (data: Buffer | string, encoding: string, done: Function) {\n    if (!this.started) {\n      this.emit('beforeStart')\n      this.start()\n    }\n    this.push(data)\n    done()\n  }\n\n  start () {\n    this.started = true\n    this.push(this.template.head(this.context))\n\n    if (this.inject) {\n      // inline server-rendered head meta information\n      if (this.context.head) {\n        this.push(this.context.head)\n      }\n\n      // inline preload/prefetch directives for initial/async chunks\n      const links = this.renderer.renderResourceHints(this.context)\n      if (links) {\n        this.push(links)\n      }\n\n      // CSS files and inline server-rendered CSS collected by vue-style-loader\n      const styles = this.renderer.renderStyles(this.context)\n      if (styles) {\n        this.push(styles)\n      }\n    }\n\n    this.push(this.template.neck(this.context))\n  }\n\n  _flush (done: Function) {\n    this.emit('beforeEnd')\n\n    if (this.inject) {\n      // inline initial store state\n      const state = this.renderer.renderState(this.context)\n      if (state) {\n        this.push(state)\n      }\n\n      // embed scripts needed\n      const scripts = this.renderer.renderScripts(this.context)\n      if (scripts) {\n        this.push(scripts)\n      }\n    }\n\n    this.push(this.template.tail(this.context))\n    done()\n  }\n}\n"
  },
  {
    "path": "vue-src/server/util.js",
    "content": "/* @flow */\n\nexport const isJS = (file: string): boolean => /\\.js(\\?[^.]+)?$/.test(file)\n\nexport const isCSS = (file: string): boolean => /\\.css(\\?[^.]+)?$/.test(file)\n"
  },
  {
    "path": "vue-src/server/webpack-plugin/client.js",
    "content": "const hash = require('hash-sum')\nconst uniq = require('lodash.uniq')\nimport { isJS } from './util'\n\nexport default class VueSSRClientPlugin {\n  constructor (options = {}) {\n    this.options = Object.assign({\n      filename: 'vue-ssr-client-manifest.json'\n    }, options)\n  }\n\n  apply (compiler) {\n    compiler.plugin('emit', (compilation, cb) => {\n      const stats = compilation.getStats().toJson()\n\n      const allFiles = uniq(stats.assets\n        .map(a => a.name))\n\n      const initialFiles = uniq(Object.keys(stats.entrypoints)\n        .map(name => stats.entrypoints[name].assets)\n        .reduce((assets, all) => all.concat(assets), [])\n        .filter(isJS))\n\n      const asyncFiles = allFiles\n        .filter(isJS)\n        .filter(file => initialFiles.indexOf(file) < 0)\n\n      const manifest = {\n        publicPath: stats.publicPath,\n        all: allFiles,\n        initial: initialFiles,\n        async: asyncFiles,\n        modules: { /* [identifier: string]: Array<index: number> */ }\n      }\n\n      const assetModules = stats.modules.filter(m => m.assets.length)\n      const fileToIndex = file => manifest.all.indexOf(file)\n      stats.modules.forEach(m => {\n        // ignore modules duplicated in multiple chunks\n        if (m.chunks.length === 1) {\n          const cid = m.chunks[0]\n          const chunk = stats.chunks.find(c => c.id === cid)\n          if (!chunk || !chunk.files) {\n            return\n          }\n          const files = manifest.modules[hash(m.identifier)] = chunk.files.map(fileToIndex)\n          // find all asset modules associated with the same chunk\n          assetModules.forEach(m => {\n            if (m.chunks.some(id => id === cid)) {\n              files.push.apply(files, m.assets.map(fileToIndex))\n            }\n          })\n        }\n      })\n\n      // const debug = (file, obj) => {\n      //   require('fs').writeFileSync(__dirname + '/' + file, JSON.stringify(obj, null, 2))\n      // }\n      // debug('stats.json', stats)\n      // debug('client-manifest.json', manifest)\n\n      const json = JSON.stringify(manifest, null, 2)\n      compilation.assets[this.options.filename] = {\n        source: () => json,\n        size: () => json.length\n      }\n      cb()\n    })\n  }\n}\n"
  },
  {
    "path": "vue-src/server/webpack-plugin/server.js",
    "content": "import { validate, isJS } from './util'\n\nexport default class VueSSRServerPlugin {\n  constructor (options = {}) {\n    this.options = Object.assign({\n      filename: 'vue-ssr-server-bundle.json'\n    }, options)\n  }\n\n  apply (compiler) {\n    validate(compiler)\n\n    compiler.plugin('emit', (compilation, cb) => {\n      const stats = compilation.getStats().toJson()\n      const entryName = Object.keys(stats.entrypoints)[0]\n      const entryInfo = stats.entrypoints[entryName]\n\n      if (!entryInfo) {\n        // #5553\n        return cb()\n      }\n\n      const entryAssets = entryInfo.assets.filter(isJS)\n\n      if (entryAssets.length > 1) {\n        throw new Error(\n          `Server-side bundle should have one single entry file. ` +\n          `Avoid using CommonsChunkPlugin in the server config.`\n        )\n      }\n\n      const entry = entryAssets[0]\n      if (!entry || typeof entry !== 'string') {\n        throw new Error(\n          `Entry \"${entryName}\" not found. Did you specify the correct entry option?`\n        )\n      }\n\n      const bundle = {\n        entry,\n        files: {},\n        maps: {}\n      }\n\n      stats.assets.forEach(asset => {\n        if (asset.name.match(/\\.js$/)) {\n          bundle.files[asset.name] = compilation.assets[asset.name].source()\n        } else if (asset.name.match(/\\.js\\.map$/)) {\n          bundle.maps[asset.name.replace(/\\.map$/, '')] = JSON.parse(compilation.assets[asset.name].source())\n        }\n        // do not emit anything else for server\n        delete compilation.assets[asset.name]\n      })\n\n      const json = JSON.stringify(bundle, null, 2)\n      const filename = this.options.filename\n\n      compilation.assets[filename] = {\n        source: () => json,\n        size: () => json.length\n      }\n\n      cb()\n    })\n  }\n}\n"
  },
  {
    "path": "vue-src/server/webpack-plugin/util.js",
    "content": "const { red, yellow } = require('chalk')\n\nconst prefix = `[vue-server-renderer-webpack-plugin]`\nconst warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\\n`))\nconst tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\\n`))\n\nexport const validate = compiler => {\n  if (compiler.options.target !== 'node') {\n    warn('webpack config `target` should be \"node\".')\n  }\n\n  if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {\n    warn('webpack config `output.libraryTarget` should be \"commonjs2\".')\n  }\n\n  if (!compiler.options.externals) {\n    tip(\n      'It is recommended to externalize dependencies in the server build for ' +\n      'better build performance.'\n    )\n  }\n}\n\nexport { isJS, isCSS } from '../util'\n"
  },
  {
    "path": "vue-src/server/write.js",
    "content": "/* @flow */\n\nconst MAX_STACK_DEPTH = 1000\n\nexport function createWriteFunction (\n  write: (text: string, next: Function) => boolean,\n  onError: Function\n): Function {\n  let stackDepth = 0\n  const cachedWrite = (text, next) => {\n    if (text && cachedWrite.caching) {\n      cachedWrite.cacheBuffer[cachedWrite.cacheBuffer.length - 1] += text\n    }\n    const waitForNext = write(text, next)\n    if (waitForNext !== true) {\n      if (stackDepth >= MAX_STACK_DEPTH) {\n        process.nextTick(() => {\n          try { next() } catch (e) {\n            onError(e)\n          }\n        })\n      } else {\n        stackDepth++\n        next()\n        stackDepth--\n      }\n    }\n  }\n  cachedWrite.caching = false\n  cachedWrite.cacheBuffer = []\n  cachedWrite.componentBuffer = []\n  return cachedWrite\n}\n"
  },
  {
    "path": "vue-src/sfc/parser.js",
    "content": "/* @flow */\n\nimport deindent from 'de-indent'\nimport { parseHTML } from 'compiler/parser/html-parser'\nimport { makeMap } from 'shared/util'\n\nconst splitRE = /\\r?\\n/g\nconst replaceRE = /./g\nconst isSpecialTag = makeMap('script,style,template', true)\n\ntype Attribute = {\n  name: string,\n  value: string\n};\n\n/**\n * Parse a single-file component (*.vue) file into an SFC Descriptor Object.\n */\nexport function parseComponent (\n  content: string,\n  options?: Object = {}\n ): SFCDescriptor {\n  const sfc: SFCDescriptor = {\n    template: null,\n    script: null,\n    styles: [],\n    customBlocks: []\n  }\n  let depth = 0\n  let currentBlock: ?(SFCBlock | SFCCustomBlock) = null\n\n  function start (\n    tag: string,\n    attrs: Array<Attribute>,\n    unary: boolean,\n    start: number,\n    end: number\n  ) {\n    if (depth === 0) {\n      currentBlock = {\n        type: tag,\n        content: '',\n        start: end,\n        attrs: attrs.reduce((cumulated, { name, value }) => {\n          cumulated[name] = value || true\n          return cumulated\n        }, Object.create(null))\n      }\n      if (isSpecialTag(tag)) {\n        checkAttrs(currentBlock, attrs)\n        if (tag === 'style') {\n          sfc.styles.push(currentBlock)\n        } else {\n          sfc[tag] = currentBlock\n        }\n      } else { // custom blocks\n        sfc.customBlocks.push(currentBlock)\n      }\n    }\n    if (!unary) {\n      depth++\n    }\n  }\n\n  function checkAttrs (block: SFCBlock, attrs: Array<Attribute>) {\n    for (let i = 0; i < attrs.length; i++) {\n      const attr = attrs[i]\n      if (attr.name === 'lang') {\n        block.lang = attr.value\n      }\n      if (attr.name === 'scoped') {\n        block.scoped = true\n      }\n      if (attr.name === 'module') {\n        block.module = attr.value || true\n      }\n      if (attr.name === 'src') {\n        block.src = attr.value\n      }\n    }\n  }\n\n  function end (tag: string, start: number, end: number) {\n    if (depth === 1 && currentBlock) {\n      currentBlock.end = start\n      let text = deindent(content.slice(currentBlock.start, currentBlock.end))\n      // pad content so that linters and pre-processors can output correct\n      // line numbers in errors and warnings\n      if (currentBlock.type !== 'template' && options.pad) {\n        text = padContent(currentBlock, options.pad) + text\n      }\n      currentBlock.content = text\n      currentBlock = null\n    }\n    depth--\n  }\n\n  function padContent (block: SFCBlock | SFCCustomBlock, pad: true | \"line\" | \"space\") {\n    if (pad === 'space') {\n      return content.slice(0, block.start).replace(replaceRE, ' ')\n    } else {\n      const offset = content.slice(0, block.start).split(splitRE).length\n      const padChar = block.type === 'script' && !block.lang\n        ? '//\\n'\n        : '\\n'\n      return Array(offset).join(padChar)\n    }\n  }\n\n  parseHTML(content, {\n    start,\n    end\n  })\n\n  return sfc\n}\n"
  },
  {
    "path": "vue-src/shared/constants.js",
    "content": "/*用来标记是否是服务端渲染*/\nexport const SSR_ATTR = 'data-server-rendered'\n\n/*选项／资源集合*/\nexport const ASSET_TYPES = [\n  'component',\n  'directive',\n  'filter'\n]\n\n/*钩子函数集合*/\nexport const LIFECYCLE_HOOKS = [\n  'beforeCreate',\n  'created',\n  'beforeMount',\n  'mounted',\n  'beforeUpdate',\n  'updated',\n  'beforeDestroy',\n  'destroyed',\n  'activated',\n  'deactivated'\n]\n"
  },
  {
    "path": "vue-src/shared/util.js",
    "content": "/* @flow */\n\n// these helpers produces better vm code in JS engines due to their\n// explicitness and function inlining\nexport function isUndef (v: any): boolean %checks {\n  return v === undefined || v === null\n}\n\nexport function isDef (v: any): boolean %checks {\n  return v !== undefined && v !== null\n}\n\nexport function isTrue (v: any): boolean %checks {\n  return v === true\n}\n\n/**\n * Check if value is primitive\n */\nexport function isPrimitive (value: any): boolean %checks {\n  return typeof value === 'string' || typeof value === 'number'\n}\n\n/**\n * Quick object check - this is primarily used to tell\n * Objects from primitive values when we know the value\n * is a JSON-compliant type.\n */\nexport function isObject (obj: mixed): boolean %checks {\n  return obj !== null && typeof obj === 'object'\n}\n\nconst _toString = Object.prototype.toString\n\n/**\n * Strict object type check. Only returns true\n * for plain JavaScript objects.\n */\n /*对对象类型进行严格检查，只有当对象是纯javascript对象的时候返回true*/\nexport function isPlainObject (obj: any): boolean {\n  return _toString.call(obj) === '[object Object]'\n}\n\nexport function isRegExp (v: any): boolean {\n  return _toString.call(v) === '[object RegExp]'\n}\n\n/**\n * Convert a value to a string that is actually rendered.\n */\n /*将val转化成字符串*/\nexport function toString (val: any): string {\n  return val == null\n    ? ''\n    : typeof val === 'object'\n      ? JSON.stringify(val, null, 2)\n      : String(val)\n}\n\n/**\n * Convert a input value to a number for persistence.\n * If the conversion fails, return original string.\n */\n /*将字符串转化为数字，如果转换失败会返回原字符串*/\nexport function toNumber (val: string): number | string {\n  const n = parseFloat(val)\n  return isNaN(n) ? val : n\n}\n\n/**\n * Make a map and return a function for checking if a key\n * is in that map.\n */\n /*\n返回一个函数用以检测是否一个key值存在这个函数中\n比如str = \"a, b, c\"\n则返回 (key) => {\n  return map[key];\n}\nmap为{\n  a: true,\n  b: true,\n  c: true\n}\n存在expectsLowerCase参数的时候会将所有的参数转化成小写\n */\n}\n}\nexport function makeMap (\n  str: string,\n  expectsLowerCase?: boolean\n): (key: string) => true | void {\n  const map = Object.create(null)\n  const list: Array<string> = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase\n    ? val => map[val.toLowerCase()]\n    : val => map[val]\n}\n\n/**\n * Check if a tag is a built-in tag.\n */\nexport const isBuiltInTag = makeMap('slot,component', true)\n\n/**\n * Remove an item from an array\n */\nexport function remove (arr: Array<any>, item: any): Array<any> | void {\n  if (arr.length) {\n    const index = arr.indexOf(item)\n    if (index > -1) {\n      return arr.splice(index, 1)\n    }\n  }\n}\n\n/**\n * Check whether the object has the property.\n */\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nexport function hasOwn (obj: Object | Array<*>, key: string): boolean {\n  return hasOwnProperty.call(obj, key)\n}\n\n/**\n * Create a cached version of a pure function.\n */\n /*根据str得到fn(str)的结果，但是这个结果会被闭包中的cache缓存起来，下一次如果是同样的str则不需要经过fn(str)重新计算，而是直接得到结果*/\nexport function cached<F: Function> (fn: F): F {\n  const cache = Object.create(null)\n  return (function cachedFn (str: string) {\n    const hit = cache[str]\n    return hit || (cache[str] = fn(str))\n  }: any)\n}\n\n/**\n * Camelize a hyphen-delimited string.\n */\n /*将原本用-连接的字符串变成驼峰 aaa-bbb-ccc => aaaBbbCcc*/\nconst camelizeRE = /-(\\w)/g\nexport const camelize = cached((str: string): string => {\n  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')\n})\n\n/**\n * Capitalize a string.\n */\n /*首字母转大写*/\nexport const capitalize = cached((str: string): string => {\n  return str.charAt(0).toUpperCase() + str.slice(1)\n})\n\n/**\n * Hyphenate a camelCase string.\n */\n /*连接一个camelCase字符串。*/\nconst hyphenateRE = /([^-])([A-Z])/g\nexport const hyphenate = cached((str: string): string => {\n  return str\n    .replace(hyphenateRE, '$1-$2')\n    .replace(hyphenateRE, '$1-$2')\n    .toLowerCase()\n})\n\n/**\n * Simple bind, faster than native\n */\nexport function bind (fn: Function, ctx: Object): Function {\n  function boundFn (a) {\n    const l: number = arguments.length\n    return l\n      ? l > 1\n        ? fn.apply(ctx, arguments)\n        : fn.call(ctx, a)\n      : fn.call(ctx)\n  }\n  // record original fn length\n  boundFn._length = fn.length\n  return boundFn\n}\n\n/**\n * Convert an Array-like object to a real Array.\n */\n /*将类数组的对象转换成数组*/\nexport function toArray (list: any, start?: number): Array<any> {\n  start = start || 0\n  let i = list.length - start\n  const ret: Array<any> = new Array(i)\n  while (i--) {\n    ret[i] = list[i + start]\n  }\n  return ret\n}\n\n/**\n * Mix properties into target object.\n */\n /*将_from的属性混合（会覆盖）to对象中*/\nexport function extend (to: Object, _from: ?Object): Object {\n  for (const key in _from) {\n    to[key] = _from[key]\n  }\n  return to\n}\n\n/**\n * Merge an Array of Objects into a single Object.\n */\n /*合并Array数组中的每一个对象到一个新的Object中*/\nexport function toObject (arr: Array<any>): Object {\n  const res = {}\n  for (let i = 0; i < arr.length; i++) {\n    if (arr[i]) {\n      extend(res, arr[i])\n    }\n  }\n  return res\n}\n\n/**\n * Perform no operation.\n */\nexport function noop () {}\n\n/**\n * Always return false.\n */\nexport const no = () => false\n\n/**\n * Return same value\n */\nexport const identity = (_: any) => _\n\n/**\n * Generate a static keys string from compiler modules.\n */\nexport function genStaticKeys (modules: Array<ModuleOptions>): string {\n  return modules.reduce((keys, m) => {\n    return keys.concat(m.staticKeys || [])\n  }, []).join(',')\n}\n\n/**\n * Check if two values are loosely equal - that is,\n * if they are plain objects, do they have the same shape?\n */\n /*检测两个变量是否相等*/\nexport function looseEqual (a: mixed, b: mixed): boolean {\n  const isObjectA = isObject(a)\n  const isObjectB = isObject(b)\n  if (isObjectA && isObjectB) {\n    try {\n      return JSON.stringify(a) === JSON.stringify(b)\n    } catch (e) {\n      // possible circular reference\n      return a === b\n    }\n  } else if (!isObjectA && !isObjectB) {\n    return String(a) === String(b)\n  } else {\n    return false\n  }\n}\n\n/*检测arr数组中是否包含与val变量相等的项*/\nexport function looseIndexOf (arr: Array<mixed>, val: mixed): number {\n  for (let i = 0; i < arr.length; i++) {\n    if (looseEqual(arr[i], val)) return i\n  }\n  return -1\n}\n\n/**\n * Ensure a function is called only once.\n */\nexport function once (fn: Function): Function {\n  let called = false\n  return function () {\n    if (!called) {\n      called = true\n      fn.apply(this, arguments)\n    }\n  }\n}\n"
  },
  {
    "path": "vuex-src/helpers.js",
    "content": "/* https://vuex.vuejs.org/zh-cn/state.html#mapstate-辅助函数 */\nexport const mapState = normalizeNamespace((namespace, states) => {\n  const res = {}\n  normalizeMap(states).forEach(({ key, val }) => {\n    res[key] = function mappedState () {\n      let state = this.$store.state\n      let getters = this.$store.getters\n      /* 处理namespace */\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapState', namespace)\n        if (!module) {\n          return\n        }\n        state = module.context.state\n        getters = module.context.getters\n      }\n      /* \n        如果val是一个函数，则返回函数的调用，否则从state里找出这个val对应的属性 \n        举个例子：\n          mapState({\n            test,\n            test2: state => {\n              return state.a + state.b\n            }\n          })\n          得到\n          {\n            test () {\n              return this.$store.state.test;\n            },\n            test2 (state, getters) {\n              return state.a + state.b;\n            }\n          }\n      */\n      return typeof val === 'function'\n        ? val.call(this, state, getters)\n        : state[val]\n    }\n    // mark vuex getter for devtools\n    res[key].vuex = true\n  })\n  return res\n})\n\n/* https://vuex.vuejs.org/zh-cn/mutations.html#在组件中提交-mutation */\nexport const mapMutations = normalizeNamespace((namespace, mutations) => {\n  const res = {}\n  normalizeMap(mutations).forEach(({ key, val }) => {\n    res[key] = function mappedMutation (...args) {\n      let commit = this.$store.commit\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)\n        if (!module) {\n          return\n        }\n        commit = module.context.commit\n      }\n      return typeof val === 'function'\n        ? val.apply(this, [commit].concat(args))\n        : commit.apply(this.$store, [val].concat(args))\n    }\n  })\n  return res\n})\n\n/* https://vuex.vuejs.org/zh-cn/getters.html#mapgetters-辅助函数 */\nexport const mapGetters = normalizeNamespace((namespace, getters) => {\n  const res = {}\n  normalizeMap(getters).forEach(({ key, val }) => {\n    val = namespace + val\n    res[key] = function mappedGetter () {\n      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {\n        return\n      }\n      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {\n        console.error(`[vuex] unknown getter: ${val}`)\n        return\n      }\n      return this.$store.getters[val]\n    }\n    // mark vuex getter for devtools\n    res[key].vuex = true\n  })\n  return res\n})\n\n/* https://vuex.vuejs.org/zh-cn/actions.html#在组件中分发-action */\nexport const mapActions = normalizeNamespace((namespace, actions) => {\n  const res = {}\n  normalizeMap(actions).forEach(({ key, val }) => {\n    res[key] = function mappedAction (...args) {\n      let dispatch = this.$store.dispatch\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)\n        if (!module) {\n          return\n        }\n        dispatch = module.context.dispatch\n      }\n      return typeof val === 'function'\n        ? val.apply(this, [dispatch].concat(args))\n        : dispatch.apply(this.$store, [val].concat(args))\n    }\n  })\n  return res\n})\n\nexport const createNamespacedHelpers = (namespace) => ({\n  mapState: mapState.bind(null, namespace),\n  mapGetters: mapGetters.bind(null, namespace),\n  mapMutations: mapMutations.bind(null, namespace),\n  mapActions: mapActions.bind(null, namespace)\n})\n\n/* 将map转化成[{key, val},{key, val},{key, val}...]的数据结构 */\nfunction normalizeMap (map) {\n  return Array.isArray(map)\n    ? map.map(key => ({ key, val: key }))\n    : Object.keys(map).map(key => ({ key, val: map[key] }))\n}\n\nfunction normalizeNamespace (fn) {\n  return (namespace, map) => {\n    if (typeof namespace !== 'string') {\n      /* 兼容namespace不传的情况 */\n      map = namespace\n      namespace = ''\n    } else if (namespace.charAt(namespace.length - 1) !== '/') {\n      /* namespace最后一位补上'/' */\n      namespace += '/'\n    }\n    return fn(namespace, map)\n  }\n}\n\n/* 根据namespace获取module */\nfunction getModuleByNamespace (store, helper, namespace) {\n  const module = store._modulesNamespaceMap[namespace]\n  /* 不存在打印err */\n  if (process.env.NODE_ENV !== 'production' && !module) {\n    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)\n  }\n  return module\n}\n"
  },
  {
    "path": "vuex-src/index.esm.js",
    "content": "import { Store, install } from './store'\nimport { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'\n\nexport default {\n  Store,\n  install,\n  version: '__VERSION__',\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n\nexport {\n  Store,\n  install,\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n"
  },
  {
    "path": "vuex-src/index.js",
    "content": "import { Store, install } from './store'\nimport { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'\n\nexport default {\n  Store,\n  install,\n  version: '__VERSION__',\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n"
  },
  {
    "path": "vuex-src/mixin.js",
    "content": "export default function (Vue) {\n  /*获取Vue版本，鉴别Vue1.0还是Vue2.0*/\n  const version = Number(Vue.version.split('.')[0])\n\n  if (version >= 2) {\n    /*通过mixin将vuexInit混淆到Vue实例的beforeCreate钩子中*/\n    Vue.mixin({ beforeCreate: vuexInit })\n  } else {\n    // override init and inject vuex init procedure\n    // for 1.x backwards compatibility.\n    /*将vuexInit放入_init中调用*/\n    const _init = Vue.prototype._init\n    Vue.prototype._init = function (options = {}) {\n      options.init = options.init\n        ? [vuexInit].concat(options.init)\n        : vuexInit\n      _init.call(this, options)\n    }\n  }\n\n  /**\n   * Vuex init hook, injected into each instances init hooks list.\n   */\n   /*Vuex的init钩子，会存入每一个Vue实例等钩子列表*/\n  function vuexInit () {\n    const options = this.$options\n    // store injection\n    if (options.store) {\n      /*存在store其实代表的就是Root节点，直接执行store（function时）或者使用store（非function）*/\n      this.$store = typeof options.store === 'function'\n        ? options.store()\n        : options.store\n    } else if (options.parent && options.parent.$store) {\n      /*子组件直接从父组件中获取$store，这样就保证了所有组件都公用了全局的同一份store*/\n      this.$store = options.parent.$store\n    }\n  }\n}\n"
  },
  {
    "path": "vuex-src/module/module-collection.js",
    "content": "import Module from './module'\nimport { assert, forEachValue } from '../util'\n\n/*module收集类*/\nexport default class ModuleCollection {\n  constructor (rawRootModule) {\n    // register root module (Vuex.Store options)\n    this.register([], rawRootModule, false)\n  }\n\n  /*获取父级module*/\n  get (path) {\n    return path.reduce((module, key) => {\n      return module.getChild(key)\n    }, this.root)\n  }\n\n  /*\n    获取namespace，当namespaced为true的时候会返回'moduleName/name'\n    默认情况下，模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。\n    如果希望你的模块更加自包含或提高可重用性，你可以通过添加 namespaced: true 的方式使其成为命名空间模块。\n    当模块被注册后，它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。\n  */\n  getNamespace (path) {\n    let module = this.root\n    return path.reduce((namespace, key) => {\n      module = module.getChild(key)\n      return namespace + (module.namespaced ? key + '/' : '')\n    }, '')\n  }\n\n  update (rawRootModule) {\n    update([], this.root, rawRootModule)\n  }\n\n  /*注册*/\n  register (path, rawModule, runtime = true) {\n    if (process.env.NODE_ENV !== 'production') {\n      assertRawModule(path, rawModule)\n    }\n\n    /*新建一个Module对象*/\n    const newModule = new Module(rawModule, runtime)\n    if (path.length === 0) {\n      /*path为空数组的代表跟节点*/\n      this.root = newModule\n    } else {\n      /*获取父级module*/\n      const parent = this.get(path.slice(0, -1))\n      /*在父module中插入一个子module*/\n      parent.addChild(path[path.length - 1], newModule)\n    }\n\n    // register nested modules\n    /*递归注册module*/\n    if (rawModule.modules) {\n      forEachValue(rawModule.modules, (rawChildModule, key) => {\n        this.register(path.concat(key), rawChildModule, runtime)\n      })\n    }\n  }\n\n  /*注销*/\n  unregister (path) {\n    const parent = this.get(path.slice(0, -1))\n    const key = path[path.length - 1]\n    if (!parent.getChild(key).runtime) return\n\n    parent.removeChild(key)\n  }\n}\n\n/* 更新 */\nfunction update (path, targetModule, newModule) {\n  if (process.env.NODE_ENV !== 'production') {\n    assertRawModule(path, newModule)\n  }\n\n  // update target module\n  /* 更新module */\n  targetModule.update(newModule)\n\n  // update nested modules\n  /* 更新嵌套的module */\n  if (newModule.modules) {\n    for (const key in newModule.modules) {\n      if (!targetModule.getChild(key)) {\n        if (process.env.NODE_ENV !== 'production') {\n          console.warn(\n            `[vuex] trying to add a new module '${key}' on hot reloading, ` +\n            'manual reload is needed'\n          )\n        }\n        return\n      }\n      update(\n        path.concat(key),\n        targetModule.getChild(key),\n        newModule.modules[key]\n      )\n    }\n  }\n}\n\nfunction assertRawModule (path, rawModule) {\n  ['getters', 'actions', 'mutations'].forEach(key => {\n    if (!rawModule[key]) return\n\n    forEachValue(rawModule[key], (value, type) => {\n      assert(\n        typeof value === 'function',\n        makeAssertionMessage(path, key, type, value)\n      )\n    })\n  })\n}\n\nfunction makeAssertionMessage (path, key, type, value) {\n  let buf = `${key} should be function but \"${key}.${type}\"`\n  if (path.length > 0) {\n    buf += ` in module \"${path.join('.')}\"`\n  }\n  buf += ` is ${JSON.stringify(value)}.`\n\n  return buf\n}\n"
  },
  {
    "path": "vuex-src/module/module.js",
    "content": "import { forEachValue } from '../util'\n\n/*Module构造类*/\nexport default class Module {\n  constructor (rawModule, runtime) {\n    this.runtime = runtime\n    this._children = Object.create(null)\n    /*保存module*/\n    this._rawModule = rawModule\n    /*保存modele的state*/\n    const rawState = rawModule.state\n    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}\n  }\n\n  /* 获取namespace */\n  get namespaced () {\n    return !!this._rawModule.namespaced\n  }\n\n  /*插入一个子module，存入_children中*/\n  addChild (key, module) {\n    this._children[key] = module\n  }\n\n  /*移除一个子module*/\n  removeChild (key) {\n    delete this._children[key]\n  }\n\n  /*根据key获取子module*/\n  getChild (key) {\n    return this._children[key]\n  }\n\n  /* 更新module */\n  update (rawModule) {\n    this._rawModule.namespaced = rawModule.namespaced\n    if (rawModule.actions) {\n      this._rawModule.actions = rawModule.actions\n    }\n    if (rawModule.mutations) {\n      this._rawModule.mutations = rawModule.mutations\n    }\n    if (rawModule.getters) {\n      this._rawModule.getters = rawModule.getters\n    }\n  }\n\n  /* 遍历child  */\n  forEachChild (fn) {\n    forEachValue(this._children, fn)\n  }\n\n  /* 遍历getter */\n  forEachGetter (fn) {\n    if (this._rawModule.getters) {\n      forEachValue(this._rawModule.getters, fn)\n    }\n  }\n\n  /* 遍历action */\n  forEachAction (fn) {\n    if (this._rawModule.actions) {\n      forEachValue(this._rawModule.actions, fn)\n    }\n  }\n\n  /* 遍历matation */\n  forEachMutation (fn) {\n    if (this._rawModule.mutations) {\n      forEachValue(this._rawModule.mutations, fn)\n    }\n  }\n}\n"
  },
  {
    "path": "vuex-src/plugins/devtool.js",
    "content": "/* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件 */\nconst devtoolHook =\n  typeof window !== 'undefined' &&\n  window.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\nexport default function devtoolPlugin (store) {\n  if (!devtoolHook) return\n\n  /* devtoll插件实例存储在store的_devtoolHook上 */\n  store._devtoolHook = devtoolHook\n\n  /* 出发vuex的初始化事件，并将store的引用地址传给deltool插件，使插件获取store的实例 */\n  devtoolHook.emit('vuex:init', store)\n\n  /* 监听travel-to-state事件 */\n  devtoolHook.on('vuex:travel-to-state', targetState => {\n    /* 重制state */\n    store.replaceState(targetState)\n  })\n\n  /* 订阅store的变化 */\n  store.subscribe((mutation, state) => {\n    devtoolHook.emit('vuex:mutation', mutation, state)\n  })\n}\n"
  },
  {
    "path": "vuex-src/plugins/logger.js",
    "content": "// Credits: borrowed code from fcomb/redux-logger\n\nimport { deepCopy } from '../util'\n\nexport default function createLogger ({\n  collapsed = true,\n  filter = (mutation, stateBefore, stateAfter) => true,\n  transformer = state => state,\n  mutationTransformer = mut => mut\n} = {}) {\n  return store => {\n    let prevState = deepCopy(store.state)\n\n    store.subscribe((mutation, state) => {\n      if (typeof console === 'undefined') {\n        return\n      }\n      const nextState = deepCopy(state)\n\n      if (filter(mutation, prevState, nextState)) {\n        const time = new Date()\n        const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`\n        const formattedMutation = mutationTransformer(mutation)\n        const message = `mutation ${mutation.type}${formattedTime}`\n        const startMessage = collapsed\n          ? console.groupCollapsed\n          : console.group\n\n        // render\n        try {\n          startMessage.call(console, message)\n        } catch (e) {\n          console.log(message)\n        }\n\n        console.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))\n        console.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)\n        console.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))\n\n        try {\n          console.groupEnd()\n        } catch (e) {\n          console.log('—— log end ——')\n        }\n      }\n\n      prevState = nextState\n    })\n  }\n}\n\nfunction repeat (str, times) {\n  return (new Array(times + 1)).join(str)\n}\n\nfunction pad (num, maxLength) {\n  return repeat('0', maxLength - num.toString().length) + num\n}\n"
  },
  {
    "path": "vuex-src/store.js",
    "content": "import applyMixin from './mixin'\nimport devtoolPlugin from './plugins/devtool'\nimport ModuleCollection from './module/module-collection'\nimport { forEachValue, isObject, isPromise, assert } from './util'\n\nlet Vue // bind on install\n\n/*Store构造类*/\nexport class Store {\n  constructor (options = {}) {\n    // Auto install if it is not done yet and `window` has `Vue`.\n    // To allow users to avoid auto-installation in some cases,\n    // this code should be placed here. See #731\n    /*\n      在浏览器环境下，如果插件还未安装（!Vue即判断是否未安装），则它会自动安装。\n      它允许用户在某些情况下避免自动安装。\n    */\n    if (!Vue && typeof window !== 'undefined' && window.Vue) {\n      install(window.Vue)\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)\n      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)\n      assert(this instanceof Store, `Store must be called with the new operator.`)\n    }\n\n    const {\n      /*一个数组，包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数，可以监听 mutation（用于外部地数据持久化、记录或调试）或者提交 mutation （用于内部数据，例如 websocket 或 某些观察者）*/\n      plugins = [],\n      /*使 Vuex store 进入严格模式，在严格模式下，任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。*/\n      strict = false\n    } = options\n\n    /*从option中取出state，如果state是function则执行，最终得到一个对象*/\n    let {\n      state = {}\n    } = options\n    if (typeof state === 'function') {\n      state = state()\n    }\n\n    // store internal state\n    /* 用来判断严格模式下是否是用mutation修改state的 */\n    this._committing = false\n    /* 存放action */\n    this._actions = Object.create(null)\n    /* 存放mutation */\n    this._mutations = Object.create(null)\n    /* 存放getter */\n    this._wrappedGetters = Object.create(null)\n    /* module收集器 */\n    this._modules = new ModuleCollection(options)\n    /* 根据namespace存放module */\n    this._modulesNamespaceMap = Object.create(null)\n    /* 存放订阅者 */\n    this._subscribers = []\n    /* 用以实现Watch的Vue实例 */\n    this._watcherVM = new Vue()\n\n    // bind commit and dispatch to self\n    /*将dispatch与commit调用的this绑定为store对象本身，否则在组件内部this.dispatch时的this会指向组件的vm*/\n    const store = this\n    const { dispatch, commit } = this\n    /* 为dispatch与commit绑定this（Store实例本身） */\n    this.dispatch = function boundDispatch (type, payload) {\n      return dispatch.call(store, type, payload)\n    }\n    this.commit = function boundCommit (type, payload, options) {\n      return commit.call(store, type, payload, options)\n    }\n\n    // strict mode\n    /*严格模式(使 Vuex store 进入严格模式，在严格模式下，任何 mutation 处理函数以外修改 Vuex state 都会抛出错误)*/\n    this.strict = strict\n\n    // init root module.\n    // this also recursively registers all sub-modules\n    // and collects all module getters inside this._wrappedGetters\n    /*初始化根module，这也同时递归注册了所有子modle，收集所有module的getter到_wrappedGetters中去，this._modules.root代表根module才独有保存的Module对象*/\n    installModule(this, state, [], this._modules.root)\n\n    // initialize the store vm, which is responsible for the reactivity\n    // (also registers _wrappedGetters as computed properties)\n    /* 通过vm重设store，新建Vue对象使用Vue内部的响应式实现注册state以及computed */\n    resetStoreVM(this, state)\n\n    // apply plugins\n    /* 调用插件 */\n    plugins.forEach(plugin => plugin(this))\n\n    /* devtool插件 */\n    if (Vue.config.devtools) {\n      devtoolPlugin(this)\n    }\n  }\n\n  get state () {\n    return this._vm._data.$$state\n  }\n\n  set state (v) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert(false, `Use store.replaceState() to explicit replace store state.`)\n    }\n  }\n\n  /* 调用mutation的commit方法 */\n  commit (_type, _payload, _options) {\n    // check object-style commit\n    /* 校验参数 */\n    const {\n      type,\n      payload,\n      options\n    } = unifyObjectStyle(_type, _payload, _options)\n\n    const mutation = { type, payload }\n    /* 取出type对应的mutation的方法 */\n    const entry = this._mutations[type]\n    if (!entry) {\n      if (process.env.NODE_ENV !== 'production') {\n        console.error(`[vuex] unknown mutation type: ${type}`)\n      }\n      return\n    }\n    /* 执行mutation中的所有方法 */\n    this._withCommit(() => {\n      entry.forEach(function commitIterator (handler) {\n        handler(payload)\n      })\n    })\n    /* 通知所有订阅者 */\n    this._subscribers.forEach(sub => sub(mutation, this.state))\n\n    if (\n      process.env.NODE_ENV !== 'production' &&\n      options && options.silent\n    ) {\n      console.warn(\n        `[vuex] mutation type: ${type}. Silent option has been removed. ` +\n        'Use the filter functionality in the vue-devtools'\n      )\n    }\n  }\n\n  /* 调用action的dispatch方法 */\n  dispatch (_type, _payload) {\n    // check object-style dispatch\n    const {\n      type,\n      payload\n    } = unifyObjectStyle(_type, _payload)\n\n    /* actions中取出type对应的ation */\n    const entry = this._actions[type]\n    if (!entry) {\n      if (process.env.NODE_ENV !== 'production') {\n        console.error(`[vuex] unknown action type: ${type}`)\n      }\n      return\n    }\n\n    /* 是数组则包装Promise形成一个新的Promise，只有一个则直接返回第0个 */\n    return entry.length > 1\n      ? Promise.all(entry.map(handler => handler(payload)))\n      : entry[0](payload)\n  }\n\n  /* 注册一个订阅函数，返回取消订阅的函数 */\n  subscribe (fn) {\n    const subs = this._subscribers\n    if (subs.indexOf(fn) < 0) {\n      subs.push(fn)\n    }\n    return () => {\n      const i = subs.indexOf(fn)\n      if (i > -1) {\n        subs.splice(i, 1)\n      }\n    }\n  }\n\n  /* 观察一个getter方法 */\n  watch (getter, cb, options) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert(typeof getter === 'function', `store.watch only accepts a function.`)\n    }\n    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)\n  }\n\n  /* 重置state */\n  replaceState (state) {\n    this._withCommit(() => {\n      this._vm._data.$$state = state\n    })\n  }\n\n  /* 注册一个动态module，当业务进行异步加载的时候，可以通过该接口进行注册动态module */\n  registerModule (path, rawModule) {\n    /* 转化称Array */\n    if (typeof path === 'string') path = [path]\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Array.isArray(path), `module path must be a string or an Array.`)\n      assert(path.length > 0, 'cannot register the root module by using registerModule.')\n    }\n\n    /*注册*/\n    this._modules.register(path, rawModule)\n    /*初始化module*/\n    installModule(this, this.state, path, this._modules.get(path))\n    // reset store to update getters...\n    /* 通过vm重设store，新建Vue对象使用Vue内部的响应式实现注册state以及computed */\n    resetStoreVM(this, this.state)\n  }\n\n  /* 注销一个动态module */\n  unregisterModule (path) {\n    /* 转化称Array */\n    if (typeof path === 'string') path = [path]\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Array.isArray(path), `module path must be a string or an Array.`)\n    }\n\n    /*注销*/\n    this._modules.unregister(path)\n    this._withCommit(() => {\n      /* 获取父级的state */\n      const parentState = getNestedState(this.state, path.slice(0, -1))\n      /* 从父级中删除 */\n      Vue.delete(parentState, path[path.length - 1])\n    })\n    /* 重制store */\n    resetStore(this)\n  }\n\n  /* 热更新 */\n  hotUpdate (newOptions) {\n    /* 更新module */\n    this._modules.update(newOptions)\n    /* 重制store */\n    resetStore(this, true)\n  }\n\n  /* 保证通过mutation修改store的数据 */\n  _withCommit (fn) {\n    /* 调用withCommit修改state的值时会将store的committing值置为true，内部会有断言检查该值，在严格模式下只允许使用mutation来修改store中的值，而不允许直接修改store的数值 */\n    const committing = this._committing\n    this._committing = true\n    fn()\n    this._committing = committing\n  }\n}\n\n/* 重制store */\nfunction resetStore (store, hot) {\n  store._actions = Object.create(null)\n  store._mutations = Object.create(null)\n  store._wrappedGetters = Object.create(null)\n  store._modulesNamespaceMap = Object.create(null)\n  const state = store.state\n  // init all modules\n  installModule(store, state, [], store._modules.root, true)\n  // reset vm\n  resetStoreVM(store, state, hot)\n}\n\n/* 通过vm重设store，新建Vue对象使用Vue内部的响应式实现注册state以及computed */\nfunction resetStoreVM (store, state, hot) {\n  /* 存放之前的vm对象 */\n  const oldVm = store._vm \n\n  // bind store public getters\n  store.getters = {}\n  const wrappedGetters = store._wrappedGetters\n  const computed = {}\n\n  /* 通过Object.defineProperty为每一个getter方法设置get方法，比如获取this.$store.getters.test的时候获取的是store._vm.test，也就是Vue对象的computed属性 */\n  forEachValue(wrappedGetters, (fn, key) => {\n    // use computed to leverage its lazy-caching mechanism\n    computed[key] = () => fn(store)\n    Object.defineProperty(store.getters, key, {\n      get: () => store._vm[key],\n      enumerable: true // for local getters\n    })\n  })\n\n  // use a Vue instance to store the state tree\n  // suppress warnings just in case the user has added\n  // some funky global mixins\n  const silent = Vue.config.silent\n  /* Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程中不会报出一切警告 */\n  Vue.config.silent = true\n  /*  这里new了一个Vue对象，运用Vue内部的响应式实现注册state以及computed*/\n  store._vm = new Vue({\n    data: {\n      $$state: state\n    },\n    computed\n  })\n  Vue.config.silent = silent\n\n  // enable strict mode for new vm\n  /* 使能严格模式，保证修改store只能通过mutation */\n  if (store.strict) {\n    enableStrictMode(store)\n  }\n\n  if (oldVm) {\n    /* 解除旧vm的state的引用，以及销毁旧的Vue对象 */\n    if (hot) {\n      // dispatch changes in all subscribed watchers\n      // to force getter re-evaluation for hot reloading.\n      store._withCommit(() => {\n        oldVm._data.$$state = null\n      })\n    }\n    Vue.nextTick(() => oldVm.$destroy())\n  }\n}\n\n/*初始化module*/\nfunction installModule (store, rootState, path, module, hot) {\n  /* 是否是根module */\n  const isRoot = !path.length\n  /* 获取module的namespace */\n  const namespace = store._modules.getNamespace(path)\n\n  // register in namespace map\n  /* 如果有namespace则在_modulesNamespaceMap中注册 */\n  if (module.namespaced) {\n    store._modulesNamespaceMap[namespace] = module\n  }\n\n  // set state\n  if (!isRoot && !hot) {\n    /* 获取父级的state */\n    const parentState = getNestedState(rootState, path.slice(0, -1))\n    /* module的name */\n    const moduleName = path[path.length - 1]\n    store._withCommit(() => {\n      /* 将子module设置称响应式的 */\n      Vue.set(parentState, moduleName, module.state)\n    })\n  }\n\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  /* 遍历注册mutation */\n  module.forEachMutation((mutation, key) => {\n    const namespacedType = namespace + key\n    registerMutation(store, namespacedType, mutation, local)\n  })\n\n  /* 遍历注册action */\n  module.forEachAction((action, key) => {\n    const namespacedType = namespace + key\n    registerAction(store, namespacedType, action, local)\n  })\n\n  /* 遍历注册getter */\n  module.forEachGetter((getter, key) => {\n    const namespacedType = namespace + key\n    registerGetter(store, namespacedType, getter, local)\n  })\n\n  /* 递归安装mudule */\n  module.forEachChild((child, key) => {\n    installModule(store, rootState, path.concat(key), child, hot)\n  })\n}\n\n/**\n * make localized dispatch, commit, getters and state\n * if there is no namespace, just use root ones\n */\nfunction makeLocalContext (store, namespace, path) {\n  /* 判断是否有名字空间 */\n  const noNamespace = namespace === ''\n\n  const local = {\n    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {\n      const args = unifyObjectStyle(_type, _payload, _options)\n      const { payload, options } = args\n      let { type } = args\n\n      if (!options || !options.root) {\n        type = namespace + type\n        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {\n          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)\n          return\n        }\n      }\n\n      return store.dispatch(type, payload)\n    },\n\n    commit: noNamespace ? store.commit : (_type, _payload, _options) => {\n      const args = unifyObjectStyle(_type, _payload, _options)\n      const { payload, options } = args\n      let { type } = args\n\n      if (!options || !options.root) {\n        type = namespace + type\n        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {\n          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)\n          return\n        }\n      }\n\n      store.commit(type, payload, options)\n    }\n  }\n\n  // getters and state object must be gotten lazily\n  // because they will be changed by vm update\n  Object.defineProperties(local, {\n    getters: {\n      get: noNamespace\n        ? () => store.getters\n        : () => makeLocalGetters(store, namespace)\n    },\n    state: {\n      get: () => getNestedState(store.state, path)\n    }\n  })\n\n  return local\n}\n\nfunction makeLocalGetters (store, namespace) {\n  const gettersProxy = {}\n\n  const splitPos = namespace.length\n  Object.keys(store.getters).forEach(type => {\n    // skip if the target getter is not match this namespace\n    if (type.slice(0, splitPos) !== namespace) return\n\n    // extract local getter type\n    const localType = type.slice(splitPos)\n\n    // Add a port to the getters proxy.\n    // Define as getter property because\n    // we do not want to evaluate the getters in this time.\n    Object.defineProperty(gettersProxy, localType, {\n      get: () => store.getters[type],\n      enumerable: true\n    })\n  })\n\n  return gettersProxy\n}\n\n/* 遍历注册mutation */\nfunction registerMutation (store, type, handler, local) {\n  /* 所有的mutation会被push进一个数组中，这样相同的mutation就可以调用不同module中的同名的mutation了 */\n  const entry = store._mutations[type] || (store._mutations[type] = [])\n  entry.push(function wrappedMutationHandler (payload) {\n    handler.call(store, local.state, payload)\n  })\n}\n\n/* 遍历注册action */\nfunction registerAction (store, type, handler, local) {\n  /* 取出type对应的action */\n  const entry = store._actions[type] || (store._actions[type] = [])\n  entry.push(function wrappedActionHandler (payload, cb) {\n    let res = handler.call(store, {\n      dispatch: local.dispatch,\n      commit: local.commit,\n      getters: local.getters,\n      state: local.state,\n      rootGetters: store.getters,\n      rootState: store.state\n    }, payload, cb)\n    /* 判断是否是Promise */\n    if (!isPromise(res)) {\n      /* 不是Promise对象的时候转化称Promise对象 */\n      res = Promise.resolve(res)\n    }\n    if (store._devtoolHook) {\n      /* 存在devtool捕获的时候触发vuex的error给devtool */\n      return res.catch(err => {\n        store._devtoolHook.emit('vuex:error', err)\n        throw err\n      })\n    } else {\n      return res\n    }\n  })\n}\n\n/* 遍历注册getter */\nfunction registerGetter (store, type, rawGetter, local) {\n  /* 不存在直接返回 */\n  if (store._wrappedGetters[type]) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] duplicate getter key: ${type}`)\n    }\n    return\n  }\n\n  /* 包装getter */\n  store._wrappedGetters[type] = function wrappedGetter (store) {\n    return rawGetter(\n      local.state, // local state\n      local.getters, // local getters\n      store.state, // root state\n      store.getters // root getters\n    )\n  }\n}\n\n/* 使能严格模式 */\nfunction enableStrictMode (store) {\n  store._vm.$watch(function () { return this._data.$$state }, () => {\n    if (process.env.NODE_ENV !== 'production') {\n      /* 检测store中的_committing的值，如果是true代表不是通过mutation的方法修改的 */\n      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)\n    }\n  }, { deep: true, sync: true })\n}\n\nfunction getNestedState (state, path) {\n  return path.length\n    ? path.reduce((state, key) => state[key], state)\n    : state\n}\n\nfunction unifyObjectStyle (type, payload, options) {\n  if (isObject(type) && type.type) {\n    options = payload\n    payload = type\n    type = type.type\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)\n  }\n\n  return { type, payload, options }\n}\n\n/*暴露给外部的插件install方法，供Vue.use调用安装插件*/\nexport function install (_Vue) {\n  if (Vue) {\n    /*避免重复安装（Vue.use内部也会检测一次是否重复安装同一个插件）*/\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(\n        '[vuex] already installed. Vue.use(Vuex) should be called only once.'\n      )\n    }\n    return\n  }\n  /*保存Vue，同时用于检测是否重复安装*/\n  Vue = _Vue\n  /*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/\n  applyMixin(Vue)\n}\n"
  },
  {
    "path": "vuex-src/util.js",
    "content": "/**\n * Get the first item that pass the test\n * by second argument function\n *\n * @param {Array} list\n * @param {Function} f\n * @return {*}\n */\nfunction find (list, f) {\n  return list.filter(f)[0]\n}\n\n/**\n * Deep copy the given object considering circular structure.\n * This function caches all nested objects and its copies.\n * If it detects circular structure, use cached copy to avoid infinite loop.\n *\n * @param {*} obj\n * @param {Array<Object>} cache\n * @return {*}\n */\nexport function deepCopy (obj, cache = []) {\n  // just return if obj is immutable value\n  if (obj === null || typeof obj !== 'object') {\n    return obj\n  }\n\n  // if obj is hit, it is in circular structure\n  const hit = find(cache, c => c.original === obj)\n  if (hit) {\n    return hit.copy\n  }\n\n  const copy = Array.isArray(obj) ? [] : {}\n  // put the copy into cache at first\n  // because we want to refer it in recursive deepCopy\n  cache.push({\n    original: obj,\n    copy\n  })\n\n  Object.keys(obj).forEach(key => {\n    copy[key] = deepCopy(obj[key], cache)\n  })\n\n  return copy\n}\n\n/**\n * forEach for object\n */\nexport function forEachValue (obj, fn) {\n  Object.keys(obj).forEach(key => fn(obj[key], key))\n}\n\nexport function isObject (obj) {\n  return obj !== null && typeof obj === 'object'\n}\n\n/* 判断是否是Promise */\nexport function isPromise (val) {\n  return val && typeof val.then === 'function'\n}\n\nexport function assert (condition, msg) {\n  if (!condition) throw new Error(`[vuex] ${msg}`)\n}\n"
  }
]