Full Code of answershuto/learnVue for AI

master 1c0371909bbc cached
213 files
619.7 KB
188.1k tokens
445 symbols
1 requests
Download .txt
Showing preview only (671K chars total). Download the full file or copy to clipboard to get everything.
Repository: answershuto/learnVue
Branch: master
Commit: 1c0371909bbc
Files: 213
Total size: 619.7 KB

Directory structure:
gitextract_wq0j9njv/

├── README.md
├── docs/
│   ├── VNode节点.MarkDown
│   ├── VirtualDOM与diff(Vue实现).MarkDown
│   ├── Vue.js异步更新DOM策略及nextTick.MarkDown
│   ├── Vuex源码解析.MarkDown
│   ├── Vue事件机制.MarkDown
│   ├── Vue组件间通信.MarkDown
│   ├── 从template到DOM(Vue.js源码角度看内部运行机制).MarkDown
│   ├── 从源码角度再看数据绑定.MarkDown
│   ├── 依赖收集.MarkDown
│   ├── 响应式原理.MarkDown
│   ├── 聊聊Vue的template编译.MarkDown
│   ├── 聊聊keep-alive组件的使用及其实现原理.MarkDown
│   └── 说说element组件库broadcast与dispatch.MarkDown
├── images/
│   ├── VNode.sketch
│   ├── VNode2.sketch
│   ├── diff1.sketch
│   ├── diff10.sketch
│   ├── diff2.sketch
│   ├── diff3.sketch
│   ├── diff4.sketch
│   ├── diff5.sketch
│   ├── diff6.sketch
│   ├── diff7.sketch
│   ├── diff8.sketch
│   └── diff9.sketch
├── vue-router-src/
│   ├── components/
│   │   ├── link.js
│   │   └── view.js
│   ├── create-matcher.js
│   ├── create-route-map.js
│   ├── history/
│   │   ├── abstract.js
│   │   ├── base.js
│   │   ├── hash.js
│   │   └── html5.js
│   ├── index.js
│   ├── install.js
│   └── util/
│       ├── async.js
│       ├── dom.js
│       ├── location.js
│       ├── params.js
│       ├── path.js
│       ├── push-state.js
│       ├── query.js
│       ├── resolve-components.js
│       ├── route.js
│       ├── scroll.js
│       └── warn.js
├── vue-src/
│   ├── compiler/
│   │   ├── codegen/
│   │   │   ├── events.js
│   │   │   └── index.js
│   │   ├── directives/
│   │   │   ├── bind.js
│   │   │   ├── index.js
│   │   │   └── model.js
│   │   ├── error-detector.js
│   │   ├── helpers.js
│   │   ├── index.js
│   │   ├── optimizer.js
│   │   └── parser/
│   │       ├── entity-decoder.js
│   │       ├── filter-parser.js
│   │       ├── html-parser.js
│   │       ├── index.js
│   │       └── text-parser.js
│   ├── core/
│   │   ├── components/
│   │   │   ├── index.js
│   │   │   └── keep-alive.js
│   │   ├── config.js
│   │   ├── global-api/
│   │   │   ├── assets.js
│   │   │   ├── extend.js
│   │   │   ├── index.js
│   │   │   ├── mixin.js
│   │   │   └── use.js
│   │   ├── index.js
│   │   ├── instance/
│   │   │   ├── events.js
│   │   │   ├── index.js
│   │   │   ├── init.js
│   │   │   ├── inject.js
│   │   │   ├── lifecycle.js
│   │   │   ├── proxy.js
│   │   │   ├── render-helpers/
│   │   │   │   ├── bind-object-props.js
│   │   │   │   ├── check-keycodes.js
│   │   │   │   ├── render-list.js
│   │   │   │   ├── render-slot.js
│   │   │   │   ├── render-static.js
│   │   │   │   ├── resolve-filter.js
│   │   │   │   └── resolve-slots.js
│   │   │   ├── render.js
│   │   │   └── state.js
│   │   ├── observer/
│   │   │   ├── array.js
│   │   │   ├── dep.js
│   │   │   ├── index.js
│   │   │   ├── scheduler.js
│   │   │   └── watcher.js
│   │   ├── util/
│   │   │   ├── debug.js
│   │   │   ├── env.js
│   │   │   ├── error.js
│   │   │   ├── index.js
│   │   │   ├── lang.js
│   │   │   ├── options.js
│   │   │   ├── perf.js
│   │   │   └── props.js
│   │   └── vdom/
│   │       ├── create-component.js
│   │       ├── create-element.js
│   │       ├── create-functional-component.js
│   │       ├── helpers/
│   │       │   ├── extract-props.js
│   │       │   ├── get-first-component-child.js
│   │       │   ├── index.js
│   │       │   ├── merge-hook.js
│   │       │   ├── normalize-children.js
│   │       │   ├── resolve-async-component.js
│   │       │   └── update-listeners.js
│   │       ├── modules/
│   │       │   ├── directives.js
│   │       │   ├── index.js
│   │       │   └── ref.js
│   │       ├── patch.js
│   │       └── vnode.js
│   ├── platforms/
│   │   ├── web/
│   │   │   ├── compiler/
│   │   │   │   ├── directives/
│   │   │   │   │   ├── html.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── model.js
│   │   │   │   │   └── text.js
│   │   │   │   ├── index.js
│   │   │   │   ├── modules/
│   │   │   │   │   ├── class.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── style.js
│   │   │   │   └── util.js
│   │   │   ├── compiler.js
│   │   │   ├── runtime/
│   │   │   │   ├── class-util.js
│   │   │   │   ├── components/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── transition-group.js
│   │   │   │   │   └── transition.js
│   │   │   │   ├── directives/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── model.js
│   │   │   │   │   └── show.js
│   │   │   │   ├── index.js
│   │   │   │   ├── modules/
│   │   │   │   │   ├── attrs.js
│   │   │   │   │   ├── class.js
│   │   │   │   │   ├── dom-props.js
│   │   │   │   │   ├── events.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── style.js
│   │   │   │   │   └── transition.js
│   │   │   │   ├── node-ops.js
│   │   │   │   ├── patch.js
│   │   │   │   └── transition-util.js
│   │   │   ├── runtime-with-compiler.js
│   │   │   ├── runtime.js
│   │   │   ├── server/
│   │   │   │   ├── directives/
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── show.js
│   │   │   │   ├── modules/
│   │   │   │   │   ├── attrs.js
│   │   │   │   │   ├── class.js
│   │   │   │   │   ├── dom-props.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── style.js
│   │   │   │   └── util.js
│   │   │   ├── server-renderer.js
│   │   │   └── util/
│   │   │       ├── attrs.js
│   │   │       ├── class.js
│   │   │       ├── compat.js
│   │   │       ├── element.js
│   │   │       ├── index.js
│   │   │       └── style.js
│   │   └── weex/
│   │       ├── compiler/
│   │       │   ├── directives/
│   │       │   │   ├── index.js
│   │       │   │   └── model.js
│   │       │   ├── index.js
│   │       │   └── modules/
│   │       │       ├── append.js
│   │       │       ├── class.js
│   │       │       ├── index.js
│   │       │       ├── props.js
│   │       │       └── style.js
│   │       ├── compiler.js
│   │       ├── framework.js
│   │       ├── runtime/
│   │       │   ├── components/
│   │       │   │   ├── index.js
│   │       │   │   ├── transition-group.js
│   │       │   │   └── transition.js
│   │       │   ├── directives/
│   │       │   │   └── index.js
│   │       │   ├── index.js
│   │       │   ├── modules/
│   │       │   │   ├── attrs.js
│   │       │   │   ├── class.js
│   │       │   │   ├── events.js
│   │       │   │   ├── index.js
│   │       │   │   ├── style.js
│   │       │   │   └── transition.js
│   │       │   ├── node-ops.js
│   │       │   ├── patch.js
│   │       │   └── text-node.js
│   │       ├── runtime-factory.js
│   │       └── util/
│   │           └── index.js
│   ├── server/
│   │   ├── bundle-renderer/
│   │   │   ├── create-bundle-renderer.js
│   │   │   ├── create-bundle-runner.js
│   │   │   └── source-map-support.js
│   │   ├── create-renderer.js
│   │   ├── render-context.js
│   │   ├── render-stream.js
│   │   ├── render.js
│   │   ├── template-renderer/
│   │   │   ├── create-async-file-mapper.js
│   │   │   ├── index.js
│   │   │   ├── parse-template.js
│   │   │   └── template-stream.js
│   │   ├── util.js
│   │   ├── webpack-plugin/
│   │   │   ├── client.js
│   │   │   ├── server.js
│   │   │   └── util.js
│   │   └── write.js
│   ├── sfc/
│   │   └── parser.js
│   └── shared/
│       ├── constants.js
│       └── util.js
└── vuex-src/
    ├── helpers.js
    ├── index.esm.js
    ├── index.js
    ├── mixin.js
    ├── module/
    │   ├── module-collection.js
    │   └── module.js
    ├── plugins/
    │   ├── devtool.js
    │   └── logger.js
    ├── store.js
    └── util.js

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# learnVue

## 介绍

Vue.js源码分析,记录了个人学习Vue.js源码的过程中的一些心得以及收获。以及对于Vue框架,周边库的一些个人见解。

在学习的过程中我为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及周边库的源码。

感谢[尤大](https://github.com/yyx990803)提高生产力。

本项目希望对Vue.js做更进一步的探索与学习,Vue.js基础内容请参考Vue.js官网,[https://cn.vuejs.org/v2/guide/](https://cn.vuejs.org/v2/guide/)。
可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。

---

## 目录

### 源码相关

[Vue.js响应式原理](./docs/响应式原理.MarkDown)

[Vue.js依赖收集](./docs/依赖收集.MarkDown)

[从Vue.js源码角度再看数据绑定](./docs/从源码角度再看数据绑定.MarkDown)

[Vue.js事件机制](./docs/Vue事件机制.MarkDown)

[VNode节点(Vue.js实现)](./docs/VNode节点.MarkDown)

[Virtual DOM与diff(Vue.js实现)](./docs/VirtualDOM与diff(Vue实现).MarkDown)

[聊聊Vue.js的template编译](./docs/聊聊Vue的template编译.MarkDown)

[Vue.js异步更新DOM策略及nextTick](./docs/Vue.js异步更新DOM策略及nextTick.MarkDown)

[从template到DOM(Vue.js源码角度看内部运行机制)](./docs/从template到DOM(Vue.js源码角度看内部运行机制).MarkDown)

[Vuex源码解析](./docs/Vuex源码解析.MarkDown)

[聊聊keep-alive组件的使用及其实现原理](./docs/聊聊keep-alive组件的使用及其实现原理.MarkDown)

### 随笔杂谈

[Vue组件间通信](./docs/Vue组件间通信.MarkDown)

[说说element组件库broadcast与dispatch](./docs/说说element组件库broadcast与dispatch.MarkDown)

---

## 对于新手同学

由于以上内容都是针对 Vue.js 源码进行讲解了,可能有一些不太熟悉源码的同学读起来感觉晦涩难懂。

笔者撰写的[《剖析 Vue.js 内部运行机制》](https://juejin.im/book/5a36661851882538e2259c0f)或许可以帮到你。


## 关于作者

作者: 染陌

Email:answershuto@gmail.com

Github: [https://github.com/answershuto](https://github.com/answershuto)

知乎:[https://www.zhihu.com/people/cao-yang-49/activities](https://www.zhihu.com/people/cao-yang-49/activities)

掘金:[https://juejin.im/user/58f87ae844d9040069ca7507](https://juejin.im/user/58f87ae844d9040069ca7507)

对内容有任何疑问,欢迎联系我。

================================================
FILE: docs/VNode节点.MarkDown
================================================
## 抽象DOM树

在刀耕火种的年代,我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。

那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树,在修改抽象树数据后将抽象树转化成真实DOM重绘到页面上呢?于是虚拟DOM出现了,它是真实DOM的一层抽象,用属性描述真实DOM的各个特性。当它发生变化的时候,就会去修改视图。

可以想象,最简单粗暴的方法就是将整个DOM结构用innerHTML修改到页面上,但是这样进行重绘整个视图层是相当消耗性能的,我们是不是可以每次只更新它的修改呢?所以Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,以VNode节点模拟真实DOM,可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作,在这过程中都不需要操作真实DOM,只需要操作JavaScript对象后只对差异修改,相对于整块的innerHTML的粗暴式修改,大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位,再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作,大大提高了性能。

Vue就使用了这样的抽象节点VNode,它是对真实DOM的一层抽象,而不依赖某个平台,它可以是浏览器平台,也可以是weex,甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作,这也为前后端同构提供了可能。

## VNode基类

先来看一下Vue.js源码中对VNode类的定义。

```javascript
export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    /*当前节点的标签名*/
    this.tag = tag
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
    /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.functionalContext = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
    /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为根节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    this.isOnce = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}
```

这是一个最基础的VNode节点,作为其他派生VNode类的基类,里面定义了下面这些数据。

tag: 当前节点的标签名

data: 当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息

children: 当前节点的子节点,是一个数组

text: 当前节点的文本

elm: 当前虚拟节点对应的真实dom节点

ns: 当前节点的名字空间

context: 当前节点的编译作用域

functionalContext: 函数化组件作用域

key: 节点的key属性,被当作节点的标志,用以优化

componentOptions: 组件的option选项

componentInstance: 当前节点对应的组件的实例

parent: 当前节点的父节点

raw: 简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false

isStatic: 是否为静态节点

isRootInsert: 是否作为跟节点插入

isComment: 是否为注释节点

isCloned: 是否为克隆节点

isOnce: 是否有v-once指令

---

打个比方,比如说我现在有这么一个VNode树

```JavaScript
{
    tag: 'div'
    data: {
        class: 'test'
    },
    children: [
        {
            tag: 'span',
            data: {
                class: 'demo'
            }
            text: 'hello,VNode'
        }
    ]
}
```

渲染之后的结果就是这样的

```html
<div class="test">
    <span class="demo">hello,VNode</span>
</div>
```

## 生成一个新的VNode的方法

下面这些方法都是一些常用的构造VNode的方法。

### createEmptyVNode 创建一个空VNode节点

```javascript
/*创建一个空VNode节点*/
export const createEmptyVNode = () => {
  const node = new VNode()
  node.text = ''
  node.isComment = true
  return node
}
```

### createTextVNode 创建一个文本节点

```javascript
/*创建一个文本节点*/
export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}
```

### createComponent 创建一个组件节点

```javascript
 // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  /*Github:https://github.com/answershuto*/
  /*如果在该阶段Ctor依然不是一个构造函数或者是一个异步组件工厂则直接返回*/
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  /*处理异步组件*/
  if (isUndef(Ctor.cid)) {
    Ctor = resolveAsyncComponent(Ctor, baseCtor, context)
    if (Ctor === undefined) {
      // return nothing if this is indeed an async component
      // wait for the callback to trigger parent update.
      /*如果这是一个异步组件则会不会返回任何东西(undifiened),直接return掉,等待回调函数去触发父组件更新。s*/
      return
    }
  }

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  data = data || {}

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners
    data = {}
  }

  // merge component management hooks onto the placeholder node
  mergeHooks(data)

  // return a placeholder vnode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children }
  )
  return vnode
}

```

### cloneVNode 克隆一个VNode节点

```javascript
export function cloneVNode (vnode: VNode): VNode {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    vnode.children,
    vnode.text,
    vnode.elm,
    vnode.context,
    vnode.componentOptions
  )
  cloned.ns = vnode.ns
  cloned.isStatic = vnode.isStatic
  cloned.key = vnode.key
  cloned.isCloned = true
  return cloned
}
```

## createElement

```javascript
// wrapper function for providing a more flexible interface
// without getting yelled at by flow
export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode {
  /*兼容不传data的情况*/
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  /*如果alwaysNormalize为true,则normalizationType标记为ALWAYS_NORMALIZE*/
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  /*Github:https://github.com/answershuto*/
  /*创建虚拟节点*/
  return _createElement(context, tag, data, children, normalizationType)
}

/*创建虚拟节点*/
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode {
  /*
    如果传递data参数且data的__ob__已经定义(代表已经被observed,上面绑定了Oberver对象),
    https://cn.vuejs.org/v2/guide/render-function.html#约束
    那么创建一个空节点
  */
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  /*如果tag不存在也是创建一个空节点*/
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // support single function children as default scoped slot
  /*默认默认作用域插槽*/
  if (Array.isArray(children) &&
      typeof children[0] === 'function') {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    /*获取tag的名字空间*/
    ns = config.getTagNamespace(tag)
    /*判断是否是保留的标签*/
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      /*如果是保留的标签则创建一个相应节点*/
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      /*从vm实例的option的components中寻找该tag,存在则就是一个组件,创建相应节点,Ctor为组件的构造类*/
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      /*未知的元素,在运行时检查,因为父组件可能在序列化子组件的时候分配一个名字空间*/
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    /*tag不是字符串的时候则是组件的构造类*/
    vnode = createComponent(tag, data, context, children)
  }
  if (isDef(vnode)) {
    /*如果有名字空间,则递归所有子节点应用该名字空间*/
    if (ns) applyNS(vnode, ns)
    return vnode
  } else {
    /*如果vnode没有成功创建则创建空节点*/
    return createEmptyVNode()
  }
}
```

createElement用来创建一个虚拟节点。当data上已经绑定__ob__的时候,代表该对象已经被Oberver过了,所以创建一个空节点。tag不存在的时候同样创建一个空节点。当tag不是一个String类型的时候代表tag是一个组件的构造类,直接用new VNode创建。当tag是String类型的时候,如果是保留标签,则用new VNode创建一个VNode实例,如果在vm的option的components找得到该tag,代表这是一个组件,否则统一用new VNode创建。


================================================
FILE: docs/VirtualDOM与diff(Vue实现).MarkDown
================================================
## VNode

在刀耕火种的年代,我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。

那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树,在修改抽象树数据后将抽象树转化成真实DOM重绘到页面上呢?于是虚拟DOM出现了,它是真实DOM的一层抽象,用属性描述真实DOM的各个特性。当它发生变化的时候,就会去修改视图。

可以想象,最简单粗暴的方法就是将整个DOM结构用innerHTML修改到页面上,但是这样进行重绘整个视图层是相当消耗性能的,我们是不是可以每次只更新它的修改呢?所以Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,以VNode节点模拟真实DOM,可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作,在这过程中都不需要操作真实DOM,只需要操作JavaScript对象后只对差异修改,相对于整块的innerHTML的粗暴式修改,大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位,再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作,大大提高了性能。

Vue就使用了这样的抽象节点VNode,它是对真实DOM的一层抽象,而不依赖某个平台,它可以是浏览器平台,也可以是weex,甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作,这也为前后端同构提供了可能。

具体VNode的细节可以看[VNode节点](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)。

## 修改视图

众所周知,Vue通过数据绑定来修改视图,当某个数据被修改的时候,set方法会让闭包中的Dep调用notify通知所有订阅者Watcher,Watcher通过get方法执行vm._update(vm._render(), hydrating)。

这里看一下_update方法

```JavaScript
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    /*如果已经该组件已经挂载过了则代表进入这个步骤是个更新的过程,触发beforeUpdate钩子*/
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    /*基于后端渲染Vue.prototype.__patch__被用来作为一个入口*/
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
    // update __vue__ reference
    /*更新新的实例对象的__vue__*/
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }
```

_update方法的第一个参数是一个VNode对象,在内部会将该VNode对象与之前旧的VNode对象进行__patch__。

什么是__patch__呢?

## __patch__

patch将新老VNode节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法,这套算法可以高效地比较virtual DOM的变更,得出变化以修改视图。

那么patch如何工作的呢?

首先说一下patch的核心diff算法,diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。

![img](https://i.loli.net/2017/08/27/59a23cfca50f3.png)

![img](https://i.loli.net/2017/08/27/59a2419a3c617.png)

这两张图代表旧的VNode与新VNode进行patch的过程,他们只是在同层级的VNode之间进行比较得到变化(第二张图中相同颜色的方块代表互相进行比较的VNode节点),然后修改变化的视图,所以十分高效。

让我们看一下patch的代码。

```JavaScript
  /*createPatchFunction的返回值,一个patch函数*/
  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    /*vnode不存在则直接调用销毁钩子*/
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      /*oldVnode未定义的时候,其实也就是root节点,创建一个新的节点*/
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      /*标记旧的VNode是否有nodeType*/
      /*Github:https://github.com/answershuto*/
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        /*是同一个节点的时候直接修改现有的节点*/
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            /*当旧的VNode是服务端渲染的元素,hydrating记为true*/
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            /*需要合并到真实DOM上*/
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              /*调用insert钩子*/
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          /*如果不是服务端渲染或者合并到真实DOM失败,则创建一个空的VNode节点替换它*/
          oldVnode = emptyNodeAt(oldVnode)
        }
        // replacing existing element
        /*取代现有元素*/
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        if (isDef(vnode.parent)) {
          // component root element replaced.
          // update parent placeholder node element, recursively
          /*组件根节点被替换,遍历更新父节点element*/
          let ancestor = vnode.parent
          while (ancestor) {
            ancestor.elm = vnode.elm
            ancestor = ancestor.parent
          }
          if (isPatchable(vnode)) {
            /*调用create回调*/
            for (let i = 0; i < cbs.create.length; ++i) {
              cbs.create[i](emptyNode, vnode.parent)
            }
          }
        }

        if (isDef(parentElm)) {
          /*移除老节点*/
          removeVnodes(parentElm, [oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          /*Github:https://github.com/answershuto*/
          /*调用destroy钩子*/
          invokeDestroyHook(oldVnode)
        }
      }
    }

    /*调用insert钩子*/
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
```

从代码中不难发现,当oldVnode与vnode在sameVnode的时候才会进行patchVnode,也就是新旧VNode节点判定为同一节点的时候才会进行patchVnode这个过程,否则就是创建新的DOM,移除旧的DOM。

怎么样的节点算sameVnode呢?

## sameVnode

我们来看一下sameVnode的实现。

```JavaScript
/*
  判断两个VNode节点是否是同一个节点,需要满足以下条件
  key相同
  tag(当前节点的标签名)相同
  isComment(是否为注释节点)相同
  是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息)都有定义
  当标签是<input>的时候,type必须相同
*/
function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
  )
}

// Some browsers do not support dynamically changing type for <input>
// so they need to be treated as different nodes
/*
  判断当标签是<input>的时候,type是否相同
  某些浏览器不支持动态修改<input>类型,所以他们被视为不同节点
*/
function sameInputType (a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB
}
```

当两个VNode的tag、key、isComment都相同,并且同时定义或未定义data的时候,且如果标签为input则type必须相同。这时候这两个VNode则算sameVnode,可以直接进行patchVnode操作。

## patchVnode

还是先来看一下patchVnode的代码。

```JavaScript
  /*patch VNode节点*/
  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    /*两个VNode节点相同则直接返回*/
    if (oldVnode === vnode) {
      return
    }
    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    /*
      如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),
      并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),
      那么只需要替换elm以及componentInstance即可。
    */
    if (isTrue(vnode.isStatic) &&
        isTrue(oldVnode.isStatic) &&
        vnode.key === oldVnode.key &&
        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
      vnode.elm = oldVnode.elm
      vnode.componentInstance = oldVnode.componentInstance
      return
    }
    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      /*i = data.hook.prepatch,如果存在的话,见"./create-component componentVNodeHooks"。*/
      i(oldVnode, vnode)
    }
    const elm = vnode.elm = oldVnode.elm
    const oldCh = oldVnode.children
    const ch = vnode.children
    if (isDef(data) && isPatchable(vnode)) {
      /*调用update回调以及update钩子*/
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    /*如果这个VNode节点没有text文本时*/
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        /*新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren*/
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        /*如果老节点没有子节点而新节点存在子节点,先清空elm的文本内容,然后为当前节点加入子节点*/
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        /*当新节点没有子节点而老节点有子节点的时候,则移除所有ele的子节点*/
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        /*当新老节点都无子节点的时候,只是文本的替换,因为这个逻辑中新节点text不存在,所以直接去除ele的文本*/
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      /*当新老节点text不一样时,直接替换这段文本*/
      nodeOps.setTextContent(elm, vnode.text)
    }
    /*调用postpatch钩子*/
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }
```

patchVnode的规则是这样的:

1.如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentInstance即可。

2.新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。

3.如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。

4.当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。

5.当新老节点都无子节点的时候,只是文本的替换。

## updateChildren

```JavaScript
  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, elmToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        /*前四种情况其实是指定key的时候,判定为同一个VNode,则直接patchVnode即可,分别比较oldCh以及newCh的两头节点2*2=4种情况*/
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        /*
          生成一个key与旧VNode的key对应的哈希表(只有第一次进来undefined的时候会生成,也为后面检测重复的key值做铺垫)
          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  
          结果生成{key0: 0, key1: 1, key2: 2}
        */
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld(即第几个节点,下标)*/
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
        if (isUndef(idxInOld)) { // New element
          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          newStartVnode = newCh[++newStartIdx]
        } else {
          /*获取同key的老节点*/
          elmToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !elmToMove) {
            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的DOM中,提示可能存在重复的key,确保v-for的时候item有唯一的key值*/
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }
          if (sameVnode(elmToMove, newStartVnode)) {
            /*Github:https://github.com/answershuto*/
            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
            /*因为已经patchVnode进去了,所以将这个老节点赋值undefined,之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/
            oldCh[idxInOld] = undefined
            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实DOM节点前面*/
            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          } else {
            // same key but different element. treat as new element
            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候(比如说tag不一样或者是有不一样type的input标签),创建一个新的节点*/
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          }
        }
      }
    }
    if (oldStartIdx > oldEndIdx) {
      /*全部比较完成以后,发现oldStartIdx > oldEndIdx的话,说明老节点已经遍历完了,新节点比老节点多,所以这时候多出来的新节点需要一个一个创建出来加入到真实DOM中*/
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      /*如果全部比较完成以后发现newStartIdx > newEndIdx,则说明新节点已经遍历完了,老节点多余新节点,这个时候需要将多余的老节点从真实DOM中移除*/
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }
```

直接看源码可能比较难以捋清其中的关系,我们通过图来看一下。

![img](https://i.loli.net/2017/08/28/59a4015bb2765.png)

首先,在新老两个VNode节点的左右头尾两侧都有一个变量标记,在遍历过程中这几个变量都会向中间靠拢。当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环。

索引与VNode节点的对应关系:
oldStartIdx => oldStartVnode
oldEndIdx => oldEndVnode
newStartIdx => newStartVnode
newEndIdx => newEndVnode

在遍历中,如果存在key,并且满足sameVnode,会将该DOM节点进行复用,否则则会创建一个新的DOM节点。

首先,oldStartVnode、oldEndVnode与newStartVnode、newEndVnode两两比较一共有2*2=4种比较方法。

当新老VNode节点的start或者end满足sameVnode时,也就是sameVnode(oldStartVnode, newStartVnode)或者sameVnode(oldEndVnode, newEndVnode),直接将该VNode节点进行patchVnode即可。

![img](https://i.loli.net/2017/08/28/59a40c12c1655.png)

如果oldStartVnode与newEndVnode满足sameVnode,即sameVnode(oldStartVnode, newEndVnode)。

这时候说明oldStartVnode已经跑到了oldEndVnode后面去了,进行patchVnode的同时还需要将真实DOM节点移动到oldEndVnode的后面。

![img](https://ooo.0o0.ooo/2017/08/28/59a4214784979.png)

如果oldEndVnode与newStartVnode满足sameVnode,即sameVnode(oldEndVnode, newStartVnode)。

这说明oldEndVnode跑到了oldStartVnode的前面,进行patchVnode的同时真实的DOM节点移动到了oldStartVnode的前面。

![img](https://i.loli.net/2017/08/29/59a4c70685d12.png)

如果以上情况均不符合,则通过createKeyToOldIdx会得到一个oldKeyToIdx,里面存放了一个key为旧的VNode,value为对应index序列的哈希表。从这个哈希表中可以找到是否有与newStartVnode一致key的旧的VNode节点,如果同时满足sameVnode,patchVnode的同时会将这个真实DOM(elmToMove)移动到oldStartVnode对应的真实DOM的前面。

![img](https://i.loli.net/2017/08/29/59a4d7552d299.png)

当然也有可能newStartVnode在旧的VNode节点找不到一致的key,或者是即便key相同却不是sameVnode,这个时候会调用createElm创建一个新的DOM节点。

![img](https://i.loli.net/2017/08/29/59a4de0fa4dba.png)

到这里循环已经结束了,那么剩下我们还需要处理多余或者不够的真实DOM节点。

1.当结束时oldStartIdx > oldEndIdx,这个时候老的VNode节点已经遍历完了,但是新的节点还没有。说明了新的VNode节点实际上比老的VNode节点多,也就是比真实DOM多,需要将剩下的(也就是新增的)VNode节点插入到真实DOM节点中去,此时调用addVnodes(批量调用createElm的接口将这些节点加入到真实DOM中去)。

![img](https://i.loli.net/2017/08/29/59a509f0d1788.png)

2。同理,当newStartIdx > newEndIdx时,新的VNode节点已经遍历完了,但是老的节点还有剩余,说明真实DOM节点多余了,需要从文档中删除,这时候调用removeVnodes将这些多余的真实DOM删除。

![img](https://i.loli.net/2017/08/29/59a4f389b98cb.png)

## DOM操作

由于Vue使用了虚拟DOM,所以虚拟DOM可以在任何支持JavaScript语言的平台上操作,譬如说目前Vue支持的浏览器平台或是weex,在虚拟DOM的实现上是一致的。那么最后虚拟DOM如何映射到真实的DOM节点上呢?

Vue为平台做了一层适配层,浏览器平台见[/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节点的时候,只需要调用这些适配层的接口即可,而内部实现则不需要关心,它会根据平台的改变而改变。

现在又出现了一个问题,我们只是将虚拟DOM映射成了真实的DOM。那如何给这些DOM加入attr、class、style等DOM属性呢?

这要依赖于虚拟DOM的生命钩子。虚拟DOM提供了如下的钩子函数,分别在不同的时期会进行调用。

```JavaScript
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']

/*构建cbs回调函数,web平台上见/platforms/web/runtime/modules*/
  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
```

同理,也会根据不同平台有自己不同的实现,我们这里以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属性进行操作。

以attr为例,代码很简单。

```JavaScript
/* @flow */

import { isIE9 } from 'core/util/env'

import {
  extend,
  isDef,
  isUndef
} from 'shared/util'

import {
  isXlink,
  xlinkNS,
  getXlinkProp,
  isBooleanAttr,
  isEnumeratedAttr,
  isFalsyAttrValue
} from 'web/util/index'

/*更新attr*/
function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  /*如果旧的以及新的VNode节点均没有attr属性,则直接返回*/
  if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
    return
  }
  let key, cur, old
  /*VNode节点对应的Dom实例*/
  const elm = vnode.elm
  /*旧VNode节点的attr*/
  const oldAttrs = oldVnode.data.attrs || {}
  /*新VNode节点的attr*/
  let attrs: any = vnode.data.attrs || {}
  // clone observed objects, as the user probably wants to mutate it
  /*如果新的VNode的attr已经有__ob__(代表已经被Observe处理过了), 进行深拷贝*/
  if (isDef(attrs.__ob__)) {
    attrs = vnode.data.attrs = extend({}, attrs)
  }

  /*遍历attr,不一致则替换*/
  for (key in attrs) {
    cur = attrs[key]
    old = oldAttrs[key]
    if (old !== cur) {
      setAttr(elm, key, cur)
    }
  }
  // #4391: in IE9, setting type can reset value for input[type=radio]
  /* istanbul ignore if */
  if (isIE9 && attrs.value !== oldAttrs.value) {
    setAttr(elm, 'value', attrs.value)
  }
  for (key in oldAttrs) {
    if (isUndef(attrs[key])) {
      if (isXlink(key)) {
        elm.removeAttributeNS(xlinkNS, getXlinkProp(key))
      } else if (!isEnumeratedAttr(key)) {
        elm.removeAttribute(key)
      }
    }
  }
}

/*设置attr*/
function setAttr (el: Element, key: string, value: any) {
  if (isBooleanAttr(key)) {
    // set attribute for blank value
    // e.g. <option disabled>Select one</option>
    if (isFalsyAttrValue(value)) {
      el.removeAttribute(key)
    } else {
      el.setAttribute(key, key)
    }
  } else if (isEnumeratedAttr(key)) {
    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')
  } else if (isXlink(key)) {
    if (isFalsyAttrValue(value)) {
      el.removeAttributeNS(xlinkNS, getXlinkProp(key))
    } else {
      el.setAttributeNS(xlinkNS, key, value)
    }
  } else {
    if (isFalsyAttrValue(value)) {
      el.removeAttribute(key)
    } else {
      el.setAttribute(key, value)
    }
  }
}

export default {
  create: updateAttrs,
  update: updateAttrs
}

```

attr只需要在create以及update钩子被调用时更新DOM的attr属性即可。


================================================
FILE: docs/Vue.js异步更新DOM策略及nextTick.MarkDown
================================================
## 操作DOM

在使用vue.js的时候,有时候因为一些特定的业务场景,不得不去操作DOM,比如这样:

```html
<template>
  <div>
    <div ref="test">{{test}}</div>
    <button @click="handleClick">tet</button>
  </div>
</template>

```

```javascript
export default {
    data () {
        return {
            test: 'begin'
        };
    },
    methods () {
        handleClick () {
            this.test = 'end';
            console.log(this.$refs.test.innerText);//打印“begin”
        }
    }
}
```

打印的结果是begin,为什么我们明明已经将test设置成了“end”,获取真实DOM节点的innerText却没有得到我们预期中的“end”,而是得到之前的值“begin”呢?

## Watcher队列

带着疑问,我们找到了Vue.js源码的Watch实现。当某个响应式数据发生变化的时候,它的setter函数会通知闭包中的Dep,Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。我们来看一下update的实现。

```javascript
update () {
    /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true
    } else if (this.sync) {
        /*同步则执行run直接渲染视图*/
        this.run()
    } else {
        /*异步推送到观察者队列中,下一个tick时调用。*/
        queueWatcher(this)
    }
}
```

我们发现Vue.js默认是使用[异步执行DOM更新](https://cn.vuejs.org/v2/guide/reactivity.html#异步更新队列)。
当异步执行update的时候,会调用queueWatcher函数。

```javascript
 /*将一个观察者对象push进观察者队列,在队列中已经存在相同的id则该观察者对象将被跳过,除非它是在队列被刷新时推送*/
export function queueWatcher (watcher: Watcher) {
  /*获取watcher的id*/
  const id = watcher.id
  /*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      /*如果没有flush掉,直接push到队列中即可*/
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i >= 0 && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(Math.max(i, index) + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}
```

查看queueWatcher的源码我们发现,Watch对象并不是立即更新视图,而是被push进了一个队列queue,此时状态处于waiting的状态,这时候会继续会有Watch对象被push进这个队列queue,等到下一个tick运行时,这些Watch对象才会被遍历取出,更新视图。同时,id重复的Watcher不会被多次加入到queue中去,因为在最终渲染时,我们只需要关心数据的最终结果。

那么,什么是下一个tick?

## nextTick

vue.js提供了一个[nextTick](https://cn.vuejs.org/v2/api/#Vue-nextTick)函数,其实也就是上面调用的nextTick。

nextTick的实现比较简单,执行的目的是在microtask或者task中推入一个function,在当前栈执行完毕(也许还会有一些排在前面的需要执行的任务)以后执行nextTick传入的function,看一下源码:

```javascript
/**
 * Defer a task to execute it asynchronously.
 */
 /*
    延迟一个任务使其异步执行,在下一个tick时执行,一个立即执行函数,返回一个function
    这个函数的作用是在task或者microtask中推入一个timerFunc,在当前调用栈执行完以后以此执行直到执行到timerFunc
    目的是延迟到当前调用栈执行完以后执行
*/
export const nextTick = (function () {
  /*存放异步执行的回调*/
  const callbacks = []
  /*一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送*/
  let pending = false
  /*一个函数指针,指向函数将被推送到任务队列中,等到主线程任务执行完时,任务队列中的timerFunc被调用*/
  let timerFunc

  /*下一个tick时的回调*/
  function nextTickHandler () {
    /*一个标记位,标记等待状态(即函数已经被推入任务队列或者主线程,已经在等待当前栈执行完毕去执行),这样就不需要在push多个回调到callbacks时将timerFunc多次推入任务队列或者主线程*/
    pending = false
    /*执行所有callback*/
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }

  // the nextTick behavior leverages the microtask queue, which can be accessed
  // via either native Promise.then or MutationObserver.
  // MutationObserver has wider support, however it is seriously bugged in
  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
  // completely stops working after triggering a few times... so, if native
  // Promise is available, we will use it:
  /* istanbul ignore if */

  /*
    这里解释一下,一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法
    优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法都会在microtask中执行,会比setTimeout更早执行,所以优先使用。
    如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。
    参考:https://www.zhihu.com/question/55364497
  */
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    /*使用Promise*/
    var p = Promise.resolve()
    var logError = err => { console.error(err) }
    timerFunc = () => {
      p.then(nextTickHandler).catch(logError)
      // in problematic UIWebViews, Promise.then doesn't completely break, but
      // it can get stuck in a weird state where callbacks are pushed into the
      // microtask queue but the queue isn't being flushed, until the browser
      // needs to do some other work, e.g. handle a timer. Therefore we can
      // "force" the microtask queue to be flushed by adding an empty timer.
      if (isIOS) setTimeout(noop)
    }
  } else if (typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    // use MutationObserver where native Promise is not available,
    // e.g. PhantomJS IE11, iOS7, Android 4.4
    /*新建一个textNode的DOM对象,用MutationObserver绑定该DOM并指定回调函数,在DOM变化的时候则会触发回调,该回调会进入主线程(比任务队列优先执行),即textNode.data = String(counter)时便会触发回调*/
    var counter = 1
    var observer = new MutationObserver(nextTickHandler)
    var textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
      characterData: true
    })
    timerFunc = () => {
      counter = (counter + 1) % 2
      textNode.data = String(counter)
    }
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    /*使用setTimeout将回调推入任务队列尾部*/
    timerFunc = () => {
      setTimeout(nextTickHandler, 0)
    }
  }

  /*
    推送到队列中下一个tick时执行
    cb 回调函数
    ctx 上下文
  */
  return function queueNextTick (cb?: Function, ctx?: Object) {
    let _resolve
    /*cb存到callbacks中*/
    callbacks.push(() => {
      if (cb) {
        try {
          cb.call(ctx)
        } catch (e) {
          handleError(e, ctx, 'nextTick')
        }
      } else if (_resolve) {
        _resolve(ctx)
      }
    })
    if (!pending) {
      pending = true
      timerFunc()
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        _resolve = resolve
      })
    }
  }
})()
```

它是一个立即执行函数,返回一个queueNextTick接口。

传入的cb会被push进callbacks中存放起来,然后执行timerFunc(pending是一个状态标记,保证timerFunc在下一个tick之前只执行一次)。

timerFunc是什么?

看了源码发现timerFunc会检测当前环境而不同实现,其实就是按照Promise,MutationObserver,setTimeout优先级,哪个存在使用哪个,最不济的环境下使用setTimeout。

这里解释一下,一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法。
优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法的回调函数都会在microtask中执行,它们会比setTimeout更早执行,所以优先使用。
如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。

为什么要优先使用microtask?我在顾轶灵在知乎的回答中学习到:

```
JS 的 event loop 执行时会区分 task 和 microtask,引擎在每个 task 执行完毕,从队列中取下一个 task 来执行之前,会先执行完所有 microtask 队列中的 microtask。
setTimeout 回调会被分配到一个新的 task 中执行,而 Promise 的 resolver、MutationObserver 的回调都会被安排到一个新的 microtask 中执行,会比 setTimeout 产生的 task 先执行。
要创建一个新的 microtask,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。
实在不行,只能用 setTimeout 创建 task 了。
为啥要用 microtask?
根据 HTML Standard,在每个 task 运行完以后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。
反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

参考顾轶灵知乎的回答:https://www.zhihu.com/question/55364497/answer/144215284
```

首先是Promise,Promise.resolve().then()可以在microtask中加入它的回调,

MutationObserver新建一个textNode的DOM对象,用MutationObserver绑定该DOM并指定回调函数,在DOM变化的时候则会触发回调,该回调会进入microtask,即textNode.data = String(counter)时便会加入该回调。

setTimeout是最后的一种备选方案,它会将回调函数加入task中,等到执行。

综上,nextTick的目的就是产生一个回调函数加入task或者microtask中,当前栈执行完以后(可能中间还有别的排在前面的函数)调用该回调函数,起到了异步触发(即下一个tick时触发)的目的。

## flushSchedulerQueue

```javascript
/*Github:https://github.com/answershuto*/
/**
 * Flush both queues and run the watchers.
 */
 /*nextTick的回调函数,在下一个tick时flush掉两个队列同时运行watchers*/
function flushSchedulerQueue () {
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  /*
    给queue排序,这样做可以保证:
    1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
    2.一个组件的user watchers比render watcher先运行,因为user watchers往往比render watcher更早创建
    3.如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。
  */
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  /*这里不用index = queue.length;index > 0; index--的方式写是因为不要将length进行缓存,因为在执行处理现有watcher对象期间,更多的watcher对象可能会被push进queue*/
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    id = watcher.id
    /*将has的标记删除*/
    has[id] = null
    /*执行watcher*/
    watcher.run()
    // in dev build, check and stop circular updates.
    /*
      在测试环境中,检测watch是否在死循环中
      比如这样一种情况
      watch: {
        test () {
          this.test++;
        }
      }
      持续执行了一百次watch代表可能存在死循环
    */
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

  // keep copies of post queues before resetting state
  /**/
  /*得到队列的拷贝*/
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  /*重置调度者的状态*/
  resetSchedulerState()

  // call component updated and activated hooks
  /*使子组件状态都改编成active同时调用activated钩子*/
  callActivatedHooks(activatedQueue)
  /*调用updated钩子*/
  callUpdateHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
```

flushSchedulerQueue是下一个tick时的回调函数,主要目的是执行Watcher的run函数,用来更新视图 

## 为什么要异步更新视图

来看一下下面这一段代码

```html
<template>
  <div>
    <div>{{test}}</div>
  </div>
</template>

```

```javascript
export default {
    data () {
        return {
            test: 0
        };
    },
    mounted () {
      for(let i = 0; i < 1000; i++) {
        this.test++;
      }
    }
}
```

现在有这样的一种情况,mounted的时候test的值会被++循环执行1000次。
每次++时,都会根据响应式触发setter->Dep->Watcher->update->patch。
如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。
所以Vue.js实现了一个queue队列,在下一个tick的时候会统一执行queue中Watcher的run。同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行1000次Watcher的run。最终更新视图只会直接将test对应的DOM的0变成1000。
保证更新视图操作DOM的动作是在当前栈执行完以后下一个tick的时候调用,大大优化了性能。

## 访问真实DOM节点更新后的数据

所以我们需要在修改data中的数据后访问真实的DOM节点更新后的数据,只需要这样,我们把文章第一个例子进行修改。

```html
<template>
  <div>
    <div ref="test">{{test}}</div>
    <button @click="handleClick">tet</button>
  </div>
</template>

```

```javascript
export default {
    data () {
        return {
            test: 'begin'
        };
    },
    methods () {
        handleClick () {
            this.test = 'end';
            this.$nextTick(() => {
                console.log(this.$refs.test.innerText);//打印"end"
            });
            console.log(this.$refs.test.innerText);//打印“begin”
        }
    }
}
```

使用Vue.js的global API的$nextTick方法,即可在回调中获取已经更新好的DOM实例了。


================================================
FILE: docs/Vuex源码解析.MarkDown
================================================
## Vuex

我们在使用Vue.js开发复杂的应用时,经常会遇到多个组件共享同一个状态,亦或是多个组件会去更新同一个状态,在应用代码量较少的时候,我们可以组件间通信去维护修改数据,或者是通过事件总线来进行数据的传递以及修改。但是当应用逐渐庞大以后,代码就会变得难以维护,从父组件开始通过prop传递多层嵌套的数据由于层级过深而显得异常脆弱,而事件总线也会因为组件的增多、代码量的增大而显得交互错综复杂,难以捋清其中的传递关系。

那么为什么我们不能将数据层与组件层抽离开来呢?把数据层放到全局形成一个单一的Store,组件层变得更薄,专门用来进行数据的展示及操作。所有数据的变更都需要经过全局的Store来进行,形成一个单向数据流,使数据变化变得“可预测”。

Vuex是一个专门为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)。

先来看一下这张Vuex的数据流程图,熟悉Vuex使用的同学应该已经有所了解。

![](https://vuex.vuejs.org/vuex.png)

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,所有修改State的操作必须通过Mutation进行,Mutation的同时提供了订阅者模式供外部插件调用获取State数据的更新。所有异步接口需要走Action,常见于调用后端接口异步获取更新数据,而Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。Vuex运行依赖Vue内部数据双向绑定机制,需要new一个Vue对象来实现“响应式化”,所以Vuex是一个专门为Vue.js设计的状态管理库。

## 安装

使用过Vuex的朋友一定知道,Vuex的安装十分简单,只需要提供一个store,然后执行下面两句代码即完成的Vuex的引入。

```javascript
Vue.use(Vuex);

/*将store放入Vue创建时的option中*/
new Vue({
    el: '#app',
    store
});
```

那么问题来了,Vuex是怎样把store注入到Vue实例中去的呢?

Vue.js提供了[Vue.use](https://cn.vuejs.org/v2/api/#Vue-use)方法用来给Vue.js安装插件,内部通过调用插件的install方法(当插件是一个对象的时候)来进行插件的安装。

我们来看一下Vuex的install实现。

```javascript
/*暴露给外部的插件install方法,供Vue.use调用安装插件*/
export function install (_Vue) {
  if (Vue) {
    /*避免重复安装(Vue.use内部也会检测一次是否重复安装同一个插件)*/
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  /*保存Vue,同时用于检测是否重复安装*/
  Vue = _Vue
  /*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
  applyMixin(Vue)
}
```

这段install代码做了两件事情,一件是防止Vuex被重复安装,另一件是执行applyMixin,目的是执行vuexInit方法初始化Vuex。Vuex针对Vue1.0与2.0分别进行了不同的处理,如果是Vue1.0,Vuex会将vuexInit方法放入Vue的_init方法中,而对于Vue2.0,则会将vuexinit混淆进Vue的beforeCreate钩子中。来看一下vuexInit的代码。

```javascript
 /*Vuex的init钩子,会存入每一个Vue实例等钩子列表*/
  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      /*存在store其实代表的就是Root节点,直接执行store(function时)或者使用store(非function)*/
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      /*子组件直接从父组件中获取$store,这样就保证了所有组件都公用了全局的同一份store*/
      this.$store = options.parent.$store
    }
  }
```

vuexInit会尝试从options中获取store,如果当前组件是根组件(Root节点),则options中会存在store,直接获取赋值给$store即可。如果当前组件非根组件,则通过options中的parent获取父组件的$store引用。这样一来,所有的组件都获取到了同一份内存地址的Store实例,于是我们可以在每一个组件中通过this.$store愉快地访问全局的Store实例了。

那么,什么是Store实例?

## Store

我们传入到根组件的store,就是Store实例,用Vuex提供的Store方法构造。

```javascript
export default new Vuex.Store({
    strict: true,
    modules: {
        moduleA,
        moduleB
    }
});
```

我们来看一下Store的实现。首先是构造函数。

```javascript
constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    /*
      在浏览器环境下,如果插件还未安装(!Vue即判断是否未安装),则它会自动安装。
      它允许用户在某些情况下避免自动安装。
    */
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    if (process.env.NODE_ENV !== 'production') {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `Store must be called with the new operator.`)
    }

    const {
      /*一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数,可以监听 mutation(用于外部地数据持久化、记录或调试)或者提交 mutation (用于内部数据,例如 websocket 或 某些观察者)*/
      plugins = [],
      /*使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。*/
      strict = false
    } = options

    /*从option中取出state,如果state是function则执行,最终得到一个对象*/
    let {
      state = {}
    } = options
    if (typeof state === 'function') {
      state = state()
    }

    // store internal state
    /* 用来判断严格模式下是否是用mutation修改state的 */
    this._committing = false
    /* 存放action */
    this._actions = Object.create(null)
    /* 存放mutation */
    this._mutations = Object.create(null)
    /* 存放getter */
    this._wrappedGetters = Object.create(null)
    /* module收集器 */
    this._modules = new ModuleCollection(options)
    /* 根据namespace存放module */
    this._modulesNamespaceMap = Object.create(null)
    /* 存放订阅者 */
    this._subscribers = []
    /* 用以实现Watch的Vue实例 */
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    /*将dispatch与commit调用的this绑定为store对象本身,否则在组件内部this.dispatch时的this会指向组件的vm*/
    const store = this
    const { dispatch, commit } = this
    /* 为dispatch与commit绑定this(Store实例本身) */
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    /*严格模式(使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误)*/
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    /*初始化根module,这也同时递归注册了所有子module,收集所有module的getter到_wrappedGetters中去,this._modules.root代表根module才独有保存的Module对象*/
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    /* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */
    resetStoreVM(this, state)

    // apply plugins
    /* 调用插件 */
    plugins.forEach(plugin => plugin(this))

    /* devtool插件 */
    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }
```

Store的构造类除了初始化一些内部变量以外,主要执行了installModule(初始化module)以及resetStoreVM(通过VM使store“响应式”)。

### installModule

installModule的作用主要是为module加上namespace名字空间(如果有)后,注册mutation、action以及getter,同时递归安装所有子module。

```javascript
/*初始化module*/
function installModule (store, rootState, path, module, hot) {
  /* 是否是根module */
  const isRoot = !path.length
  /* 获取module的namespace */
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  /* 如果有namespace则在_modulesNamespaceMap中注册 */
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    /* 获取父级的state */
    const parentState = getNestedState(rootState, path.slice(0, -1))
    /* module的name */
    const moduleName = path[path.length - 1]
    store.`_withCommit`(() => {
      /* 将子module设成响应式的 */
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)

  /* 遍历注册mutation */
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  /* 遍历注册action */
  module.forEachAction((action, key) => {
    const namespacedType = namespace + key
    registerAction(store, namespacedType, action, local)
  })

  /* 遍历注册getter */
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  /* 递归安装mudule */
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}
```

### resetStoreVM

在说resetStoreVM之前,先来看一个小demo。

```javascript
let globalData = {
    d: 'hello world'
};
new Vue({
    data () {
        return {
            $$state: {
                globalData
            }
        }
    }
});

/* modify */
setTimeout(() => {
    globalData.d = 'hi~';
}, 1000);

Vue.prototype.globalData = globalData;

/* 任意模板中 */
<div>{{globalData.d}}</div>
```

上述代码在全局有一个globalData,它被传入一个Vue对象的data中,之后在任意Vue模板中对该变量进行展示,因为此时globalData已经在Vue的prototype上了所以直接通过this.prototype访问,也就是在模板中的{{prototype.d}}。此时,setTimeout在1s之后将globalData.d进行修改,我们发现模板中的globalData.d发生了变化。其实上述部分就是Vuex依赖Vue核心实现数据的“响应式化”。

不熟悉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是如何进行数据双向绑定的。

接着来看代码。

```javascript
/* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */
function resetStoreVM (store, state, hot) {
  /* 存放之前的vm对象 */
  const oldVm = store._vm 

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}

  /* 通过Object.defineProperty为每一个getter方法设置get方法,比如获取this.$store.getters.test的时候获取的是store._vm.test,也就是Vue对象的computed属性 */
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent
  /* Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程中不会报出一切警告 */
  Vue.config.silent = true
  /*  这里new了一个Vue对象,运用Vue内部的响应式实现注册state以及computed*/
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  /* 使能严格模式,保证修改store只能通过mutation */
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    /* 解除旧vm的state的引用,以及销毁旧的Vue对象 */
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}
```

resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法为每一个getter绑定上get方法,这样我们就可以在组件里访问this.$store.getters.test就等同于访问store._vm.test。

```javascript
forEachValue(wrappedGetters, (fn, key) => {
  // use computed to leverage its lazy-caching mechanism
  computed[key] = () => fn(store)
  Object.defineProperty(store.getters, key, {
    get: () => store._vm[key],
    enumerable: true // for local getters
  })
})
```

之后Vuex采用了new一个Vue对象来实现数据的“响应式化”,运用Vue.js内部提供的数据双向绑定功能来实现store的数据与视图的同步更新。

```javascript
store._vm = new Vue({
  data: {
    $$state: state
  },
  computed
})
```

这时候我们访问store._vm.test也就访问了Vue实例中的属性。

这两步执行完以后,我们就可以通过this.$store.getter.test访问vm中的test属性了。

### 严格模式

Vuex的Store构造类的option有一个strict的参数,可以控制Vuex执行严格模式,严格模式下,所有修改state的操作必须通过mutation实现,否则会抛出错误。

```javascript
/* 使能严格模式 */
function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      /* 检测store中的_committing的值,如果是false代表不是通过mutation的方法修改的 */
      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}
```

首先,在严格模式下,Vuex会利用vm的$watch方法来观察$$state,也就是Store的state,在它被修改的时候进入回调。我们发现,回调中只有一句话,用assert断言来检测store._committing,当store._committing为false的时候会触发断言,抛出异常。

我们发现,Store的commit方法中,执行mutation的语句是这样的。

```javascript
this._withCommit(() => {
  entry.forEach(function commitIterator (handler) {
    handler(payload)
  })
})
```

再来看看_withCommit的实现。

```javascript
_withCommit (fn) {
  /* 调用withCommit修改state的值时会将store的committing值置为true,内部会有断言检查该值,在严格模式下只允许使用mutation来修改store中的值,而不允许直接修改store的数值 */
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}
```

我们发现,通过commit(mutation)修改state数据的时候,会在调用mutation方法之前将committing置为true,接下来再通过mutation函数修改state中的数据,这时候触发$watch中的回调断言committing是不会抛出异常的(此时committing为true)。而当我们直接修改state的数据时,触发$watch的回调执行断言,这时committing为false,则会抛出异常。这就是Vuex的严格模式的实现。

接下来我们来看看Store提供的一些API。

### commit([mutation](https://vuex.vuejs.org/zh-cn/mutations.html))

```javascript
/* 调用mutation的commit方法 */
commit (_type, _payload, _options) {
  // check object-style commit
  /* 校验参数 */
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  /* 取出type对应的mutation的方法 */
  const entry = this._mutations[type]
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown mutation type: ${type}`)
    }
    return
  }
  /* 执行mutation中的所有方法 */
  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })
  /* 通知所有订阅者 */
  this._subscribers.forEach(sub => sub(mutation, this.state))

  if (
    process.env.NODE_ENV !== 'production' &&
    options && options.silent
  ) {
    console.warn(
      `[vuex] mutation type: ${type}. Silent option has been removed. ` +
      'Use the filter functionality in the vue-devtools'
    )
  }
}
```

commit方法会根据type找到并调用_mutations中的所有type对应的mutation方法,所以当没有namespace的时候,commit方法会触发所有module中的mutation方法。再执行完所有的mutation之后会执行_subscribers中的所有订阅者。我们来看一下_subscribers是什么。

Store给外部提供了一个subscribe方法,用以注册一个订阅函数,会push到Store实例的_subscribers中,同时返回一个从_subscribers中注销该订阅者的方法。

```javascript
/* 注册一个订阅函数,返回取消订阅的函数 */
subscribe (fn) {
  const subs = this._subscribers
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}
```

在commit结束以后则会调用这些_subscribers中的订阅者,这个订阅者模式提供给外部一个监视state变化的可能。state通过mutation改变时,可以有效补获这些变化。

### dispatch([action](https://vuex.vuejs.org/zh-cn/actions.html))

来看一下dispatch的实现。

```javascript
/* 调用action的dispatch方法 */
dispatch (_type, _payload) {
  // check object-style dispatch
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  /* actions中取出type对应的action */
  const entry = this._actions[type]
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }

  /* 是数组则包装Promise形成一个新的Promise,只有一个则直接返回第0个 */
  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}
```

以及registerAction时候做的事情。

```javascript
/* 遍历注册action */
function registerAction (store, type, handler, local) {
  /* 取出type对应的action */
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    /* 判断是否是Promise */
    if (!isPromise(res)) {
      /* 不是Promise对象的时候转化称Promise对象 */
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      /* 存在devtool插件的时候触发vuex的error给devtool */
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}
```

因为registerAction的时候将push进_actions的action进行了一层封装(wrappedActionHandler),所以我们在进行dispatch的第一个参数中获取state、commit等方法。之后,执行结果res会被进行判断是否是Promise,不是则会进行一层封装,将其转化成Promise对象。dispatch时则从_actions中取出,只有一个的时候直接返回,否则用Promise.all处理再返回。

### watch

```javascript
/* 观察一个getter方法 */
watch (getter, cb, options) {
  if (process.env.NODE_ENV !== 'production') {
    assert(typeof getter === 'function', `store.watch only accepts a function.`)
  }
  return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}
```

熟悉Vue的朋友应该很熟悉watch这个方法。这里采用了比较巧妙的设计,_watcherVM是一个Vue的实例,所以watch就可以直接采用了Vue内部的watch特性提供了一种观察数据getter变动的方法。

### registerModule

```javascript
/* 注册一个动态module,当业务进行异步加载的时候,可以通过该接口进行注册动态module */
registerModule (path, rawModule) {
  /* 转化称Array */
  if (typeof path === 'string') path = [path]

  if (process.env.NODE_ENV !== 'production') {
    assert(Array.isArray(path), `module path must be a string or an Array.`)
    assert(path.length > 0, 'cannot register the root module by using registerModule.')
  }

  /*注册*/
  this._modules.register(path, rawModule)
  /*初始化module*/
  installModule(this, this.state, path, this._modules.get(path))
  // reset store to update getters...
  /* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */
  resetStoreVM(this, this.state)
}
```

registerModule用以注册一个动态模块,也就是在store创建以后再注册模块的时候用该接口。内部实现实际上也只有installModule与resetStoreVM两个步骤,前面已经讲过,这里不再累述。

### unregisterModule

```javascript
 /* 注销一个动态module */
unregisterModule (path) {
  /* 转化称Array */
  if (typeof path === 'string') path = [path]

  if (process.env.NODE_ENV !== 'production') {
    assert(Array.isArray(path), `module path must be a string or an Array.`)
  }

  /*注销*/
  this._modules.unregister(path)
  this._withCommit(() => {
    /* 获取父级的state */
    const parentState = getNestedState(this.state, path.slice(0, -1))
    /* 从父级中删除 */
    Vue.delete(parentState, path[path.length - 1])
  })
  /* 重制store */
  resetStore(this)
}
```

同样,与registerModule对应的方法unregisterModule,动态注销模块。实现方法是先从state中删除模块,然后用resetStore来重制store。

### resetStore

```javascript
/* 重制store */
function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}
```

这里的resetStore其实也就是将store中的_actions等进行初始化以后,重新执行installModule与resetStoreVM来初始化module以及用Vue特性使其“响应式化”,这跟构造函数中的是一致的。

## 插件

Vue提供了一个非常好用的插件[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)

```javascript
/* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件 */
const devtoolHook =
  typeof window !== 'undefined' &&
  window.__VUE_DEVTOOLS_GLOBAL_HOOK__

export default function devtoolPlugin (store) {
  if (!devtoolHook) return

  /* devtoll插件实例存储在store的_devtoolHook上 */
  store._devtoolHook = devtoolHook

  /* 出发vuex的初始化事件,并将store的引用地址传给deltool插件,使插件获取store的实例 */
  devtoolHook.emit('vuex:init', store)

  /* 监听travel-to-state事件 */
  devtoolHook.on('vuex:travel-to-state', targetState => {
    /* 重制state */
    store.replaceState(targetState)
  })

  /* 订阅store的变化 */
  store.subscribe((mutation, state) => {
    devtoolHook.emit('vuex:mutation', mutation, state)
  })
}
```

如果已经安装了该插件,则会在windows对象上暴露一个__VUE_DEVTOOLS_GLOBAL_HOOK__。devtoolHook用在初始化的时候会触发“vuex:init”事件通知插件,然后通过on方法监听“vuex:travel-to-state”事件来重置state。最后通过Store的subscribe方法来添加一个订阅者,在触发commit方法修改mutation数据以后,该订阅者会被通知,从而触发“vuex:mutation”事件。

## 最后

Vuex是一个非常优秀的库,代码量不多且结构清晰,非常适合研究学习其内部实现。最近的一系列源码阅读也使我自己受益匪浅,写这篇文章也希望可以帮助到更多想要学习探索Vuex内部实现原理的同学。

================================================
FILE: docs/Vue事件机制.MarkDown
================================================
## Vue事件API

众所周知,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)。

## 初始化事件

初始化事件在vm上创建一个_events对象,用来存放事件。_events的内容如下:
```javascript
{
    eventName: [func1, func2, func3]
}
```
存放事件名以及对应执行方法。


```javascript
/*初始化事件*/
export function initEvents (vm: Component) {
  /*在vm上创建一个_events对象,用来存放事件。*/
  vm._events = Object.create(null)
  /*这个bool标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
  vm._hasHookEvent = false
  // init parent attached events
  /*初始化父组件attach的事件*/
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
```

## $on

$on方法用来在vm实例上监听一个自定义事件,该事件可用$emit触发。

```javascript
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this

    /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }
```

## $once

$once监听一个只能触发一次的事件,在触发以后会自动移除该事件。

```javascript
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      /*在第一次执行的时候将该事件销毁*/
      vm.$off(event, on)
      /*执行注册的方法*/
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }
```

## $off

$off用来移除自定义事件

```javascript
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    /*如果不传参数则注销所有事件*/
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    /*如果event是数组则递归注销事件*/
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    /*Github:https://github.com/answershuto*/
    /*本身不存在该事件则直接返回*/
    if (!cbs) {
      return vm
    }
    /*如果只传了event参数则注销该event方法下的所有方法*/
    if (arguments.length === 1) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    /*遍历寻找对应方法并删除*/
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }
```

## $emit

$emit用来触发指定的自定义事件。

```javascript
Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      /*将类数组的对象转换成数组*/
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      /*遍历执行*/
      for (let i = 0, l = cbs.length; i < l; i++) {
        cbs[i].apply(vm, args)
      }
    }
    return vm
  }
```

================================================
FILE: docs/Vue组件间通信.MarkDown
================================================
## 什么是Vue组件?

[组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。](https://cn.vuejs.org/v2/guide/components.html)

<br />
<br />

## Vue组件间通信

### 父组件向子组件通信

#### 方法一:props

使用[props](https://cn.vuejs.org/v2/guide/components.html#Prop),父组件可以使用props向子组件传递数据。

父组件vue模板father.vue

```
<template>
    <child :msg="message"></child>
</template>

<script>

import child from './child.vue';

export default {
    components: {
        child
    },
    data () {
        return {
            message: 'father message';
        }
    }
}
</script>
```

子组件vue模板child.vue

```
<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    }
}
</script>
```

<br />

#### 方法二 使用$children

使用[$children](https://cn.vuejs.org/v2/api/#vm-children)可以在父组件中访问子组件。

<br /> 
<br /> 

### 子组件向父组件通信

<br />

#### 方法一:使用[vue事件](https://cn.vuejs.org/v2/guide/components.html#使用-v-on-绑定自定义事件)

父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件。

父组件vue模板father.vue

```
<template>
    <child @msgFunc="func"></child>
</template>

<script>

import child from './child.vue';

export default {
    components: {
        child
    },
    methods: {
        func (msg) {
            console.log(msg);
        }
    }
}
</script>
```

子组件vue模板child.vue

```
<template>
    <button @click="handleClick">点我</button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {
        handleClick () {
            //........
            this.$emit('msgFunc');
        }
    }
}
</script>
```

<br />

#### 方法二: 通过修改父组件传递的props来修改父组件数据

这种方法只能在父组件传递一个引用变量时可以使用,字面变量无法达到相应效果。因为引用变量最终无论是父组件中的数据还是子组件得到的props中的数据都是指向同一块内存地址,所以修改了子组件中props的数据即修改了父组件的数据。

但是并不推荐这么做,并不建议直接修改props的值,如果数据是用于显示修改的,在实际开发中我经常会将其放入data中,在需要回传给父组件的时候再用事件回传数据。这样做保持了组件独立以及解耦,不会因为使用同一份数据而导致数据流异常混乱,只通过特定的接口传递数据来达到修改数据的目的,而内部数据状态由专门的data负责管理。

<br />

#### 方法三:使用$parent

使用[$parent](https://cn.vuejs.org/v2/api/#vm-parent)可以访问父组件的数据。

<br />
<br />

### 非父子组件、兄弟组件之间的数据传递

非父子组件通信,Vue官方推荐[使用一个Vue实例作为中央事件总线](https://cn.vuejs.org/v2/guide/components.html#非父子组件通信)。

Vue内部有一个事件机制,可以参考[源码](https://github.com/vuejs/vue/blob/dev/src/core/instance/events.js)。

$on方法用来监听一个事件。

$emit用来触发一个事件。

```javascript
/*新建一个Vue实例作为中央事件总线*/
let event = new Vue();

/*监听事件*/
event.$on('eventName', (val) => {
    //......do something
});

/*触发事件*/
event.$emit('eventName', 'this is a message.');
```

<br />
<br />

### 多层级父子组件通信:

在Vue1.0中实现了$broadcast与$dispatch两个方法用来向子组件(或父组件)广播(或派发),当子组件(或父组件)上监听了事件并返回true的时候会向爷孙级组件继续广播(或派发)事件。但是这个方法在Vue2.0里面已经被移除了。

之前在学习饿了么的开源组件库[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进行向指定组件名组件定向广播(派发)事件。

其实这两个方法内部实现还是用到的还是$parent以及$children,用以遍历子节点或是逐级向上查询父节点,访问到指定组件名的时候,调用$emit触发指定事件。

<br />
<br />

### 复杂的单页应用数据管理

当应用足够复杂情况下,请使用[vuex](https://cn.vuejs.org/v2/guide/state-management.html)进行数据管理。


================================================
FILE: docs/从template到DOM(Vue.js源码角度看内部运行机制).MarkDown
================================================
## 从new一个Vue对象开始

```javascript
let vm = new Vue({
    el: '#app',
    /*some options*/
});
```

很多同学好奇,在new一个Vue对象的时候,内部究竟发生了什么?

究竟Vue.js是如何将data中的数据渲染到真实的宿主环境中的?

又是如何通过“响应式”修改数据的?

template是如何被编译成真实环境中可用的HTML的?

Vue指令又是如何执行的?

带着这些疑问,我们从Vue的构造类开始看起。

## Vue构造类

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

Vue的构造类只做了一件事情,就是调用_init函数进行初始化

来看一下init的代码

```javascript
Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-init:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    /*一个防止vm实例自身被观察的标志位*/
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    /*初始化生命周期*/
    initLifecycle(vm)
    /*初始化事件*/
    initEvents(vm)
    /*初始化render*/
    initRender(vm)
    /*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    /*初始化props、methods、data、computed与watch*/
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    /*调用created钩子函数并且触发created钩子事件*/
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      /*格式化组件名*/
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      /*挂载组件*/
      vm.$mount(vm.$options.el)
    }
  }
```

_init主要做了这两件事:

1.初始化(包括生命周期、事件、render函数、state等)。

2.$mount组件。

在生命钩子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)。

```javascript
/*初始化props、methods、data、computed与watch*/
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  /*初始化props*/
  if (opts.props) initProps(vm, opts.props)
  /*初始化方法*/
  if (opts.methods) initMethods(vm, opts.methods)
  /*初始化data*/
  if (opts.data) {
    initData(vm)
  } else {
    /*该组件没有data的时候绑定一个空对象*/
    observe(vm._data = {}, true /* asRootData */)
  }
  /*初始化computed*/
  if (opts.computed) initComputed(vm, opts.computed)
  /*初始化watchers*/
  if (opts.watch) initWatch(vm, opts.watch)
}

```

## 双向绑定

以initData为例,对option的data的数据进行双向绑定Oberver,其他option参数双向绑定的核心原理是一致的。

```javascript
function initData (vm: Component) {

  /*得到data数据*/
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

  /*判断是否是对象*/
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }

  // proxy data on instance
  /*遍历data对象*/
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length

  //遍历data中的数据
  while (i--) {
    /*保证data中的key不与props中的key重复,props优先,如果有冲突会产生warning*/
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(keys[i])) {
      /*判断是否是保留字段*/

      /*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/
      proxy(vm, `_data`, keys[i])
    }
  }
  /*Github:https://github.com/answershuto*/
  // observe data
  /*从这里开始我们要observe了,开始对数据进行绑定,这里有尤大大的注释asRootData,这步作为根数据,下面会进行递归observe进行对深层对象的绑定。*/
  observe(data, true /* asRootData */)
}
```

observe会通过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对象更新视图。

如果是修改一个数组的成员,该成员是一个对象,那只需要递归对数组的成员进行双向绑定即可。但这时候出现了一个问题,如果我们进行pop、push等操作的时候,push进去的对象根本没有进行过双向绑定,更别说pop了,那么我们如何监听数组的这些变化呢?
Vue.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)。

```javascript
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    //.......

    if (Array.isArray(value)) {
      /*
          如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。
          这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/
        : copyAugment   /*定义(覆盖)目标对象或数组的某一个方法*/
      augment(value, arrayMethods, arrayKeys)

      /*如果是数组则需要遍历数组的每一个成员进行observe*/
      this.observeArray(value)
    } else {
      /*如果是对象则直接walk进行绑定*/
      this.walk(value)
    }
  }
}

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
 /*直接覆盖原型的方法来修改目标对象或数组*/
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
/*定义(覆盖)目标对象或数组的某一个方法*/
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
```

```javascript
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

/*取得原生数组的原型*/
const arrayProto = Array.prototype
/*创建一个新的数组对象,修改该对象上的数组的七个方法,防止污染原生数组方法*/
export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */
 /*这里重写了数组的这些方法,在保证不污染原生数组原型的情况下重写数组的这些方法,截获数组的成员发生的变化,执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/
[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  /*将数组的原生方法缓存起来,后面要调用*/
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    /*调用原生的数组方法*/
    const result = original.apply(this, args)

    /*数组新插入的元素需要重新进行observe才能响应式*/
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
      
    // notify change
    /*dep通知所有注册的观察者进行响应式处理*/
    ob.dep.notify()
    return result
  })
})

```

从数组的原型新建一个Object.create(arrayProto)对象,通过修改此原型可以保证原生数组方法不被污染。如果当前浏览器支持__proto__这个属性的话就可以直接覆盖该属性使数组对象具有了重写后的数组方法。如果浏览器没有该属性,则必须通过遍历def所有需要重写的数组方法,这种方法效率较低,所以优先使用第一种。

在保证不污染不覆盖数组原生方法添加监听,主要做了两个操作,第一是通知所有注册的观察者进行响应式处理,第二是如果是添加成员的操作,需要对新成员进行observe。

但是修改了数组的原生方法以后我们还是没法像原生数组一样直接通过数组的下标或者设置length来修改数组,可以通过[Vue.set以及splice方法](https://cn.vuejs.org/v2/guide/list.html#%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84)。


对于更具体的讲解数据双向绑定以及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)。

## template编译

在$mount过程中,如果是使用独立构建,则会在此过程中将template编译成render function。当然,你也可以采用运行时构建。具体参考[运行时-编译器-vs-只包含运行时](https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时)。

template是如何被编译成render function的呢?

```javascript
function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  /*parse解析得到ast树*/
  const ast = parse(template.trim(), options)
  /*
    将AST树进行优化
    优化的目标:生成模板AST树,检测不需要进行DOM改变的静态子树。
    一旦检测到这些静态树,我们就能做以下这些事情:
    1.把它们变成常数,这样我们就再也不需要每次重新渲染时创建新的节点了。
    2.在patch的过程中直接跳过。
 */
  optimize(ast, options)
  /*根据ast树生成所需的code(内部包含render与staticRenderFns)*/
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}
```

baseCompile首先会将模板template进行parse得到一个AST语法树,再通过optimize做一些优化,最后通过generate得到render以及staticRenderFns。

### parse

parse的源码可以参见[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)。

parse会用正则等方式解析template模板中的指令、class、style等数据,形成AST语法树。

### optimize

optimize的主要作用是标记static静态节点,这是Vue在编译过程中的一处优化,后面当update更新界面时,会有一个patch的过程,diff算法会直接跳过静态节点,从而减少了比较的过程,优化了patch的性能。

### generate

generate是将AST语法树转化成render funtion字符串的过程,得到结果是render的字符串以及staticRenderFns字符串。

具体的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)。


## Watcher到视图

Watcher对象会通过调用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)。

```javascript
updateComponent = () => {
    vm._update(vm._render(), hydrating)
}
```

updateComponent就执行一句话,_render函数会返回一个新的Vnode节点,传入_update中与旧的VNode对象进行对比,经过一个patch的过程得到两个VNode节点的差异,最后将这些差异渲染到真实环境形成视图。

什么是VNode?

## VNode

在刀耕火种的年代,我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。

那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树,在修改抽象树数据后将抽象树转化成真实DOM重绘到页面上呢?于是虚拟DOM出现了,它是真实DOM的一层抽象,用属性描述真实DOM的各个特性。当它发生变化的时候,就会去修改视图。

可以想象,最简单粗暴的方法就是将整个DOM结构用innerHTML修改到页面上,但是这样进行重绘整个视图层是相当消耗性能的,我们是不是可以每次只更新它的修改呢?所以Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,以VNode节点模拟真实DOM,可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作,在这过程中都不需要操作真实DOM,只需要操作JavaScript对象后只对差异修改,相对于整块的innerHTML的粗暴式修改,大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位,再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作,大大提高了性能。

Vue就使用了这样的抽象节点VNode,它是对真实DOM的一层抽象,而不依赖某个平台,它可以是浏览器平台,也可以是weex,甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作,这也为前后端同构提供了可能。

先来看一下Vue.js源码中对VNode类的定义。

```javascript
export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    /*当前节点的标签名*/
    this.tag = tag
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
    /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.functionalContext = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
    /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为跟节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    this.isOnce = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}
```

这是一个最基础的VNode节点,作为其他派生VNode类的基类,里面定义了下面这些数据。

tag: 当前节点的标签名

data: 当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息

children: 当前节点的子节点,是一个数组

text: 当前节点的文本

elm: 当前虚拟节点对应的真实dom节点

ns: 当前节点的名字空间

context: 当前节点的编译作用域

functionalContext: 函数化组件作用域

key: 节点的key属性,被当作节点的标志,用以优化

componentOptions: 组件的option选项

componentInstance: 当前节点对应的组件的实例

parent: 当前节点的父节点

raw: 简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false

isStatic: 是否为静态节点

isRootInsert: 是否作为跟节点插入

isComment: 是否为注释节点

isCloned: 是否为克隆节点

isOnce: 是否有v-once指令

---

打个比方,比如说我现在有这么一个VNode树

```JavaScript
{
    tag: 'div'
    data: {
        class: 'test'
    },
    children: [
        {
            tag: 'span',
            data: {
                class: 'demo'
            }
            text: 'hello,VNode'
        }
    ]
}
```

渲染之后的结果就是这样的

```html
<div class="test">
    <span class="demo">hello,VNode</span>
</div>
```

更多操作VNode的方法,请参考[《VNode节点》](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)。

## patch

最后_update会将新旧两个VNode进行一次patch的过程,得出两个VNode最小的差异,然后将这些差异渲染到视图上。

首先说一下patch的核心diff算法,diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。

![img](https://i.loli.net/2017/08/27/59a23cfca50f3.png)

![img](https://i.loli.net/2017/08/27/59a2419a3c617.png)

这两张图代表旧的VNode与新VNode进行patch的过程,他们只是在同层级的VNode之间进行比较得到变化(第二张图中相同颜色的方块代表互相进行比较的VNode节点),然后修改变化的视图,所以十分高效。

在patch的过程中,如果两个VNode被认为是同一个VNode(sameVnode),则会进行深度的比较,得出最小差异,否则直接删除旧有DOM节点,创建新的DOM节点。

什么是sameVnode?

我们来看一下sameVnode的实现。

```JavaScript
/*
  判断两个VNode节点是否是同一个节点,需要满足以下条件
  key相同
  tag(当前节点的标签名)相同
  isComment(是否为注释节点)相同
  是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息)都有定义
  当标签是<input>的时候,type必须相同
*/
function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
  )
}

// Some browsers do not support dynamically changing type for <input>
// so they need to be treated as different nodes
/*
  判断当标签是<input>的时候,type是否相同
  某些浏览器不支持动态修改<input>类型,所以他们被视为不同类型
*/
function sameInputType (a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB
}
```

当两个VNode的tag、key、isComment都相同,并且同时定义或未定义data的时候,且如果标签为input则type必须相同。这时候这两个VNode则算sameVnode,可以直接进行patchVnode操作。

patchVnode的规则是这样的:

1.如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentInstance即可。

2.新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。

3.如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。

4.当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。

5.当新老节点都无子节点的时候,只是文本的替换。

## updateChildren

```JavaScript
  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, elmToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        /*前四种情况其实是指定key的时候,判定为同一个VNode,则直接patchVnode即可,分别比较oldCh以及newCh的两头节点2*2=4种情况*/
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        /*
          生成一个key与旧VNode的key对应的哈希表(只有第一次进来undefined的时候会生成,也为后面检测重复的key值做铺垫)
          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  
          结果生成{key0: 0, key1: 1, key2: 2}
        */
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld(即第几个节点,下标)*/
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
        if (isUndef(idxInOld)) { // New element
          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          newStartVnode = newCh[++newStartIdx]
        } else {
          /*获取同key的老节点*/
          elmToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !elmToMove) {
            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的DOM中,提示可能存在重复的key,确保v-for的时候item有唯一的key值*/
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }
          if (sameVnode(elmToMove, newStartVnode)) {
            /*Github:https://github.com/answershuto*/
            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
            /*因为已经patchVnode进去了,所以将这个老节点赋值undefined,之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/
            oldCh[idxInOld] = undefined
            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实DOM节点前面*/
            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          } else {
            // same key but different element. treat as new element
            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候(比如说tag不一样或者是有不一样type的input标签),创建一个新的节点*/
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          }
        }
      }
    }
    if (oldStartIdx > oldEndIdx) {
      /*全部比较完成以后,发现oldStartIdx > oldEndIdx的话,说明老节点已经遍历完了,新节点比老节点多,所以这时候多出来的新节点需要一个一个创建出来加入到真实DOM中*/
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      /*如果全部比较完成以后发现newStartIdx > newEndIdx,则说明新节点已经遍历完了,老节点多余新节点,这个时候需要将多余的老节点从真实DOM中移除*/
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }
```

直接看源码可能比较难以捋清其中的关系,我们通过图来看一下。

![img](https://i.loli.net/2017/08/28/59a4015bb2765.png)

首先,在新老两个VNode节点的左右头尾两侧都有一个变量标记,在遍历过程中这几个变量都会向中间靠拢。当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环。

索引与VNode节点的对应关系:
oldStartIdx => oldStartVnode
oldEndIdx => oldEndVnode
newStartIdx => newStartVnode
newEndIdx => newEndVnode

在遍历中,如果存在key,并且满足sameVnode,会将该DOM节点进行复用,否则则会创建一个新的DOM节点。

首先,oldStartVnode、oldEndVnode与newStartVnode、newEndVnode两两比较一共有2*2=4种比较方法。

当新老VNode节点的start或者end满足sameVnode时,也就是sameVnode(oldStartVnode, newStartVnode)或者sameVnode(oldEndVnode, newEndVnode),直接将该VNode节点进行patchVnode即可。

![img](https://i.loli.net/2017/08/28/59a40c12c1655.png)

如果oldStartVnode与newEndVnode满足sameVnode,即sameVnode(oldStartVnode, newEndVnode)。

这时候说明oldStartVnode已经跑到了oldEndVnode后面去了,进行patchVnode的同时还需要将真实DOM节点移动到oldEndVnode的后面。

![img](https://ooo.0o0.ooo/2017/08/28/59a4214784979.png)

如果oldEndVnode与newStartVnode满足sameVnode,即sameVnode(oldEndVnode, newStartVnode)。

这说明oldEndVnode跑到了oldStartVnode的前面,进行patchVnode的同时真实的DOM节点移动到了oldStartVnode的前面。

![img](https://i.loli.net/2017/08/29/59a4c70685d12.png)

如果以上情况均不符合,则通过createKeyToOldIdx会得到一个oldKeyToIdx,里面存放了一个key为旧的VNode,value为对应index序列的哈希表。从这个哈希表中可以找到是否有与newStartVnode一致key的旧的VNode节点,如果同时满足sameVnode,patchVnode的同时会将这个真实DOM(elmToMove)移动到oldStartVnode对应的真实DOM的前面。

![img](https://i.loli.net/2017/08/29/59a4d7552d299.png)

当然也有可能newStartVnode在旧的VNode节点找不到一致的key,或者是即便key相同却不是sameVnode,这个时候会调用createElm创建一个新的DOM节点。

![img](https://i.loli.net/2017/08/29/59a4de0fa4dba.png)

到这里循环已经结束了,那么剩下我们还需要处理多余或者不够的真实DOM节点。

1.当结束时oldStartIdx > oldEndIdx,这个时候老的VNode节点已经遍历完了,但是新的节点还没有。说明了新的VNode节点实际上比老的VNode节点多,也就是比真实DOM多,需要将剩下的(也就是新增的)VNode节点插入到真实DOM节点中去,此时调用addVnodes(批量调用createElm的接口将这些节点加入到真实DOM中去)。

![img](https://i.loli.net/2017/08/29/59a509f0d1788.png)

2。同理,当newStartIdx > newEndIdx时,新的VNode节点已经遍历完了,但是老的节点还有剩余,说明真实DOM节点多余了,需要从文档中删除,这时候调用removeVnodes将这些多余的真实DOM删除。

![img](https://i.loli.net/2017/08/29/59a4f389b98cb.png)

更详细的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)。

## 映射到真实DOM

由于Vue使用了虚拟DOM,所以虚拟DOM可以在任何支持JavaScript语言的平台上操作,譬如说目前Vue支持的浏览器平台或是weex,在虚拟DOM的实现上是一致的。那么最后虚拟DOM如何映射到真实的DOM节点上呢?

Vue为平台做了一层适配层,浏览器平台见[/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节点的时候,只需要调用这些适配层的接口即可,而内部实现则不需要关心,它会根据平台的改变而改变。

现在又出现了一个问题,我们只是将虚拟DOM映射成了真实的DOM。那如何给这些DOM加入attr、class、style等DOM属性呢?

这要依赖于虚拟DOM的生命钩子。虚拟DOM提供了如下的钩子函数,分别在不同的时期会进行调用。

```JavaScript
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']

/*构建cbs回调函数,web平台上见/platforms/web/runtime/modules*/
  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
```

同理,也会根据不同平台有自己不同的实现,我们这里以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属性进行操作。

以attr为例,代码很简单。

```JavaScript
/* @flow */

import { isIE9 } from 'core/util/env'

import {
  extend,
  isDef,
  isUndef
} from 'shared/util'

import {
  isXlink,
  xlinkNS,
  getXlinkProp,
  isBooleanAttr,
  isEnumeratedAttr,
  isFalsyAttrValue
} from 'web/util/index'

/*更新attr*/
function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  /*如果旧的以及新的VNode节点均没有attr属性,则直接返回*/
  if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
    return
  }
  let key, cur, old
  /*VNode节点对应的Dom实例*/
  const elm = vnode.elm
  /*旧VNode节点的attr*/
  const oldAttrs = oldVnode.data.attrs || {}
  /*新VNode节点的attr*/
  let attrs: any = vnode.data.attrs || {}
  // clone observed objects, as the user probably wants to mutate it
  /*如果新的VNode的attr已经有__ob__(代表已经被Observe处理过了), 进行深拷贝*/
  if (isDef(attrs.__ob__)) {
    attrs = vnode.data.attrs = extend({}, attrs)
  }

  /*遍历attr,不一致则替换*/
  for (key in attrs) {
    cur = attrs[key]
    old = oldAttrs[key]
    if (old !== cur) {
      setAttr(elm, key, cur)
    }
  }
  // #4391: in IE9, setting type can reset value for input[type=radio]
  /* istanbul ignore if */
  if (isIE9 && attrs.value !== oldAttrs.value) {
    setAttr(elm, 'value', attrs.value)
  }
  for (key in oldAttrs) {
    if (isUndef(attrs[key])) {
      if (isXlink(key)) {
        elm.removeAttributeNS(xlinkNS, getXlinkProp(key))
      } else if (!isEnumeratedAttr(key)) {
        elm.removeAttribute(key)
      }
    }
  }
}

/*设置attr*/
function setAttr (el: Element, key: string, value: any) {
  if (isBooleanAttr(key)) {
    // set attribute for blank value
    // e.g. <option disabled>Select one</option>
    if (isFalsyAttrValue(value)) {
      el.removeAttribute(key)
    } else {
      el.setAttribute(key, key)
    }
  } else if (isEnumeratedAttr(key)) {
    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')
  } else if (isXlink(key)) {
    if (isFalsyAttrValue(value)) {
      el.removeAttributeNS(xlinkNS, getXlinkProp(key))
    } else {
      el.setAttributeNS(xlinkNS, key, value)
    }
  } else {
    if (isFalsyAttrValue(value)) {
      el.removeAttribute(key)
    } else {
      el.setAttribute(key, value)
    }
  }
}

export default {
  create: updateAttrs,
  update: updateAttrs
}

```

attr只需要在create以及update钩子被调用时更新DOM的attr属性即可。

## 最后

至此,我们已经从template到真实DOM的整个过程梳理完了。现在再去看这张图,是不是更清晰了呢?

![](https://cn.vuejs.org/images/data.png)


================================================
FILE: docs/从源码角度再看数据绑定.MarkDown
================================================
## 数据绑定原理

前面已经讲过Vue数据绑定的原理了,现在从源码来看一下数据绑定在Vue中是如何实现的。

首先看一下Vue.js官网介绍响应式原理的这张图。

![](https://cn.vuejs.org/images/data.png)

这张图比较清晰地展示了整个流程,首先通过一次渲染操作触发Data的getter(这里保证只有视图中需要被用到的data才会触发getter)进行依赖收集,这时候其实Watcher与data可以看成一种被绑定的状态(实际上是data的闭包中有一个Deps订阅者,在修改的时候会通知所有的Watcher观察者),在data发生变化的时候会触发它的setter,setter通知Watcher,Watcher进行回调通知组件重新渲染的函数,之后根据diff算法来决定是否发生视图的更新。

Vue在初始化组件数据时,在生命周期的[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)的处理。

## initData

这里来讲一下[initData](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L107),可以参考源码instance下的state.js文件,下面所有的中文注释都是我加的,英文注释是尤大加的,请不要忽略英文注释,英文注释都讲到了比较关键或者晦涩难懂的点。

加注释版的vue源码也可以直接通过[传送门](https://github.com/answershuto/learnVue/tree/master/vue-src)查看,这些是我在阅读Vue源码过程中加的注释,持续更新中。

initData主要是初始化data中的数据,将数据进行Observer,监听数据的变化,其他的监视原理一致,这里以data为例。

```javascript
function initData (vm: Component) {

  /*得到data数据*/
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

  /*判断是否是对象*/
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }

  // proxy data on instance
  /*遍历data对象*/
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length

  //遍历data中的数据
  while (i--) {
    /*保证data中的key不与props中的key重复,props优先,如果有冲突会产生warning*/
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(keys[i])) {
      /*判断是否是保留字段*/

      /*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/
      proxy(vm, `_data`, keys[i])
    }
  }
  /*Github:https://github.com/answershuto*/
  // observe data
  /*从这里开始我们要observe了,开始对数据进行绑定,这里有尤大大的注释asRootData,这步作为根数据,下面会进行递归observe进行对深层对象的绑定。*/
  observe(data, true /* asRootData */)
}
```

其实这段代码主要做了两件事,一是将_data上面的数据代理到vm上,另一件事通过observe将所有数据变成observable。

## proxy

接下来看一下proxy代理。

```javascript
/*添加代理*/
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
```

这里比较好理解,通过proxy函数将data上面的数据代理到vm上,这样就可以用app.text代替app._data.text了。

## observe

接下来是[observe](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L106),这个函数定义在core文件下observer的index.js文件中。

```javascript
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
 /*
 尝试创建一个Observer实例(__ob__),如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  /*判断是否是一个对象*/
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void

  /*这里用__ob__这个属性来判断是否已经有Observer实例,如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性,如果已有Observer实例则直接返回该Observer实例*/
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (

    /*这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。*/
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {

    /*如果是根数据则计数,后面Observer中的observe的asRootData非true*/
    ob.vmCount++
  }
  return ob
}

```

Vue的响应式数据都会有一个__ob__的属性作为标记,里面存放了该属性的观察器,也就是Observer的实例,防止重复绑定。

## Observer

接下来看一下新建的[Observer](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L34)。Observer的作用就是遍历对象的所有属性将其进行双向绑定。

```javascript
/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object's property keys into getter/setters that
 * collect dependencies and dispatches updates.
 */
export class  {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0

    /*
    将Observer实例绑定到data的__ob__属性上面去,之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了,def方法定义可以参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16
    */
    def(value, '__ob__', this)
    if (Array.isArray(value)) {

      /*
          如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。
          这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/
        : copyAugment   /*定义(覆盖)目标对象或数组的某一个方法*/
      augment(value, arrayMethods, arrayKeys)
      /*Github:https://github.com/answershuto*/
      /*如果是数组则需要遍历数组的每一个成员进行observe*/
      this.observeArray(value)
    } else {

      /*如果是对象则直接walk进行绑定*/
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)

    /*walk方法会遍历对象的每一个属性进行defineReactive绑定*/
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {

    /*数组需要遍历每一个成员进行observe*/
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
```

Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定上方法。

如果是修改一个数组的成员,该成员是一个对象,那只需要递归对数组的成员进行双向绑定即可。但这时候出现了一个问题:如果我们进行pop、push等操作的时候,push进去的对象根本没有进行过双向绑定,更别说pop了,那么我们如何监听数组的这些变化呢?
Vue.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)。

```javascript
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    //.......

    if (Array.isArray(value)) {
      /*
          如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。
          这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/
        : copyAugment   /*定义(覆盖)目标对象或数组的某一个方法*/
      augment(value, arrayMethods, arrayKeys)

      /*如果是数组则需要遍历数组的每一个成员进行observe*/
      this.observeArray(value)
    } else {
      /*如果是对象则直接walk进行绑定*/
      this.walk(value)
    }
  }
}

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
 /*直接覆盖原型的方法来修改目标对象或数组*/
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
/*定义(覆盖)目标对象或数组的某一个方法*/
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
```

```javascript
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

/*取得原生数组的原型*/
const arrayProto = Array.prototype
/*创建一个新的数组对象,修改该对象上的数组的七个方法,防止污染原生数组方法*/
export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */
 /*这里重写了数组的这些方法,在保证不污染原生数组原型的情况下重写数组的这些方法,截获数组的成员发生的变化,执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/
[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  /*将数组的原生方法缓存起来,后面要调用*/
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    /*调用原生的数组方法*/
    const result = original.apply(this, args)

    /*数组新插入的元素需要重新进行observe才能响应式*/
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)

    // notify change
    /*dep通知所有注册的观察者进行响应式处理*/
    ob.dep.notify()
    return result
  })
})

```

从数组的原型新建一个Object.create(arrayProto)对象,通过修改此原型可以保证原生数组方法不被污染。如果当前浏览器支持__proto__这个属性的话就可以直接覆盖该属性则使数组对象具有了重写后的数组方法。如果没有该属性的浏览器,则必须通过遍历def所有需要重写的数组方法,这种方法效率较低,所以优先使用第一种。

在保证不污染不覆盖数组原生方法添加监听,主要做了两个操作,第一是通知所有注册的观察者进行响应式处理,第二是如果是添加成员的操作,需要对新成员进行observe。

但是修改了数组的原生方法以后我们还是没法像原生数组一样直接通过数组的下标或者设置length来修改数组,可以通过[Vue.set以及splice方法](https://cn.vuejs.org/v2/guide/list.html#%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84)。

## Watcher

[Watcher](https://github.com/vuejs/vue/blob/dev/src/core/observer/watcher.js#L24)是一个观察者对象。依赖收集以后Watcher对象会被保存在Deps中,数据变动的时候会由Deps通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。

```javascript
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    /*_watchers存放订阅者实例*/
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    /*把表达式expOrFn解析成getter*/
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
   /*获得getter的值并且重新进行依赖收集*/
  get () {
    /*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
    pushTarget(this)
    let value
    const vm = this.vm

    /*
      执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
      在将Dep.target设置为自身观察者实例以后,执行getter操作。
      譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
      那么在执行getter的时候就会触发a跟c两个数据的getter函数,
      在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
      将该观察者对象放入闭包中的Dep的subs中去。
    */
    if (this.user) {
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      }
    } else {
      value = this.getter.call(vm, vm)
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    /*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
    if (this.deep) {
      /*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/
      traverse(value)
    }

    /*将观察者实例从target栈中取出并设置给Dep.target*/
    popTarget()
    this.cleanupDeps()
    return value
  }

  /**
   * Add a dependency to this directive.
   */
   /*添加一个依赖关系到Deps集合中*/
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
   /*清理依赖收集*/
  cleanupDeps () {
    /*移除所有观察者对象*/
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
   /*
      调度者接口,当依赖发生改变的时候进行回调。
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      /*同步则执行run直接渲染视图*/
      this.run()
    } else {
      /*异步推送到观察者队列中,由调度者调用。*/
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
   /*
      调度者工作接口,将被调度者回调。
    */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        /*
            即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。
        */
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        /*设置新的值*/
        this.value = value

        /*触发回调渲染视图*/
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
   /*获取观察者的值*/
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
   /*收集该watcher的所有deps依赖*/
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
   /*将自身从所有依赖收集订阅列表删除*/
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      /*从vm实例的观察者列表中将自身移除,由于该操作比较耗费资源,所以如果vm实例正在被销毁则跳过该步骤。*/
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
```

## Dep

来看看[Dep](https://github.com/vuejs/vue/blob/dev/src/core/observer/dep.js#L12)类。其实Dep就是一个发布者,可以订阅多个观察者,依赖收集之后Deps中会存在一个或多个Watcher对象,在数据变更的时候通知所有的Watcher。

```javascript
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  /*添加一个观察者对象*/
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  /*移除一个观察者对象*/
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  /*依赖收集,当存在Dep.target的时候添加观察者对象*/
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  /*通知所有订阅者*/
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/
```

## defineReactive

接下来是[defineReactive](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L131)。defineReactive的作用是通过Object.defineProperty为数据定义上getter\setter方法,进行依赖收集后闭包中的Deps会存放Watcher对象。触发setter改变数据的时候会通知Deps订阅者通知所有的Watcher观察者对象进行试图的更新。

```javascript
/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  /*在闭包中定义一个dep对象*/
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  /*如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。*/
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  /*对象的子对象递归进行observe并返回子节点的Observer对象*/
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {

      /*如果原本对象拥有getter方法则执行*/
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {

        /*进行依赖收集*/
        dep.depend()
        if (childOb) {

          /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {

          /*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {

      /*通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作*/
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {

        /*如果原本对象拥有setter方法则执行setter*/
        setter.call(obj, newVal)
      } else {
        val = newVal
      }

      /*新的值需要重新进行observe,保证数据响应式*/
      childOb = observe(newVal)

      /*dep对象通知所有的观察者*/
      dep.notify()
    }
  })
}
```

现在再来看这张图是不是更清晰了呢?

![](https://cn.vuejs.org/images/data.png)


================================================
FILE: docs/依赖收集.MarkDown
================================================
## 为什么要依赖收集

先看下面这段代码

```javascript
new Vue({
    template: 
        `<div>
            <span>text1:</span> {{text1}}
            <span>text2:</span> {{text2}}
        <div>`,
    data: {
        text1: 'text1',
        text2: 'text2',
        text3: 'text3'
    }
});
```

按照之前[《响应式原理》](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导致重新执行渲染,这显然不正确。

## 先说说Dep

当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。

定义一个依赖收集类Dep。

```javascript
class Dep {
    constructor () {
        this.subs = [];
    }

    addSub (sub: Watcher) {
        this.subs.push(sub)
    }

    removeSub (sub: Watcher) {
        remove(this.subs, sub)
    }
    /*Github:https://github.com/answershuto*/
    notify () {
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
        }
    }
}
function remove (arr, item) {
    if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}
```

## Watcher

订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。

```javascript
class Watcher {
    constructor (vm, expOrFn, cb, options) {
        this.cb = cb;
        this.vm = vm;

        /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/
        Dep.target = this;
        /*Github:https://github.com/answershuto*/
        /*触发渲染操作进行依赖收集*/
        this.cb.call(this.vm);
    }

    update () {
        this.cb.call(this.vm);
    }
}
```

## 开始依赖收集

```javascript
class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data, options.render);
        let watcher = new Watcher(this, );
    }
}

function defineReactive (obj, key, val, cb) {
    /*在闭包内存储一个Dep对象*/
    const dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
            if (Dep.target) {
                /*Watcher对象存在全局的Dep.target中*/
                dep.addSub(Dep.target);
            }
        },
        set:newVal=> {
            /*只有之前addSub中的函数才会触发*/
            dep.notify();
        }
    })
}

Dep.target = null;
```

将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。


================================================
FILE: docs/响应式原理.MarkDown
================================================
## 关于Vue.js

Vue.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方法就是一个观察者,在数据变更的时候通知订阅者更新视图。


## 将数据data变成可观察(observable)的

那么Vue是如何将所有data下面的所有属性变成可观察的(observable)呢?

```javascript
function observe(value, cb) {
    Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

function defineReactive (obj, key, val, cb) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
            /*....依赖收集等....*/
            /*Github:https://github.com/answershuto*/
            return val
        },
        set:newVal=> {
            val = newVal;
            cb();/*订阅者收到消息的回调*/
        }
    })
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observe(this._data, options.render)
    }
}

let app = new Vue({
    el: '#app',
    data: {
        text: 'text',
        text2: 'text2'
    },
    render(){
        console.log("render");
    }
})
```

为了便于理解,首先考虑一种最简单的情况,不考虑数组等情况,代码如上所示。在[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)。

那么问题来了,需要对app._data.text操作才会触发set。为了偷懒,我们需要一种方便的方法通过app.text直接设置就能触发set对视图进行重绘。那么就需要用到代理。

## 代理

我们可以在Vue的构造函数constructor中为data执行一个代理[proxy](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L33)。这样我们就把data上面的属性代理到了vm实例上。

```javascript
_proxy.call(this, options.data);/*构造函数中*/

/*代理*/
function _proxy (data) {
    const that = this;
    Object.keys(data).forEach(key => {
        Object.defineProperty(that, key, {
            configurable: true,
            enumerable: true,
            get: function proxyGetter () {
                return that._data[key];
            },
            set: function proxySetter (val) {
                that._data[key] = val;
            }
        })
    });
}
```

我们就可以用app.text代替app._data.text了。


================================================
FILE: docs/聊聊Vue的template编译.MarkDown
================================================
## $mount

首先看一下mount的代码

```javascript
/*把原本不带编译的$mount方法保存下来,在最后会调用。*/
const mount = Vue.prototype.$mount
/*挂载组件,带模板编译*/
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  /*处理模板templete,编译成render函数,render不存在的时候才会编译template,否则优先使用render*/
  if (!options.render) {
    let template = options.template
    /*template存在的时候取template,不存在的时候取el的outerHTML*/
    if (template) {
      /*当template是字符串的时候*/
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        /*当template为DOM节点的时候*/
        template = template.innerHTML
      } else {
        /*报错*/
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      /*获取element的outerHTML*/
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      /*将template编译成render函数,这里会有render以及staticRenderFns两个返回,这是vue的编译时优化,static静态不需要在VNode更新时进行patch,优化性能*/
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  /*Github:https://github.com/answershuto*/
  /*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/
  return mount.call(this, el, hydrating)
}
```

通过mount代码我们可以看到,在mount的过程中,如果render函数不存在(render函数存在会优先使用render)会将template进行compileToFunctions得到render以及staticRenderFns。譬如说手写组件时加入了template的情况都会在运行时进行编译。而render function在运行后会返回VNode节点,供页面的渲染以及在update的时候patch。接下来我们来看一下template是如何编译的。

## 一些基础

首先,template会被编译成AST,那么AST是什么?

在计算机科学中,抽象语法树(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)。

AST会经过generate得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,具体定义如下:

```javascript
export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  /*Github:https://github.com/answershuto*/
  
  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    /*当前节点的标签名*/
    this.tag = tag
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
    /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.functionalContext = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
    /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为跟节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    this.isOnce = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}
```

关于VNode的一些细节,请参考[VNode节点](https://github.com/answershuto/learnVue/blob/master/docs/VNode%E8%8A%82%E7%82%B9.MarkDown)。

## createCompiler

createCompiler用以创建编译器,返回值是compile以及compileToFunctions。compile是一个编译器,它会将传入的template转换成对应的AST、render函数以及staticRenderFns函数。而compileToFunctions则是带缓存的编译器,同时staticRenderFns以及render函数会被转换成Funtion对象。

因为不同平台有一些不同的options,所以createCompiler会根据平台区分传入一个baseOptions,会与compile本身传入的options合并得到最终的finalOptions。

## compileToFunctions

首先还是贴一下compileToFunctions的代码。

```javascript
  /*带缓存的编译器,同时staticRenderFns以及render函数会被转换成Funtion对象*/
  function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = options || {}

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {
      // detect possible CSP restriction
      try {
        new Function('return 1')
      } catch (e) {
        if (e.toString().match(/unsafe-eval|CSP/)) {
          warn(
            'It seems you are using the standalone build of Vue.js in an ' +
            'environment with Content Security Policy that prohibits unsafe-eval. ' +
            'The template compiler cannot work in this environment. Consider ' +
            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
            'templates into render functions.'
          )
        }
      }
    }
    /*Github:https://github.com/answershuto*/
    // check cache
    /*有缓存的时候直接取出缓存中的结果即可*/
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (functionCompileCache[key]) {
      return functionCompileCache[key]
    }

    // compile
    /*编译*/
    const compiled = compile(template, options)

    // check compilation errors/tips
    if (process.env.NODE_ENV !== 'production') {
      if (compiled.errors && compiled.errors.length) {
        warn(
          `Error compiling template:\n\n${template}\n\n` +
          compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
          vm
        )
      }
      if (compiled.tips && compiled.tips.length) {
        compiled.tips.forEach(msg => tip(msg, vm))
      }
    }

    // turn code into functions
    const res = {}
    const fnGenErrors = []
    /*将render转换成Funtion对象*/
    res.render = makeFunction(compiled.render, fnGenErrors)
    /*将staticRenderFns全部转化成Funtion对象 */
    const l = compiled.staticRenderFns.length
    res.staticRenderFns = new Array(l)
    for (let i = 0; i < l; i++) {
      res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)
    }

    // check function generation errors.
    // this should only happen if there is a bug in the compiler itself.
    // mostly for codegen development use
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {
      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
        warn(
          `Failed to generate render function:\n\n` +
          fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
          vm
        )
      }
    }

    /*存放在缓存中,以免每次都重新编译*/
    return (functionCompileCache[key] = res) 
  }
```

我们可以发现,在闭包中,会有一个functionCompileCache对象作为缓存器。

```javascript
  /*作为缓存,防止每次都重新编译*/
  const functionCompileCache: {
    [key: string]: CompiledFunctionResult;
  } = Object.create(null)
```

在进入compileToFunctions以后,会先检查缓存中是否有已经编译好的结果,如果有结果则直接从缓存中读取。这样做防止每次同样的模板都要进行重复的编译工作。

```javascript
    // check cache
    /*有缓存的时候直接取出缓存中的结果即可*/
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (functionCompileCache[key]) {
      return functionCompileCache[key]
    }
```
在compileToFunctions的末尾会将编译结果进行缓存

```javascript
  /*存放在缓存中,以免每次都重新编译*/
  return (functionCompileCache[key] = res) 
```

## compile

```javascript
  /*编译,将模板template编译成AST、render函数以及staticRenderFns函数*/
  function compile (
    template: string,
    options?: CompilerOptions
  ): CompiledResult {
    const finalOptions = Object.create(baseOptions)
    const errors = []
    const tips = []
    finalOptions.warn = (msg, tip) => {
      (tip ? tips : errors).push(msg)
    }

    /*做下面这些merge的目的因为不同平台可以提供自己本身平台的一个baseOptions,内部封装了平台自己的实现,然后把共同的部分抽离开来放在这层compiler中,所以在这里需要merge一下*/
    if (options) {
      // merge custom modules
      /*合并modules*/
      if (options.modules) {
        finalOptions.modules = (baseOptions.modules || []).concat(options.modules)
      }
      // merge custom directives
      if (options.directives) {
        /*合并directives*/
        finalOptions.directives = extend(
          Object.create(baseOptions.directives),
          options.directives
        )
      }
      // copy other options
      for (const key in options) {
        /*合并其余的options,modules与directives已经在上面做了特殊处理了*/
        if (key !== 'modules' && key !== 'directives') {
          finalOptions[key] = options[key]
        }
      }
    }

    /*基础模板编译,得到编译结果*/
    const compiled = baseCompile(template, finalOptions)
    if (process.env.NODE_ENV !== 'production') {
      errors.push.apply(errors, detectErrors(compiled.ast))
    }
    compiled.errors = errors
    compiled.tips = tips
    return compiled
  }
```

compile主要做了两件事,一件是合并option(前面说的将平台自有的option与传入的option进行合并),另一件是baseCompile,进行模板template的编译。

来看一下baseCompile

## baseCompile

```javascript
function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  /*parse解析得到AST*/
  const ast = parse(template.trim(), options)
  /*
    将AST进行优化
    优化的目标:生成模板AST,检测不需要进行DOM改变的静态子树。
    一旦检测到这些静态树,我们就能做以下这些事情:
    1.把它们变成常数,这样我们就再也不需要每次重新渲染时创建新的节点了。
    2.在patch的过程中直接跳过。
 */
  optimize(ast, options)
  /*根据AST生成所需的code(内部包含render与staticRenderFns)*/
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}
```

baseCompile首先会将模板template进行parse得到一个AST,再通过optimize做一些优化,最后通过generate得到render以及staticRenderFns。

### parse

parse的源码可以参见[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)。

parse会用正则等方式解析template模板中的指令、class、style等数据,形成AST。

### optimize

optimize的主要作用是标记static静态节点,这是Vue在编译过程中的一处优化,后面当update更新界面时,会有一个patch的过程,diff算法会直接跳过静态节点,从而减少了比较的过程,优化了patch的性能。

### generate

generate是将AST转化成render funtion字符串的过程,得到结果是render的字符串以及staticRenderFns字符串。

---

至此,我们的template模板已经被转化成了我们所需的AST、render function字符串以及staticRenderFns字符串。

## 举个例子

来看一下这段代码的编译结果

```html
<div class="main" :class="bindClass">
    <div>{{text}}</div>
    <div>hello world</div>
    <div v-for="(item, index) in arr">
        <p>{{item.name}}</p>
        <p>{{item.value}}</p>
        <p>{{index}}</p>
        <p>---</p>
    </div>
    <div v-if="text">
        {{text}}
    </div>
    <div v-else></div>
</div>
```

转化后得到AST,如下图:

![img](https://i.loli.net/2017/09/07/59b135001cbfa.png)

我们可以看到最外层的div是这颗AST的根节点,节点上有许多数据代表这个节点的形态,比如static表示是否是静态节点,staticClass表示静态class属性(非bind:class)。children代表该节点的子节点,可以看到children是一个长度为4的数组,里面包含的是该节点下的四个div子节点。children里面的节点与父节点的结构类似,层层往下形成一棵AST。

再来看看由AST得到的render函数

```javascript
with(this){
    return _c(  'div',
                {
                    /*static class*/
                    staticClass:"main",
                    /*bind class*/
                    class:bindClass
                },
                [
                    _c( 'div', [_v(_s(text))]),
                    _c('div',[_v("hello world")]),
                    /*这是一个v-for循环*/
                    _l(
                        (arr),
                        function(item,index){
                            return _c(  'div',
                                        [_c('p',[_v(_s(item.name))]),
                                        _c('p',[_v(_s(item.value))]),
                                        _c('p',[_v(_s(index))]),
                                        _c('p',[_v("---")])]
                                    )
                        }
                    ),
                    /*这是v-if*/
                    (text)?_c('div',[_v(_s(text))]):_c('div',[_v("no text")])],
                    2
            )
}
```


## \_c,\_v,\_s,\_q

看了render function字符串,发现有大量的_c,_v,_s,_q,这些函数究竟是什么?

带着问题,我们来看一下[core/instance/render](https://github.com/answershuto/learnVue/blob/master/vue-src/core/instance/render.js#L124)。

```javascript
/*处理v-once的渲染函数*/
  Vue.prototype._o = markOnce
  /*将字符串转化为数字,如果转换失败会返回原字符串*/
  Vue.prototype._n = toNumber
  /*将val转化成字符串*/
  Vue.prototype._s = toString
  /*处理v-for列表渲染*/
  Vue.prototype._l = renderList
  /*处理slot的渲染*/
  Vue.prototype._t = renderSlot
  /*检测两个变量是否相等*/
  Vue.prototype._q = looseEqual
  /*检测arr数组中是否包含与val变量相等的项*/
  Vue.prototype._i = looseIndexOf
  /*处理static树的渲染*/
  Vue.prototype._m = renderStatic
  /*处理filters*/
  Vue.prototype._f = resolveFilter
  /*从config配置中检查eventKeyCode是否存在*/
  Vue.prototype._k = checkKeyCodes
  /*合并v-bind指令到VNode中*/
  Vue.prototype._b = bindObjectProps
  /*创建一个文本节点*/
  Vue.prototype._v = createTextVNode
  /*创建一个空VNode节点*/
  Vue.prototype._e = createEmptyVNode
  /*处理ScopedSlots*/
  Vue.prototype._u = resolveScopedSlots

  /*创建VNode节点*/
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
```

通过这些函数,render函数最后会返回一个VNode节点,在_update的时候,经过patch与之前的VNode节点进行比较,得出差异后将这些差异渲染到真实的DOM上。

================================================
FILE: docs/聊聊keep-alive组件的使用及其实现原理.MarkDown
================================================
## keep-alive

keep-alive是Vue.js的一个内置组件。它能够不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

它提供了include与exclude两个属性,允许组件有条件地进行缓存。

具体内容可以参考[官网](https://cn.vuejs.org/v2/api/#keep-alive)。

## 使用

### 用法

```html
<keep-alive>
    <component></component>
</keep-alive>
```

这里的component组件会被缓存起来。

### 举个栗子

```html
<keep-alive>
    <coma v-if="test"></coma>
    <comb v-else></comb>
</keep-alive>
<button @click="test=handleClick">请点击</button>
```

```javascript
export default {
    data () {
        return {
            test: true
        }
    },
    methods: {
        handleClick () {
            this.test = !this.test;
        }
    }
}
```

在点击button时候,coma与comb两个组件会发生切换,但是这时候这两个组件的状态会被缓存起来,比如说coma与comb组件中都有一个input标签,那么input标签中的内容不会因为组件的切换而消失。

### props

keep-alive组件提供了include与exclude两个属性来允许组件有条件地进行缓存,二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。

```html
<keep-alive include="a">
  <component></component>
</keep-alive>
```

将缓存name为a的组件。

```html
<keep-alive exclude="a">
  <component></component>
</keep-alive>
```

name为a的组件将不会被缓存。

### 生命钩子

keep-alive提供了两个生命钩子,分别是activated与deactivated。

因为keep-alive会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的created等方法,需要用activated与deactivated这两个生命钩子来得知当前组件是否处于活动状态。

---

## 深入keep-alive组件实现

说完了keep-alive组件的使用,我们从源码角度看一下keep-alive组件究竟是如何实现组件的缓存的呢?

### created与destroyed钩子

created钩子会创建一个cache对象,用来作为缓存容器,保存vnode节点。

```javascript
created () {
    /* 缓存对象 */
    this.cache = Object.create(null)
},
```

destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。

```javascript
/* destroyed钩子中销毁所有cache中的组件实例 */
destroyed () {
    for (const key in this.cache) {
        pruneCacheEntry(this.cache[key])
    }
},
```

### render

接下来是render函数。

```javascript
render () {
    /* 得到slot插槽中的第一个组件 */
    const vnode: VNode = getFirstComponentChild(this.$slots.default)

    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
        // check pattern
        /* 获取组件名称,优先获取组件的name字段,否则是组件的tag */
        const name: ?string = getComponentName(componentOptions)
        /* name不在inlcude中或者在exlude中则直接返回vnode(没有取缓存) */
        if (name && (
        (this.include && !matches(this.include, name)) ||
        (this.exclude && matches(this.exclude, name))
        )) {
            return vnode
        }
        const key: ?string = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
        /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode,还未缓存过则进行缓存 */
        if (this.cache[key]) {
            vnode.componentInstance = this.cache[key].componentInstance
        } else {
            this.cache[key] = vnode
        }
        /* keepAlive标记位 */
        vnode.data.keepAlive = true
    }
    return vnode
}
```

首先通过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) .

```javascript
/* 检测name是否匹配 */
function matches (pattern: string | RegExp, name: string): boolean {
  if (typeof pattern === 'string') {
    /* 字符串情况,如a,b,c */
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    /* 正则 */
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}
```

检测include与exclude属性匹配的函数很简单,include与exclude属性支持字符串如"a,b,c"这样组件名以逗号隔开的情况以及正则表达式。matches通过这两种方式分别检测是否匹配当前组件。

```javascript
if (this.cache[key]) {
    vnode.componentInstance = this.cache[key].componentInstance
} else {
    this.cache[key] = vnode
}
```

接下来的事情很简单,根据key在this.cache中查找,如果存在则说明之前已经缓存过了,直接将缓存的vnode的componentInstance(组件实例)覆盖到目前的vnode上面。否则将vnode存储在cache中。

最后返回vnode(有缓存时该vnode的componentInstance已经被替换成缓存中的了)。

### watch

用watch来监听pruneCache与pruneCache这两个属性的改变,在改变的时候修改cache缓存中的缓存数据。

```javascript
watch: {
    /* 监视include以及exclude,在被修改的时候对cache进行修正 */
    include (val: string | RegExp) {
        pruneCache(this.cache, this._vnode, name => matches(val, name))
    },
    exclude (val: string | RegExp) {
        pruneCache(this.cache, this._vnode, name => !matches(val, name))
    }
},
```

来看一下pruneCache的实现。

```javascript
/* 修正cache */
function pruneCache (cache: VNodeCache, current: VNode, filter: Function) {
  for (const key in cache) {
    /* 取出cache中的vnode */
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
      /* name不符合filter条件的,同时不是目前渲染的vnode时,销毁vnode对应的组件实例(Vue实例),并从cache中移除 */
      if (name && !filter(name)) {
        if (cachedNode !== current) {
          pruneCacheEntry(cachedNode)
        }
        cache[key] = null
      }
    }
  }
} 

/* 销毁vnode对应的组件实例(Vue实例) */
function pruneCacheEntry (vnode: ?VNode) {
  if (vnode) {
    vnode.componentInstance.$destroy()
  }
}
```

遍历cache中的所有项,如果不符合filter指定的规则的话,则会执行pruneCacheEntry。pruneCacheEntry则会调用组件实例的$destroy方法来将组件销毁。

## 最后

Vue.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对象中取出并渲染。

================================================
FILE: docs/说说element组件库broadcast与dispatch.MarkDown
================================================
众所周知,Vue 在 2.0 版本中去除了$broadcast方法以及$dispatch 方法,最近在学习饿了么的[Element](https://github.com/ElemeFE/element)时重新实现了这两种方法,并以 minix 的方式引入。

看一下[源代码](https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js)

```javascript
function broadcast(componentName, eventName, params) {
  /*遍历当前节点下的所有子组件*/
  this.$children.forEach(child => {
    /*获取子组件名称*/
    var name = child.$options.componentName;

    if (name === componentName) {
      /*如果是我们需要广播到的子组件的时候调用$emit触发所需事件,在子组件中用$on监听*/
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      /*非所需子组件则递归遍历深层次子组件*/
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    /*对多级父组件进行事件派发*/
    dispatch(componentName, eventName, params) {
      /*获取父组件,如果以及是根组件,则是$root*/
      var parent = this.$parent || this.$root;
      /*获取父节点的组件名*/
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        /*当父组件不是所需组件时继续向上寻找*/
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      /*找到所需组件后调用$emit触发当前事件*/
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    /*
        向所有子组件进行事件广播。
        这里包了一层,为了修改broadcast的this对象为当前Vue实例
    */
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

```

其实这里的broadcast与dispatch实现了一个定向的多层级父子组件间的事件广播及事件派发功能。完成多层级分发对应事件的组件间通信功能。

broadcast通过递归遍历子组件找到所需组件广播事件,而dispatch则逐级向上查找对应父组件派发事件。

broadcast需要三个参数,componentName(组件名),eventName(事件名称)以及params(数据)。根据componentName深度遍历子组件找到对应组件emit事件eventName。

dispatch同样道理,需要三个参数,componentName(组件名),eventName(事件名称)以及params(数据)。根据componentName向上级一直寻找对应父组件,找到以后emit事件eventName。


================================================
FILE: vue-router-src/components/link.js
================================================
/* @flow */

import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
import { _Vue } from '../install'

// work around weird flow bug
const toTypes: Array<Function> = [String, Object]
const eventTypes: Array<Function> = [String, Array]

export default {
  name: 'RouterLink',
  props: {
    to: {
      type: toTypes,
      required: true
    },
    tag: {
      type: String,
      default: 'a'
    },
    exact: Boolean,
    append: Boolean,
    replace: Boolean,
    activeClass: String,
    exactActiveClass: String,
    event: {
      type: eventTypes,
      default: 'click'
    }
  },
  render (h: Function) {
    const router = this.$router
    const current = this.$route
    const { location, route, href } = router.resolve(this.to, current, this.append)

    const classes = {}
    const globalActiveClass = router.options.linkActiveClass
    const globalExactActiveClass = router.options.linkExactActiveClass
    // Support global empty active class
    const activeClassFallback = globalActiveClass == null
            ? 'router-link-active'
            : globalActiveClass
    const exactActiveClassFallback = globalExactActiveClass == null
            ? 'router-link-exact-active'
            : globalExactActiveClass
    const activeClass = this.activeClass == null
            ? activeClassFallback
            : this.activeClass
    const exactActiveClass = this.exactActiveClass == null
            ? exactActiveClassFallback
            : this.exactActiveClass
    const compareTarget = location.path
      ? createRoute(null, location, null, router)
      : route

    classes[exactActiveClass] = isSameRoute(current, compareTarget)
    classes[activeClass] = this.exact
      ? classes[exactActiveClass]
      : isIncludedRoute(current, compareTarget)

    const handler = e => {
      if (guardEvent(e)) {
        if (this.replace) {
          router.replace(location)
        } else {
          router.push(location)
        }
      }
    }

    const on = { click: guardEvent }
    if (Array.isArray(this.event)) {
      this.event.forEach(e => { on[e] = handler })
    } else {
      on[this.event] = handler
    }

    const data: any = {
      class: classes
    }

    if (this.tag === 'a') {
      data.on = on
      data.attrs = { href }
    } else {
      // find the first <a> child and apply listener and href
      const a = findAnchor(this.$slots.default)
      if (a) {
        // in case the <a> is a static node
        a.isStatic = false
        const extend = _Vue.util.extend
        const aData = a.data = extend({}, a.data)
        aData.on = on
        const aAttrs = a.data.attrs = extend({}, a.data.attrs)
        aAttrs.href = href
      } else {
        // doesn't have <a> child, apply listener to self
        data.on = on
      }
    }

    return h(this.tag, data, this.$slots.default)
  }
}

function guardEvent (e) {
  // don't redirect with control keys
  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
  // don't redirect when preventDefault called
  if (e.defaultPrevented) return
  // don't redirect on right click
  if (e.button !== undefined && e.button !== 0) return
  // don't redirect if `target="_blank"`
  if (e.currentTarget && e.currentTarget.getAttribute) {
    const target = e.currentTarget.getAttribute('target')
    if (/\b_blank\b/i.test(target)) return
  }
  // this may be a Weex event which doesn't have this method
  if (e.preventDefault) {
    e.preventDefault()
  }
  return true
}

function findAnchor (children) {
  if (children) {
    let child
    for (let i = 0; i < children.length; i++) {
      child = children[i]
      if (child.tag === 'a') {
        return child
      }
      if (child.children && (child = findAnchor(child.children))) {
        return child
      }
    }
  }
}


================================================
FILE: vue-router-src/components/view.js
================================================
import { warn } from '../util/warn'

/* router-view组件 */
export default {
  name: 'RouterView',
  /* 
    https://cn.vuejs.org/v2/api/#functional
    使组件无状态 (没有 data ) 和无实例 (没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使他们更容易渲染。
  */
  functional: true,
  props: {
    name: {
      type: String,
      default: 'default'
    }
  },
  render (_, { props, children, parent, data }) {
    /* 标记位,标记是route-view组件 */
    data.routerView = true

    // directly use parent context's createElement() function
    // so that components rendered by router-view can resolve named slots
    /* 直接使用父组件的createElement函数 */
    const h = parent.$createElement
    /* props的name,默认'default' */
    const name = props.name
    /* option中的VueRouter对象 */
    const route = parent.$route
    /* 在parent上建立一个缓存对象 */
    const cache = parent._routerViewCache || (parent._routerViewCache = {})

    // determine current view depth, also check to see if the tree
    // has been toggled inactive but kept-alive.
    /* 记录组件深度 */
    let depth = 0
    /* 标记是否是待用(非alive状态)) */
    let inactive = false
    /* _routerRoot中中存放了根组件的势力,这边循环向上级访问,直到访问到根组件,得到depth深度 */
    while (parent && parent._routerRoot !== parent) {
      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      /* 如果_inactive为true,代表是在keep-alive中且是待用(非alive状态) */
      if (parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    /* 存放route-view组件的深度 */
    data.routerViewDepth = depth

    // render previous view if the tree is inactive and kept-alive
    /* 如果inactive为true说明在keep-alive组件中,直接从缓存中取 */
    if (inactive) {
      return h(cache[name], data, children)
    }

    const matched = route.matched[depth]
    // render empty node if no matched route
    /* 如果没有匹配到的路由,则渲染一个空节点 */
    if (!matched) {
      cache[name] = null
      return h()
    }

    /* 从成功匹配到的路由中取出组件 */
    const component = cache[name] = matched.components[name]

    // attach instance registration hook
    // this will be called in the instance's injected lifecycle hooks
    /* 注册实例的registration钩子,这个函数将在实例被注入的加入到组件的生命钩子(beforeCreate与destroyed)中被调用 */
    data.registerRouteInstance = (vm, val) => {  
      /* 第二个值不存在的时候为注销 */
      // val could be undefined for unregistration
      /* 获取组件实例 */
      const current = matched.instances[name]
      if (
        (val && current !== vm) ||
        (!val && current === vm)
      ) {
        /* 这里有两种情况,一种是val存在,则用val替换当前组件实例,另一种则是val不存在,则直接将val(这个时候其实是一个undefined)赋给instances */
        matched.instances[name] = val
      }
    }

    // also register instance in prepatch hook
    // in case the same component instance is reused across different routes
    ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {
      matched.instances[name] = vnode.componentInstance
    }

    // resolve props
    let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name])
    if (propsToPass) {
      // clone to prevent mutation
      propsToPass = data.props = extend({}, propsToPass)
      // pass non-declared props as attrs
      const attrs = data.attrs = data.attrs || {}
      for (const key in propsToPass) {
        if (!component.props || !(key in component.props)) {
          attrs[key] = propsToPass[key]
          delete propsToPass[key]
        }
      }
    }

    return h(component, data, children)
  }
}

function resolveProps (route, config) {
  switch (typeof config) {
    case 'undefined':
      return
    case 'object':
      return config
    case 'function':
      return config(route)
    case 'boolean':
      return config ? route.params : undefined
    default:
      if (process.env.NODE_ENV !== 'production') {
        warn(
          false,
          `props in "${route.path}" is a ${typeof config}, ` +
          `expecting an object, function or boolean.`
        )
      }
  }
}

function extend (to, from) {
  for (const key in from) {
    to[key] = from[key]
  }
  return to
}


================================================
FILE: vue-router-src/create-matcher.js
================================================
/* @flow */

import type VueRouter from './index'
import { resolvePath } from './util/path'
import { assert, warn } from './util/warn'
import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'

export type Matcher = {
  match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
  addRoutes: (routes: Array<RouteConfig>) => void;
};

export function createMatcher (
  routes: Array<RouteConfig>,
  router: VueRouter
): Matcher {
  const { pathList, pathMap, nameMap } = createRouteMap(routes)

  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }

  function match (
    raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location
  ): Route {
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    if (name) {
      const record = nameMap[name]
      if (process.env.NODE_ENV !== 'production') {
        warn(record, `Route with name '${name}' does not exist`)
      }
      if (!record) return _createRoute(null, location)
      const paramNames = record.regex.keys
        .filter(key => !key.optional)
        .map(key => key.name)

      if (typeof location.params !== 'object') {
        location.params = {}
      }

      if (currentRoute && typeof currentRoute.params === 'object') {
        for (const key in currentRoute.params) {
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
            location.params[key] = currentRoute.params[key]
          }
        }
      }

      if (record) {
        location.path = fillParams(record.path, location.params, `named route "${name}"`)
        return _createRoute(record, location, redirectedFrom)
      }
    } else if (location.path) {
      location.params = {}
      for (let i = 0; i < pathList.length; i++) {
        const path = pathList[i]
        const record = pathMap[path]
        if (matchRoute(record.regex, location.path, location.params)) {
          return _createRoute(record, location, redirectedFrom)
        }
      }
    }
    // no match
    return _createRoute(null, location)
  }

  function redirect (
    record: RouteRecord,
    location: Location
  ): Route {
    const originalRedirect = record.redirect
    let redirect = typeof originalRedirect === 'function'
        ? originalRedirect(createRoute(record, location, null, router))
        : originalRedirect

    if (typeof redirect === 'string') {
      redirect = { path: redirect }
    }

    if (!redirect || typeof redirect !== 'object') {
      if (process.env.NODE_ENV !== 'production') {
        warn(
          false, `invalid redirect option: ${JSON.stringify(redirect)}`
        )
      }
      return _createRoute(null, location)
    }

    const re: Object = redirect
    const { name, path } = re
    let { query, hash, params } = location
    query = re.hasOwnProperty('query') ? re.query : query
    hash = re.hasOwnProperty('hash') ? re.hash : hash
    params = re.hasOwnProperty('params') ? re.params : params

    if (name) {
      // resolved named direct
      const targetRecord = nameMap[name]
      if (process.env.NODE_ENV !== 'production') {
        assert(targetRecord, `redirect failed: named route "${name}" not found.`)
      }
      return match({
        _normalized: true,
        name,
        query,
        hash,
        params
      }, undefined, location)
    } else if (path) {
      // 1. resolve relative redirect
      const rawPath = resolveRecordPath(path, record)
      // 2. resolve params
      const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
      // 3. rematch with existing query and hash
      return match({
        _normalized: true,
        path: resolvedPath,
        query,
        hash
      }, undefined, location)
    } else {
      if (process.env.NODE_ENV !== 'production') {
        warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
      }
      return _createRoute(null, location)
    }
  }

  function alias (
    record: RouteRecord,
    location: Location,
    matchAs: string
  ): Route {
    const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
    const aliasedMatch = match({
      _normalized: true,
      path: aliasedPath
    })
    if (aliasedMatch) {
      const matched = aliasedMatch.matched
      const aliasedRecord = matched[matched.length - 1]
      location.params = aliasedMatch.params
      return _createRoute(aliasedRecord, location)
    }
    return _createRoute(null, location)
  }

  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    return createRoute(record, location, redirectedFrom, router)
  }

  return {
    match,
    addRoutes
  }
}

function matchRoute (
  regex: RouteRegExp,
  path: string,
  params: Object
): boolean {
  const m = path.match(regex)

  if (!m) {
    return false
  } else if (!params) {
    return true
  }

  for (let i = 1, len = m.length; i < len; ++i) {
    const key = regex.keys[i - 1]
    const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
    if (key) {
      params[key.name] = val
    }
  }

  return true
}

function resolveRecordPath (path: string, record: RouteRecord): string {
  return resolvePath(path, record.parent ? record.parent.path : '/', true)
}


================================================
FILE: vue-router-src/create-route-map.js
================================================
/* @flow */

import Regexp from 'path-to-regexp'
import { cleanPath } from './util/path'
import { assert, warn } from './util/warn'

export function createRouteMap (
  routes: Array<RouteConfig>,
  oldPathList?: Array<string>,
  oldPathMap?: Dictionary<RouteRecord>,
  oldNameMap?: Dictionary<RouteRecord>
): {
  pathList: Array<string>;
  pathMap: Dictionary<RouteRecord>;
  nameMap: Dictionary<RouteRecord>;
} {
  // the path list is used to control path matching priority
  const pathList: Array<string> = oldPathList || []
  // $flow-disable-line
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  // $flow-disable-line
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)

  routes.forEach(route => {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })

  // ensure wildcard routes are always at the end
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
      pathList.push(pathList.splice(i, 1)[0])
      l--
      i--
    }
  }

  return {
    pathList,
    pathMap,
    nameMap
  }
}

function addRouteRecord (
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>,
  route: RouteConfig,
  parent?: RouteRecord,
  matchAs?: string
) {
  const { path, name } = route
  if (process.env.NODE_ENV !== 'production') {
    assert(path != null, `"path" is required in a route configuration.`)
    assert(
      typeof route.component !== 'string',
      `route config "component" for path: ${String(path || name)} cannot be a ` +
      `string id. Use an actual component instead.`
    )
  }

  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}
  const normalizedPath = normalizePath(
    path,
    parent,
    pathToRegexpOptions.strict
  )

  if (typeof route.caseSensitive === 'boolean') {
    pathToRegexpOptions.sensitive = route.caseSensitive
  }

  const record: RouteRecord = {
    path: normalizedPath,
    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
    components: route.components || { default: route.component },
    instances: {},
    name,
    parent,
    matchAs,
    redirect: route.redirect,
    beforeEnter: route.beforeEnter,
    meta: route.meta || {},
    props: route.props == null
      ? {}
      : route.components
        ? route.props
        : { default: route.props }
  }

  if (route.children) {
    // Warn if route is named, does not redirect and has a default child route.
    // If users navigate to this route by name, the default child will
    // not be rendered (GH Issue #629)
    if (process.env.NODE_ENV !== 'production') {
      if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) {
        warn(
          false,
          `Named Route '${route.name}' has a default child route. ` +
          `When navigating to this named route (:to="{name: '${route.name}'"), ` +
          `the default child route will not be rendered. Remove the name from ` +
          `this route and use the name of the default child route for named ` +
          `links instead.`
        )
      }
    }
    route.children.forEach(child => {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }

  if (route.alias !== undefined) {
    const aliases = Array.isArray(route.alias)
      ? route.alias
      : [route.alias]

    aliases.forEach(alias => {
      const aliasRoute = {
        path: alias,
        children: route.children
      }
      addRouteRecord(
        pathList,
        pathMap,
        nameMap,
        aliasRoute,
        parent,
        record.path || '/' // matchAs
      )
    })
  }

  if (!pathMap[record.path]) {
    pathList.push(record.path)
    pathMap[record.path] = record
  }

  if (name) {
    if (!nameMap[name]) {
      nameMap[name] = record
    } else if (process.env.NODE_ENV !== 'production' && !matchAs) {
      warn(
        false,
        `Duplicate named routes definition: ` +
        `{ name: "${name}", path: "${record.path}" }`
      )
    }
  }
}

function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp {
  const regex = Regexp(path, [], pathToRegexpOptions)
  if (process.env.NODE_ENV !== 'production') {
    const keys: any = Object.create(null)
    regex.keys.forEach(key => {
      warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`)
      keys[key.name] = true
    })
  }
  return regex
}

function normalizePath (path: string, parent?: RouteRecord, strict?: boolean): string {
  if (!strict) path = path.replace(/\/$/, '')
  if (path[0] === '/') return path
  if (parent == null) return path
  return cleanPath(`${parent.path}/${path}`)
}


================================================
FILE: vue-router-src/history/abstract.js
================================================
/* @flow */

import type Router from '../index'
import { History } from './base'

export class AbstractHistory extends History {
  index: number;
  stack: Array<Route>;

  constructor (router: Router, base: ?string) {
    super(router, base)
    this.stack = []
    this.index = -1
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.transitionTo(location, route => {
      this.stack = this.stack.slice(0, this.index + 1).concat(route)
      this.index++
      onComplete && onComplete(route)
    }, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.transitionTo(location, route => {
      this.stack = this.stack.slice(0, this.index).concat(route)
      onComplete && onComplete(route)
    }, onAbort)
  }

  go (n: number) {
    const targetIndex = this.index + n
    if (targetIndex < 0 || targetIndex >= this.stack.length) {
      return
    }
    const route = this.stack[targetIndex]
    this.confirmTransition(route, () => {
      this.index = targetIndex
      this.updateRoute(route)
    })
  }

  getCurrentLocation () {
    const current = this.stack[this.stack.length - 1]
    return current ? current.fullPath : '/'
  }

  ensureURL () {
    // noop
  }
}


================================================
FILE: vue-router-src/history/base.js
================================================
/* @flow */

import { _Vue } from '../install'
import type Router from '../index'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { warn, isError } from '../util/warn'
import { START, isSameRoute } from '../util/route'
import {
  flatten,
  flatMapComponents,
  resolveAsyncComponents
} from '../util/resolve-components'

export class History {
  router: Router;
  base: string;
  current: Route;
  pending: ?Route;
  cb: (r: Route) => void;
  ready: boolean;
  readyCbs: Array<Function>;
  readyErrorCbs: Array<Function>;
  errorCbs: Array<Function>;

  // implemented by sub-classes
  +go: (n: number) => void;
  +push: (loc: RawLocation) => void;
  +replace: (loc: RawLocation) => void;
  +ensureURL: (push?: boolean) => void;
  +getCurrentLocation: () => string;

  constructor (router: Router, base: ?string) {
    this.router = router
    this.base = normalizeBase(base)
    // start with a route object that stands for "nowhere"
    this.current = START
    this.pending = null
    this.ready = false
    this.readyCbs = []
    this.readyErrorCbs = []
    this.errorCbs = []
  }

  listen (cb: Function) {
    this.cb = cb
  }

  onReady (cb: Function, errorCb: ?Function) {
    if (this.ready) {
      cb()
    } else {
      this.readyCbs.push(cb)
      if (errorCb) {
        this.readyErrorCbs.push(errorCb)
      }
    }
  }

  onError (errorCb: Function) {
    this.errorCbs.push(errorCb)
  }

  transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      this.updateRoute(route)
      onComplete && onComplete(route)
      this.ensureURL()

      // fire ready cbs once
      if (!this.ready) {
        this.ready = true
        this.readyCbs.forEach(cb => { cb(route) })
      }
    }, err => {
      if (onAbort) {
        onAbort(err)
      }
      if (err && !this.ready) {
        this.ready = true
        this.readyErrorCbs.forEach(cb => { cb(err) })
      }
    })
  }

  confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
    const current = this.current
    const abort = err => {
      if (isError(err)) {
        if (this.errorCbs.length) {
          this.errorCbs.forEach(cb => { cb(err) })
        } else {
          warn(false, 'uncaught error during route navigation:')
          console.error(err)
        }
      }
      onAbort && onAbort(err)
    }
    if (
      isSameRoute(route, current) &&
      // in the case the route map has been dynamically appended to
      route.matched.length === current.matched.length
    ) {
      this.ensureURL()
      return abort()
    }

    const {
      updated,
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // in-component update hooks
      extractUpdateHooks(updated),
      // in-config enter guards
      activated.map(m => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

    this.pending = route
    const iterator = (hook: NavigationGuard, next) => {
      if (this.pending !== route) {
        return abort()
      }
      try {
        hook(route, current, (to: any) => {
          if (to === false || isError(to)) {
            // next(false) -> abort navigation, ensure current URL
            this.ensureURL(true)
            abort(to)
          } else if (
            typeof to === 'string' ||
            (typeof to === 'object' && (
              typeof to.path === 'string' ||
              typeof to.name === 'string'
            ))
          ) {
            // next('/') or next({ path: '/' }) -> redirect
            abort()
            if (typeof to === 'object' && to.replace) {
              this.replace(to)
            } else {
              this.push(to)
            }
          } else {
            // confirm transition and pass on the value
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }

    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const isValid = () => this.current === route
      // wait until async components are resolved before
      // extracting in-component enter guards
      const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
      const queue = enterGuards.concat(this.router.resolveHooks)
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort()
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            postEnterCbs.forEach(cb => { cb() })
          })
        }
      })
    })
  }

  updateRoute (route: Route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }
}

function normalizeBase (base: ?string): string {
  if (!base) {
    if (inBrowser) {
      // respect <base> tag
      const baseEl = document.querySelector('base')
      base = (baseEl && baseEl.getAttribute('href')) || '/'
      // strip full URL origin
      base = base.replace(/^https?:\/\/[^\/]+/, '')
    } else {
      base = '/'
    }
  }
  // make sure there's the starting slash
  if (base.charAt(0) !== '/') {
    base = '/' + base
  }
  // remove trailing slash
  return base.replace(/\/$/, '')
}

function resolveQueue (
  current: Array<RouteRecord>,
  next: Array<RouteRecord>
): {
  updated: Array<RouteRecord>,
  activated: Array<RouteRecord>,
  deactivated: Array<RouteRecord>
} {
  let i
  const max = Math.max(current.length, next.length)
  for (i = 0; i < max; i++) {
    if (current[i] !== next[i]) {
      break
    }
  }
  return {
    updated: next.slice(0, i),
    activated: next.slice(i),
    deactivated: current.slice(i)
  }
}

function extractGuards (
  records: Array<RouteRecord>,
  name: string,
  bind: Function,
  reverse?: boolean
): Array<?Function> {
  const guards = flatMapComponents(records, (def, instance, match, key) => {
    const guard = extractGuard(def, name)
    if (guard) {
      return Array.isArray(guard)
        ? guard.map(guard => bind(guard, instance, match, key))
        : bind(guard, instance, match, key)
    }
  })
  return flatten(reverse ? guards.reverse() : guards)
}

function extractGuard (
  def: Object | Function,
  key: string
): NavigationGuard | Array<NavigationGuard> {
  if (typeof def !== 'function') {
    // extend now so that global mixins are applied.
    def = _Vue.extend(def)
  }
  return def.options[key]
}

function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}

function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
  return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
}

function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
  if (instance) {
    return function boundRouteGuard () {
      return guard.apply(instance, arguments)
    }
  }
}

function extractEnterGuards (
  activated: Array<RouteRecord>,
  cbs: Array<Function>,
  isValid: () => boolean
): Array<?Function> {
  return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {
    return bindEnterGuard(guard, match, key, cbs, isValid)
  })
}

function bindEnterGuard (
  guard: NavigationGuard,
  match: RouteRecord,
  key: string,
  cbs: Array<Function>,
  isValid: () => boolean
): NavigationGuard {
  return function routeEnterGuard (to, from, next) {
    return guard(to, from, cb => {
      next(cb)
      if (typeof cb === 'function') {
        cbs.push(() => {
          // #750
          // if a router-view is wrapped with an out-in transition,
          // the instance may not have been registered at this time.
          // we will need to poll for registration until current route
          // is no longer valid.
          poll(cb, match.instances, key, isValid)
        })
      }
    })
  }
}

function poll (
  cb: any, // somehow flow cannot infer this is a function
  instances: Object,
  key: string,
  isValid: () => boolean
) {
  if (instances[key]) {
    cb(instances[key])
  } else if (isValid()) {
    setTimeout(() => {
      poll(cb, instances, key, isValid)
    }, 16)
  }
}


================================================
FILE: vue-router-src/history/hash.js
================================================
/* @flow */

import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { getLocation } from './html5'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'

export class HashHistory extends History {
  constructor (router: Router, base: ?string, fallback: boolean) {
    super(router, base)
    // check history fallback deeplinking
    if (fallback && checkFallback(this.base)) {
      return
    }
    ensureSlash()
  }

  // this is delayed until the app mounts
  // to avoid the hashchange listener being fired too early
  setupListeners () {
    const router = this.router
    const expectScroll = router.options.scrollBehavior
    const supportsScroll = supportsPushState && expectScroll

    if (supportsScroll) {
      setupScroll()
    }

    window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {
      const current = this.current
      if (!ensureSlash()) {
        return
      }
      this.transitionTo(getHash(), route => {
        if (supportsScroll) {
          handleScroll(this.router, route, current, true)
        }
        if (!supportsPushState) {
          replaceHash(route.fullPath)
        }
      })
    })
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushHash(route.fullPath)
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      replaceHash(route.fullPath)
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  go (n: number) {
    window.history.go(n)
  }

  ensureURL (push?: boolean) {
    const current = this.current.fullPath
    if (getHash() !== current) {
      push ? pushHash(current) : replaceHash(current)
    }
  }

  getCurrentLocation () {
    return getHash()
  }
}

function checkFallback (base) {
  const location = getLocation(base)
  if (!/^\/#/.test(location)) {
    window.location.replace(
      cleanPath(base + '/#' + location)
    )
    return true
  }
}

function ensureSlash (): boolean {
  const path = getHash()
  if (path.charAt(0) === '/') {
    return true
  }
  replaceHash('/' + path)
  return false
}

export function getHash (): string {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  const href = window.location.href
  const index = href.indexOf('#')
  return index === -1 ? '' : href.slice(index + 1)
}

function getUrl (path) {
  const href = window.location.href
  const i = href.indexOf('#')
  const base = i >= 0 ? href.slice(0, i) : href
  return `${base}#${path}`
}

function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path
  }
}

function replaceHash (path) {
  if (supportsPushState) {
    replaceState(getUrl(path))
  } else {
    window.location.replace(getUrl(path))
  }
}


================================================
FILE: vue-router-src/history/html5.js
================================================
/* @flow */

import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { START } from '../util/route'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'

export class HTML5History extends History {
  constructor (router: Router, base: ?string) {
    super(router, base)

    const expectScroll = router.options.scrollBehavior
    const supportsScroll = supportsPushState && expectScroll

    if (supportsScroll) {
      setupScroll()
    }

    const initLocation = getLocation(this.base)
    window.addEventListener('popstate', e => {
      const current = this.current

      // Avoiding first `popstate` event dispatched in some browsers but first
      // history route not updated since async guard at the same time.
      const location = getLocation(this.base)
      if (this.current === START && location === initLocation) {
        return
      }

      this.transitionTo(location, route => {
        if (supportsScroll) {
          handleScroll(router, route, current, true)
        }
      })
    })
  }

  go (n: number) {
    window.history.go(n)
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      replaceState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  ensureURL (push?: boolean) {
    if (getLocation(this.base) !== this.current.fullPath) {
      const current = cleanPath(this.base + this.current.fullPath)
      push ? pushState(current) : replaceState(current)
    }
  }

  getCurrentLocation (): string {
    return getLocation(this.base)
  }
}

export function getLocation (base: string): string {
  let path = window.location.pathname
  if (base && path.indexOf(base) === 0) {
    path = path.slice(base.length)
  }
  return (path || '/') + window.location.search + window.location.hash
}


================================================
FILE: vue-router-src/index.js
================================================
/* @flow */

import { install } from './install'
import { START } from './util/route'
import { assert } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'

import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'

import type { Matcher } from './create-matcher'

/* 导出的VueRouter对象,用来包装store */
export default class VueRouter {
  static install: () => void;
  static version: string;

  app: any;
  apps: Array<any>;
  ready: boolean;
  readyCbs: Array<Function>;
  options: RouterOptions;
  mode: string;
  history: HashHistory | HTML5History | AbstractHistory;
  matcher: Matcher;
  fallback: boolean;
  beforeHooks: Array<?NavigationGuard>;
  resolveHooks: Array<?NavigationGuard>;
  afterHooks: Array<?AfterNavigationHook>;

  constructor (options: RouterOptions = {}) {
    this.app = null
    /* 保存vm实例 */
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode

    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }

  match (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    return this.matcher.match(raw, current, redirectedFrom)
  }

  get currentRoute (): ?Route {
    return this.history && this.history.current
  }

  /* 初始化 */
  init (app: any /* Vue component instance */) {
    /* 未安装就调用init会抛出异常 */
    process.env.NODE_ENV !== 'production' && assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
      `before creating root instance.`
    )

    /* 将当前vm实例保存在app中 */
    this.apps.push(app)

    // main app already initialized.
    /* 已存在说明已经被init过了,直接返回 */
    if (this.app) {
      return
    }

    /* this.app保存当前vm实例 */
    this.app = app

    const history = this.history

    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

  beforeEach (fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
  }

  beforeResolve (fn: Function): Function {
    return registerHook(this.resolveHooks, fn)
  }

  afterEach (fn: Function): Function {
    return registerHook(this.afterHooks, fn)
  }

  onReady (cb: Function, errorCb?: Function) {
    this.history.onReady(cb, errorCb)
  }

  onError (errorCb: Function) {
    this.history.onError(errorCb)
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.history.push(location, onComplete, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.history.replace(location, onComplete, onAbort)
  }

  go (n: number) {
    this.history.go(n)
  }

  back () {
    this.go(-1)
  }

  forward () {
    this.go(1)
  }

  getMatchedComponents (to?: RawLocation | Route): Array<any> {
    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute
    if (!route) {
      return []
    }
    return [].concat.apply([], route.matched.map(m => {
      return Object.keys(m.components).map(key => {
        return m.components[key]
      })
    }))
  }

  resolve (
    to: RawLocation,
    current?: Route,
    append?: boolean
  ): {
    location: Location,
    route: Route,
    href: string,
    // for backwards compat
    normalizedTo: Location,
    resolved: Route
  } {
    const location = normalizeLocation(
      to,
      current || this.history.current,
      append,
      this
    )
    const route = this.match(location, current)
    const fullPath = route.redirectedFrom || route.fullPath
    const base = this.history.base
    const href = createHref(base, fullPath, this.mode)
    return {
      location,
      route,
      href,
      // for backwards compat
      normalizedTo: location,
      resolved: route
    }
  }

  addRoutes (routes: Array<RouteConfig>) {
    this.matcher.addRoutes(routes)
    if (this.history.current !== START) {
      this.history.transitionTo(this.history.getCurrentLocation())
    }
  }
}

function registerHook (list: Array<any>, fn: Function): Function {
  list.push(fn)
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}

function createHref (base: string, fullPath: string, mode) {
  var path = mode === 'hash' ? '#' + fullPath : fullPath
  return base ? cleanPath(base + '/' + path) : path
}

/* Vue.use安装插件时候需要暴露的install方法 */
VueRouter.install = install
VueRouter.version = '__VERSION__'

/* 兼容用script标签引用的方法 */
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}


================================================
FILE: vue-router-src/install.js
================================================
import View from './components/view'
import Link from './components/link'

export let _Vue

/* Vue.use安装插件时候需要暴露的install方法 */
export function install (Vue) {
  
  /* 判断是否已安装过 */
  if (install.installed && _Vue === Vue) return
  install.installed = true

  /* 保存Vue实例 */
  _Vue = Vue

  /* 判断是否已定义 */
  const isDef = v => v !== undefined

  /* 通过registerRouteInstance方法注册router实例 */
  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  /* 混淆进Vue实例,在boforeCreate与destroyed钩子上混淆 */
  Vue.mixin({
    /* boforeCreate钩子 */
    beforeCreate () {
      if (isDef(this.$options.router)) {
        /* 在option上面存在router则代表是根组件 */
        /* 保存跟组件vm */
        this._routerRoot = this
        /* 保存router */
        this._router = this.$options.router
        /* VueRouter对象的init方法 */
        this._router.init(this)
        /* Vue内部方法,为对象defineProperty上在变化时通知的属性 */
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        /* 非根组件则直接从父组件中获取 */
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      /* 通过registerRouteInstance方法注册router实例 */
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })

  /* 在Vue的prototype上面绑定$router,这样可以在任意Vue对象中使用this.$router访问,同时经过Object.defineProperty,访问this.$router即访问this._routerRoot._router */
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  /* 以上同理,访问this.$route即访问this._routerRoot._route */
  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  /* 注册touter-view以及router-link组件 */
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  /* 该对象保存了两个option合并的规则 */
  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}


================================================
FILE: vue-router-src/util/async.js
================================================
/* @flow */

export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
  const step = index => {
    if (index >= queue.length) {
      cb()
    } else {
      if (queue[index]) {
        fn(queue[index], () => {
          step(index + 1)
        })
      } else {
        step(index + 1)
      }
    }
  }
  step(0)
}


================================================
FILE: vue-router-src/util/dom.js
================================================
/* @flow */

export const inBrowser = typeof window !== 'undefined'


================================================
FILE: vue-router-src/util/location.js
================================================
/* @flow */

import type VueRouter from '../index'
import { parsePath, resolvePath } from './path'
import { resolveQuery } from './query'
import { fillParams } from './params'
import { warn } from './warn'

export function normalizeLocation (
  raw: RawLocation,
  current: ?Route,
  append: ?boolean,
  router: ?VueRouter
): Location {
  let next: Location = typeof raw === 'string' ? { path: raw } : raw
  // named target
  if (next.name || next._normalized) {
    return next
  }

  // relative params
  if (!next.path && next.params && current) {
    next = assign({}, next)
    next._normalized = true
    const params: any = assign(assign({}, current.params), next.params)
    if (current.name) {
      next.name = current.name
      next.params = params
    } else if (current.matched.length) {
      const rawPath = current.matched[current.matched.length - 1].path
      next.path = fillParams(rawPath, params, `path ${current.path}`)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(false, `relative params navigation requires a current route.`)
    }
    return next
  }

  const parsedPath = parsePath(next.path || '')
  const basePath = (current && current.path) || '/'
  const path = parsedPath.path
    ? resolvePath(parsedPath.path, basePath, append || next.append)
    : basePath

  const query = resolveQuery(
    parsedPath.query,
    next.query,
    router && router.options.parseQuery
  )

  let hash = next.hash || parsedPath.hash
  if (hash && hash.charAt(0) !== '#') {
    hash = `#${hash}`
  }

  return {
    _normalized: true,
    path,
    query,
    hash
  }
}

function assign (a, b) {
  for (const key in b) {
    a[key] = b[key]
  }
  return a
}


================================================
FILE: vue-router-src/util/params.js
================================================
/* @flow */

import { warn } from './warn'
import Regexp from 'path-to-regexp'

// $flow-disable-line
const regexpCompileCache: {
  [key: string]: Function
} = Object.create(null)

export function fillParams (
  path: string,
  params: ?Object,
  routeMsg: string
): string {
  try {
    const filler =
      regexpCompileCache[path] ||
      (regexpCompileCache[path] = Regexp.compile(path))
    return filler(params || {}, { pretty: true })
  } catch (e) {
    if (process.env.NODE_ENV !== 'production') {
      warn(false, `missing param for ${routeMsg}: ${e.message}`)
    }
    return ''
  }
}


================================================
FILE: vue-router-src/util/path.js
================================================
/* @flow */

export function resolvePath (
  relative: string,
  base: string,
  append?: boolean
): string {
  const firstChar = relative.charAt(0)
  if (firstChar === '/') {
    return relative
  }

  if (firstChar === '?' || firstChar === '#') {
    return base + relative
  }

  const stack = base.split('/')

  // remove trailing segment if:
  // - not appending
  // - appending to trailing slash (last segment is empty)
  if (!append || !stack[stack.length - 1]) {
    stack.pop()
  }

  // resolve relative path
  const segments = relative.replace(/^\//, '').split('/')
  for (let i = 0; i < segments.length; i++) {
    const segment = segments[i]
    if (segment === '..') {
      stack.pop()
    } else if (segment !== '.') {
      stack.push(segment)
    }
  }

  // ensure leading slash
  if (stack[0] !== '') {
    stack.unshift('')
  }

  return stack.join('/')
}

export function parsePath (path: string): {
  path: string;
  query: string;
  hash: string;
} {
  let hash = ''
  let query = ''

  const hashIndex = path.indexOf('#')
  if (hashIndex >= 0) {
    hash = path.slice(hashIndex)
    path = path.slice(0, hashIndex)
  }

  const queryIndex = path.indexOf('?')
  if (queryIndex >= 0) {
    query = path.slice(queryIndex + 1)
    path = path.slice(0, queryIndex)
  }

  return {
    path,
    query,
    hash
  }
}

export function cleanPath (path: string): string {
  return path.replace(/\/\//g, '/')
}


================================================
FILE: vue-router-src/util/push-state.js
================================================
/* @flow */

import { inBrowser } from './dom'
import { saveScrollPosition } from './scroll'

export const supportsPushState = inBrowser && (function () {
  const ua = window.navigator.userAgent

  if (
    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
    ua.indexOf('Mobile Safari') !== -1 &&
    ua.indexOf('Chrome') === -1 &&
    ua.indexOf('Windows Phone') === -1
  ) {
    return false
  }

  return window.history && 'pushState' in window.history
})()

// use User Timing api (if present) for more accurate key precision
const Time = inBrowser && window.performance && window.performance.now
  ? window.performance
  : Date

let _key: string = genKey()

function genKey (): string {
  return Time.now().toFixed(3)
}

export function getStateKey () {
  return _key
}

export function setStateKey (key: string) {
  _key = key
}

export function pushState (url?: string, replace?: boolean) {
  saveScrollPosition()
  // try...catch the pushState call to get around Safari
  // DOM Exception 18 where it limits to 100 pushState calls
  const history = window.history
  try {
    if (replace) {
      history.replaceState({ key: _key }, '', url)
    } else {
      _key = genKey()
      history.pushState({ key: _key }, '', url)
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url)
  }
}

export function replaceState (url?: string) {
  pushState(url, true)
}


================================================
FILE: vue-router-src/util/query.js
================================================
/* @flow */

import { warn } from './warn'

const encodeReserveRE = /[!'()*]/g
const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16)
const commaRE = /%2C/g

// fixed encodeURIComponent which is more conformant to RFC3986:
// - escapes [!'()*]
// - preserve commas
const encode = str => encodeURIComponent(str)
  .replace(encodeReserveRE, encodeReserveReplacer)
  .replace(commaRE, ',')

const decode = decodeURIComponent

export function resolveQuery (
  query: ?string,
  extraQuery: Dictionary<string> = {},
  _parseQuery: ?Function
): Dictionary<string> {
  const parse = _parseQuery || parseQuery
  let parsedQuery
  try {
    parsedQuery = parse(query || '')
  } catch (e) {
    process.env.NODE_ENV !== 'production' && warn(false, e.message)
    parsedQuery = {}
  }
  for (const key in extraQuery) {
    parsedQuery[key] = extraQuery[key]
  }
  return parsedQuery
}

function parseQuery (query: string): Dictionary<string> {
  const res = {}

  query = query.trim().replace(/^(\?|#|&)/, '')

  if (!query) {
    return res
  }

  query.split('&').forEach(param => {
    const parts = param.replace(/\+/g, ' ').split('=')
    const key = decode(parts.shift())
    const val = parts.length > 0
      ? decode(parts.join('='))
      : null

    if (res[key] === undefined) {
      res[key] = val
    } else if (Array.isArray(res[key])) {
      res[key].push(val)
    } else {
      res[key] = [res[key], val]
    }
  })

  return res
}

export function stringifyQuery (obj: Dictionary<string>): string {
  const res = obj ? Object.keys(obj).map(key => {
    const val = obj[key]

    if (val === undefined) {
      return ''
    }

    if (val === null) {
      return encode(key)
    }

    if (Array.isArray(val)) {
      const result = []
      val.forEach(val2 => {
        if (val2 === undefined) {
          return
        }
        if (val2 === null) {
          result.push(encode(key))
        } else {
          result.push(encode(key) + '=' + encode(val2))
        }
      })
      return result.join('&')
    }

    return encode(key) + '=' + encode(val)
  }).filter(x => x.length > 0).join('&') : null
  return res ? `?${res}` : ''
}


================================================
FILE: vue-router-src/util/resolve-components.js
================================================
/* @flow */

import { _Vue } from '../install'
import { warn, isError } from './warn'

export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
  return (to, from, next) => {
    let hasAsync = false
    let pending = 0
    let error = null

    flatMapComponents(matched, (def, _, match, key) => {
      // if it's a function and doesn't have cid attached,
      // assume it's an async component resolve function.
      // we are not using Vue's default async resolving mechanism because
      // we want to halt the navigation until the incoming component has been
      // resolved.
      if (typeof def === 'function' && def.cid === undefined) {
        hasAsync = true
        pending++

        const resolve = once(resolvedDef => {
          if (isESModule(resolvedDef)) {
            resolvedDef = resolvedDef.default
          }
          // save resolved on async factory in case it's used elsewhere
          def.resolved = typeof resolvedDef === 'function'
            ? resolvedDef
            : _Vue.extend(resolvedDef)
          match.components[key] = resolvedDef
          pending--
          if (pending <= 0) {
            next()
          }
        })

        const reject = once(reason => {
          const msg = `Failed to resolve async component ${key}: ${reason}`
          process.env.NODE_ENV !== 'production' && warn(false, msg)
          if (!error) {
            error = isError(reason)
              ? reason
              : new Error(msg)
            next(error)
          }
        })

        let res
        try {
          res = def(resolve, reject)
        } catch (e) {
          reject(e)
        }
        if (res) {
          if (typeof res.then === 'function') {
            res.then(resolve, reject)
          } else {
            // new syntax in Vue 2.3
            const comp = res.component
            if (comp && typeof comp.then === 'function') {
              comp.then(resolve, reject)
            }
          }
        }
      }
    })

    if (!hasAsync) next()
  }
}

export function flatMapComponents (
  matched: Array<RouteRecord>,
  fn: Function
): Array<?Function> {
  return flatten(matched.map(m => {
    return Object.keys(m.components).map(key => fn(
      m.components[key],
      m.instances[key],
      m, key
    ))
  }))
}

export function flatten (arr: Array<any>): Array<any> {
  return Array.prototype.concat.apply([], arr)
}

const hasSymbol =
  typeof Symbol === 'function' &&
  typeof Symbol.toStringTag === 'symbol'

function isESModule (obj) {
  return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
}

// in Webpack 2, require.ensure now also returns a Promise
// so the resolve/reject functions may get called an extra time
// if the user uses an arrow function shorthand that happens to
// return that Promise.
function once (fn) {
  let called = false
  return function (...args) {
    if (called) return
    called = true
    return fn.apply(this, args)
  }
}


================================================
FILE: vue-router-src/util/route.js
================================================
/* @flow */

import type VueRouter from '../index'
import { stringifyQuery } from './query'

const trailingSlashRE = /\/?$/

export function createRoute (
  record: ?RouteRecord,
  location: Location,
  redirectedFrom?: ?Location,
  router?: VueRouter
): Route {
  const stringifyQuery = router && router.options.stringifyQuery

  let query: any = location.query || {}
  try {
    query = clone(query)
  } catch (e) {}

  const route: Route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || '/',
    hash: location.hash || '',
    query,
    params: location.params || {},
    fullPath: getFullPath(location, stringifyQuery),
    matched: record ? formatMatch(record) : []
  }
  if (redirectedFrom) {
    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
  }
  return Object.freeze(route)
}

function clone (value) {
  if (Array.isArray(value)) {
    return value.map(clone)
  } else if (value && typeof value === 'object') {
    const res = {}
    for (const key in value) {
      res[key] = clone(value[key])
    }
    return res
  } else {
    return value
  }
}

// the starting route that represents the initial state
/* 起始路由 */
export const START = createRoute(null, {
  path: '/'
})

function formatMatch (recor
Download .txt
gitextract_wq0j9njv/

├── README.md
├── docs/
│   ├── VNode节点.MarkDown
│   ├── VirtualDOM与diff(Vue实现).MarkDown
│   ├── Vue.js异步更新DOM策略及nextTick.MarkDown
│   ├── Vuex源码解析.MarkDown
│   ├── Vue事件机制.MarkDown
│   ├── Vue组件间通信.MarkDown
│   ├── 从template到DOM(Vue.js源码角度看内部运行机制).MarkDown
│   ├── 从源码角度再看数据绑定.MarkDown
│   ├── 依赖收集.MarkDown
│   ├── 响应式原理.MarkDown
│   ├── 聊聊Vue的template编译.MarkDown
│   ├── 聊聊keep-alive组件的使用及其实现原理.MarkDown
│   └── 说说element组件库broadcast与dispatch.MarkDown
├── images/
│   ├── VNode.sketch
│   ├── VNode2.sketch
│   ├── diff1.sketch
│   ├── diff10.sketch
│   ├── diff2.sketch
│   ├── diff3.sketch
│   ├── diff4.sketch
│   ├── diff5.sketch
│   ├── diff6.sketch
│   ├── diff7.sketch
│   ├── diff8.sketch
│   └── diff9.sketch
├── vue-router-src/
│   ├── components/
│   │   ├── link.js
│   │   └── view.js
│   ├── create-matcher.js
│   ├── create-route-map.js
│   ├── history/
│   │   ├── abstract.js
│   │   ├── base.js
│   │   ├── hash.js
│   │   └── html5.js
│   ├── index.js
│   ├── install.js
│   └── util/
│       ├── async.js
│       ├── dom.js
│       ├── location.js
│       ├── params.js
│       ├── path.js
│       ├── push-state.js
│       ├── query.js
│       ├── resolve-components.js
│       ├── route.js
│       ├── scroll.js
│       └── warn.js
├── vue-src/
│   ├── compiler/
│   │   ├── codegen/
│   │   │   ├── events.js
│   │   │   └── index.js
│   │   ├── directives/
│   │   │   ├── bind.js
│   │   │   ├── index.js
│   │   │   └── model.js
│   │   ├── error-detector.js
│   │   ├── helpers.js
│   │   ├── index.js
│   │   ├── optimizer.js
│   │   └── parser/
│   │       ├── entity-decoder.js
│   │       ├── filter-parser.js
│   │       ├── html-parser.js
│   │       ├── index.js
│   │       └── text-parser.js
│   ├── core/
│   │   ├── components/
│   │   │   ├── index.js
│   │   │   └── keep-alive.js
│   │   ├── config.js
│   │   ├── global-api/
│   │   │   ├── assets.js
│   │   │   ├── extend.js
│   │   │   ├── index.js
│   │   │   ├── mixin.js
│   │   │   └── use.js
│   │   ├── index.js
│   │   ├── instance/
│   │   │   ├── events.js
│   │   │   ├── index.js
│   │   │   ├── init.js
│   │   │   ├── inject.js
│   │   │   ├── lifecycle.js
│   │   │   ├── proxy.js
│   │   │   ├── render-helpers/
│   │   │   │   ├── bind-object-props.js
│   │   │   │   ├── check-keycodes.js
│   │   │   │   ├── render-list.js
│   │   │   │   ├── render-slot.js
│   │   │   │   ├── render-static.js
│   │   │   │   ├── resolve-filter.js
│   │   │   │   └── resolve-slots.js
│   │   │   ├── render.js
│   │   │   └── state.js
│   │   ├── observer/
│   │   │   ├── array.js
│   │   │   ├── dep.js
│   │   │   ├── index.js
│   │   │   ├── scheduler.js
│   │   │   └── watcher.js
│   │   ├── util/
│   │   │   ├── debug.js
│   │   │   ├── env.js
│   │   │   ├── error.js
│   │   │   ├── index.js
│   │   │   ├── lang.js
│   │   │   ├── options.js
│   │   │   ├── perf.js
│   │   │   └── props.js
│   │   └── vdom/
│   │       ├── create-component.js
│   │       ├── create-element.js
│   │       ├── create-functional-component.js
│   │       ├── helpers/
│   │       │   ├── extract-props.js
│   │       │   ├── get-first-component-child.js
│   │       │   ├── index.js
│   │       │   ├── merge-hook.js
│   │       │   ├── normalize-children.js
│   │       │   ├── resolve-async-component.js
│   │       │   └── update-listeners.js
│   │       ├── modules/
│   │       │   ├── directives.js
│   │       │   ├── index.js
│   │       │   └── ref.js
│   │       ├── patch.js
│   │       └── vnode.js
│   ├── platforms/
│   │   ├── web/
│   │   │   ├── compiler/
│   │   │   │   ├── directives/
│   │   │   │   │   ├── html.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── model.js
│   │   │   │   │   └── text.js
│   │   │   │   ├── index.js
│   │   │   │   ├── modules/
│   │   │   │   │   ├── class.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── style.js
│   │   │   │   └── util.js
│   │   │   ├── compiler.js
│   │   │   ├── runtime/
│   │   │   │   ├── class-util.js
│   │   │   │   ├── components/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── transition-group.js
│   │   │   │   │   └── transition.js
│   │   │   │   ├── directives/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── model.js
│   │   │   │   │   └── show.js
│   │   │   │   ├── index.js
│   │   │   │   ├── modules/
│   │   │   │   │   ├── attrs.js
│   │   │   │   │   ├── class.js
│   │   │   │   │   ├── dom-props.js
│   │   │   │   │   ├── events.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── style.js
│   │   │   │   │   └── transition.js
│   │   │   │   ├── node-ops.js
│   │   │   │   ├── patch.js
│   │   │   │   └── transition-util.js
│   │   │   ├── runtime-with-compiler.js
│   │   │   ├── runtime.js
│   │   │   ├── server/
│   │   │   │   ├── directives/
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── show.js
│   │   │   │   ├── modules/
│   │   │   │   │   ├── attrs.js
│   │   │   │   │   ├── class.js
│   │   │   │   │   ├── dom-props.js
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── style.js
│   │   │   │   └── util.js
│   │   │   ├── server-renderer.js
│   │   │   └── util/
│   │   │       ├── attrs.js
│   │   │       ├── class.js
│   │   │       ├── compat.js
│   │   │       ├── element.js
│   │   │       ├── index.js
│   │   │       └── style.js
│   │   └── weex/
│   │       ├── compiler/
│   │       │   ├── directives/
│   │       │   │   ├── index.js
│   │       │   │   └── model.js
│   │       │   ├── index.js
│   │       │   └── modules/
│   │       │       ├── append.js
│   │       │       ├── class.js
│   │       │       ├── index.js
│   │       │       ├── props.js
│   │       │       └── style.js
│   │       ├── compiler.js
│   │       ├── framework.js
│   │       ├── runtime/
│   │       │   ├── components/
│   │       │   │   ├── index.js
│   │       │   │   ├── transition-group.js
│   │       │   │   └── transition.js
│   │       │   ├── directives/
│   │       │   │   └── index.js
│   │       │   ├── index.js
│   │       │   ├── modules/
│   │       │   │   ├── attrs.js
│   │       │   │   ├── class.js
│   │       │   │   ├── events.js
│   │       │   │   ├── index.js
│   │       │   │   ├── style.js
│   │       │   │   └── transition.js
│   │       │   ├── node-ops.js
│   │       │   ├── patch.js
│   │       │   └── text-node.js
│   │       ├── runtime-factory.js
│   │       └── util/
│   │           └── index.js
│   ├── server/
│   │   ├── bundle-renderer/
│   │   │   ├── create-bundle-renderer.js
│   │   │   ├── create-bundle-runner.js
│   │   │   └── source-map-support.js
│   │   ├── create-renderer.js
│   │   ├── render-context.js
│   │   ├── render-stream.js
│   │   ├── render.js
│   │   ├── template-renderer/
│   │   │   ├── create-async-file-mapper.js
│   │   │   ├── index.js
│   │   │   ├── parse-template.js
│   │   │   └── template-stream.js
│   │   ├── util.js
│   │   ├── webpack-plugin/
│   │   │   ├── client.js
│   │   │   ├── server.js
│   │   │   └── util.js
│   │   └── write.js
│   ├── sfc/
│   │   └── parser.js
│   └── shared/
│       ├── constants.js
│       └── util.js
└── vuex-src/
    ├── helpers.js
    ├── index.esm.js
    ├── index.js
    ├── mixin.js
    ├── module/
    │   ├── module-collection.js
    │   └── module.js
    ├── plugins/
    │   ├── devtool.js
    │   └── logger.js
    ├── store.js
    └── util.js
Download .txt
SYMBOL INDEX (445 symbols across 129 files)

FILE: vue-router-src/components/link.js
  method let (line 127) | let child

FILE: vue-router-src/components/view.js
  method render (line 17) | render (_, { props, children, parent, data }) {
  function resolveProps (line 111) | function resolveProps (route, config) {
  function extend (line 132) | function extend (to, from) {

FILE: vue-router-src/create-matcher.js
  function addRoutes (line 22) | function addRoutes (routes) {
  method for (line 49) | for (const key in currentRoute.params) {
  function matchRoute (line 175) | function matchRoute (
  function resolveRecordPath (line 199) | function resolveRecordPath (path: string, record: RouteRecord): string {

FILE: vue-router-src/create-route-map.js
  method if (line 142) | if (!nameMap[name]) {

FILE: vue-router-src/history/base.js
  function normalizeBase (line 195) | function normalizeBase (base: ?string): string {
  method if (line 226) | if (current[i] !== next[i]) {
  method if (line 258) | if (typeof def !== 'function') {
  method if (line 274) | if (instance) {

FILE: vue-router-src/history/hash.js
  function checkFallback (line 81) | function checkFallback (base) {
  function ensureSlash (line 91) | function ensureSlash (): boolean {
  function getHash (line 100) | function getHash (): string {
  function getUrl (line 108) | function getUrl (path) {
  function pushHash (line 115) | function pushHash (path) {
  function replaceHash (line 123) | function replaceHash (path) {

FILE: vue-router-src/history/html5.js
  function getLocation (line 74) | function getLocation (base: string): string {

FILE: vue-router-src/index.js
  method constructor (line 36) | constructor (options: RouterOptions = {}) {
  method apply (line 177) | apply([], route.matched.map(m => {

FILE: vue-router-src/install.js
  function install (line 7) | function install (Vue) {

FILE: vue-router-src/util/async.js
  function runQueue (line 3) | function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Fun...

FILE: vue-router-src/util/location.js
  function assign (line 63) | function assign (a, b) {

FILE: vue-router-src/util/push-state.js
  function genKey (line 28) | function genKey (): string {
  function getStateKey (line 32) | function getStateKey () {
  function setStateKey (line 36) | function setStateKey (key: string) {

FILE: vue-router-src/util/resolve-components.js
  method return (line 7) | return (to, from, next) => {

FILE: vue-router-src/util/route.js
  function clone (line 37) | function clone (value) {
  constant START (line 53) | const START = createRoute(null, {
  function formatMatch (line 57) | function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
  function getFullPath (line 66) | function getFullPath (
  function isObjectEqual (line 97) | function isObjectEqual (a = {}, b = {}): boolean {
  function isIncludedRoute (line 116) | function isIncludedRoute (current: Route, target: Route): boolean {
  method for (line 127) | for (const key in target) {

FILE: vue-router-src/util/scroll.js
  function setupScroll (line 9) | function setupScroll () {
  function handleScroll (line 20) | function handleScroll (
  function saveScrollPosition (line 62) | function saveScrollPosition () {
  function getElementPosition (line 79) | function getElementPosition (el: Element, offset: Object): Object {
  function isValidPosition (line 89) | function isValidPosition (obj: Object): boolean {
  function normalizePosition (line 93) | function normalizePosition (obj: Object): Object {
  function normalizeOffset (line 100) | function normalizeOffset (obj: Object): Object {
  function isNumber (line 107) | function isNumber (v: any): boolean {
  function scrollToPosition (line 111) | function scrollToPosition (shouldScroll, position) {

FILE: vue-router-src/util/warn.js
  function assert (line 3) | function assert (condition: any, message: string) {
  function warn (line 9) | function warn (condition: any, message: string) {
  function isError (line 15) | function isError (err: any): boolean {

FILE: vue-src/compiler/codegen/events.js
  function genHandlers (line 37) | function genHandlers (
  method if (line 64) | if (!handler) {
  method if (line 84) | if (modifierCode[key]) {

FILE: vue-src/compiler/codegen/index.js
  function generate (line 23) | function generate (
  function genElement (line 51) | function genElement (el: ASTElement): string {
  function genStatic (line 96) | function genStatic (el: ASTElement): string {
  function genOnce (line 105) | function genOnce (el: ASTElement): string {
  function genIf (line 140) | function genIf (el: any): string {
  function genIfConditions (line 147) | function genIfConditions (conditions: ASTIfConditions): string {
  function genFor (line 168) | function genFor (el: any): string {
  function genData (line 194) | function genData (el: ASTElement): string {
  function genDirectives (line 273) | function genDirectives (el: ASTElement): string | void {
  function genScopedSlots (line 321) | function genScopedSlots (slots: { [key: string]: ASTElement }): string {
  function genScopedSlot (line 327) | function genScopedSlot (key: string, el: ASTElement) {

FILE: vue-src/compiler/directives/bind.js
  function bind (line 3) | function bind (el: ASTElement, dir: ASTDirective) {

FILE: vue-src/compiler/directives/model.js
  function genAssignmentCode (line 36) | function genAssignmentCode (
  function parseModel (line 67) | function parseModel (val: string): Object {
  function next (line 95) | function next (): number {
  function eof (line 99) | function eof (): boolean {
  function isStringStart (line 103) | function isStringStart (chr: number): boolean {
  function parseBracket (line 107) | function parseBracket (chr: number): void {
  function parseString (line 125) | function parseString (chr: number): void {

FILE: vue-src/compiler/error-detector.js
  function checkNode (line 33) | function checkNode (node: ASTNode, errors: Array<string>) {
  function checkEvent (line 59) | function checkEvent (exp: string, text: string, errors: Array<string>) {
  function checkFor (line 71) | function checkFor (node: ASTElement, text: string, errors: Array<string>) {
  function checkIdentifier (line 78) | function checkIdentifier (ident: ?string, type: string, text: string, er...
  function checkExpression (line 84) | function checkExpression (exp: string, text: string, errors: Array<strin...

FILE: vue-src/compiler/helpers.js
  function baseWarn (line 6) | function baseWarn (msg: string) {
  function addProp (line 20) | function addProp (el: ASTElement, name: string, value: string) {
  function addAttr (line 25) | function addAttr (el: ASTElement, name: string, value: string) {
  method parseFilters (line 104) | parseFilters(dynamicValue)
  method let (line 116) | let val

FILE: vue-src/compiler/index.js
  function baseCompile (line 10) | function baseCompile (
  function makeFunction (line 34) | function makeFunction (code, errors) {
  method if (line 69) | if (options.modules) {
  method if (line 73) | if (options.directives) {
  method if (line 113) | if (e.toString().match(/unsafe-eval|CSP/)) {

FILE: vue-src/compiler/optimizer.js
  function genStaticKeys (line 45) | function genStaticKeys (keys: string): Function {
  function markStatic (line 53) | function markStatic (node: ASTNode) {
  function markStaticRoots (line 83) | function markStaticRoots (node: ASTNode, isInFor: boolean) {
  function walkThroughConditionsBlocks (line 119) | function walkThroughConditionsBlocks (conditionBlocks: ASTIfConditions, ...
  function isStatic (line 126) | function isStatic (node: ASTNode): boolean {
  function isDirectChildOfTemplateFor (line 143) | function isDirectChildOfTemplateFor (node: ASTElement): boolean {

FILE: vue-src/compiler/parser/entity-decoder.js
  function decode (line 5) | function decode (html: string): string {

FILE: vue-src/compiler/parser/filter-parser.js
  function parseFilters (line 6) | function parseFilters (exp: string): string {
  function wrapFilter (line 93) | function wrapFilter (exp: string, filter: string): string {

FILE: vue-src/compiler/parser/html-parser.js
  constant IS_REGEX_CAPTURING_BROKEN (line 45) | let IS_REGEX_CAPTURING_BROKEN = false
  function decodeAttr (line 66) | function decodeAttr (value, shouldDecodeNewlines) {
  function parseHTML (line 72) | function parseHTML (html, options) {

FILE: vue-src/compiler/parser/index.js
  function parse (line 53) | function parse (
  function processPre (line 344) | function processPre (el) {
  function processRawAttrs (line 351) | function processRawAttrs (el) {
  function processKey (line 368) | function processKey (el) {
  function processRef (line 379) | function processRef (el) {
  function processFor (line 393) | function processFor (el) {
  function processIf (line 434) | function processIf (el) {
  function processIfConditions (line 458) | function processIfConditions (el, parent) {
  method if (line 478) | if (children[i].type === 1) {
  method warn (line 482) | warn(

FILE: vue-src/core/components/keep-alive.js
  method if (line 17) | if (typeof pattern === 'string') {
  method for (line 71) | for (const key in this.cache) {

FILE: vue-src/core/global-api/assets.js
  method if (line 16) | if (!definition) {

FILE: vue-src/core/global-api/extend.js
  function initExtend (line 7) | function initExtend (Vue: GlobalAPI) {
  function initProps (line 122) | function initProps (Comp) {
  function initComputed (line 130) | function initComputed (Comp) {

FILE: vue-src/core/global-api/index.js
  function initGlobalAPI (line 20) | function initGlobalAPI (Vue: GlobalAPI) {

FILE: vue-src/core/global-api/mixin.js
  function initMixin (line 6) | function initMixin (Vue: GlobalAPI) {

FILE: vue-src/core/global-api/use.js
  function initUse (line 6) | function initUse (Vue: GlobalAPI) {

FILE: vue-src/core/instance/events.js
  function initEvents (line 7) | function initEvents (vm: Component) {
  function add (line 23) | function add (event, fn, once) {
  function remove (line 32) | function remove (event, fn) {
  function eventsMixin (line 47) | function eventsMixin (Vue: Class<Component>) {

FILE: vue-src/core/instance/index.js
  function Vue (line 8) | function Vue (options) {

FILE: vue-src/core/instance/init.js
  function initMixin (line 16) | function initMixin (Vue: Class<Component>) {
  function initInternalComponent (line 84) | function initInternalComponent (vm: Component, options: InternalComponen...
  function resolveConstructorOptions (line 101) | function resolveConstructorOptions (Ctor: Class<Component>) {
  method if (line 138) | if (latest[key] !== sealed[key]) {

FILE: vue-src/core/instance/inject.js
  function initProvide (line 7) | function initProvide (vm: Component) {
  function initInjections (line 16) | function initInjections (vm: Component) {

FILE: vue-src/core/instance/lifecycle.js
  function initLifecycle (line 23) | function initLifecycle (vm: Component) {
  function lifecycleMixin (line 50) | function lifecycleMixin (Vue: Class<Component>) {
  function isInInactiveTree (line 276) | function isInInactiveTree (vm) {
  function activateChildComponent (line 284) | function activateChildComponent (vm: Component, direct?: boolean) {
  function deactivateChildComponent (line 304) | function deactivateChildComponent (vm: Component, direct?: boolean) {
  function callHook (line 321) | function callHook (vm: Component, hook: string) {

FILE: vue-src/core/instance/proxy.js
  method set (line 33) | set (target, key, value) {
  method has (line 46) | has (target, key) {
  method get (line 57) | get (target, key) {

FILE: vue-src/core/instance/render-helpers/bind-object-props.js
  method if (line 16) | if (value) {

FILE: vue-src/core/instance/render-helpers/render-list.js
  method key (line 14) | key
  method isObject (line 25) | isObject(val)) {

FILE: vue-src/core/instance/render-helpers/resolve-filter.js
  function resolveFilter (line 9) | function resolveFilter (id: string): Function {

FILE: vue-src/core/instance/render-helpers/resolve-slots.js
  function isWhitespace (line 39) | function isWhitespace (node: VNode): boolean {

FILE: vue-src/core/instance/render.js
  function initRender (line 30) | function initRender (vm: Component) {
  function renderMixin (line 49) | function renderMixin (Vue: Class<Component>) {

FILE: vue-src/core/instance/state.js
  function proxy (line 34) | function proxy (target: Object, sourceKey: string, key: string) {
  function initState (line 45) | function initState (vm: Component) {
  function initProps (line 72) | function initProps (vm: Component, propsOptions: Object) {
  function initData (line 128) | function initData (vm: Component) {
  function getData (line 173) | function getData (data: Function, vm: Component): any {
  function initComputed (line 185) | function initComputed (vm: Component, computed: Object) {
  function defineComputed (line 231) | function defineComputed (target: any, key: string, userDef: Object | Fun...
  function createComputedGetter (line 258) | function createComputedGetter (key) {
  function initMethods (line 276) | function initMethods (vm: Component, methods: Object) {
  function initWatch (line 301) | function initWatch (vm: Component, watch: Object) {
  function createWatcher (line 316) | function createWatcher (vm: Component, key: string, handler: any) {
  function stateMixin (line 342) | function stateMixin (Vue: Class<Component>) {

FILE: vue-src/core/observer/dep.js
  function pushTarget (line 57) | function pushTarget (_target: Watcher) {
  function popTarget (line 63) | function popTarget () {

FILE: vue-src/core/observer/index.js
  method for (line 117) | for (let i = 0, l = keys.length; i < l; i++) {
  method if (line 132) | if (!isObject(value)) {

FILE: vue-src/core/observer/scheduler.js
  constant MAX_UPDATE_COUNT (line 13) | const MAX_UPDATE_COUNT = 100

FILE: vue-src/core/observer/watcher.js
  method if (line 183) | if (this.lazy) {
  method if (line 202) | if (this.active) {
  method if (line 261) | if (this.active) {

FILE: vue-src/core/util/env.js
  method get (line 26) | get () {
  function isNative (line 56) | function isNative (Ctor: any): boolean {
  function nextTickHandler (line 81) | function nextTickHandler () {

FILE: vue-src/core/util/error.js
  function handleError (line 7) | function handleError (err: Error, vm: any, info: string) {

FILE: vue-src/core/util/lang.js
  function isReserved (line 8) | function isReserved (str: string): boolean {

FILE: vue-src/core/util/options.js
  function mergeData (line 50) | function mergeData (to: Object, from: ?Object): Object {
  method if (line 75) | if (!vm) {
  method for (line 246) | for (const key in props) {
  method mergeField (line 315) | mergeField(key)
  method mergeField (line 323) | mergeField (key) {

FILE: vue-src/core/util/props.js
  method if (line 31) | if (absent && !hasOwn(prop, 'default')) {
  method if (line 66) | if (!hasOwn(prop, 'default')) {
  method if (line 120) | if (!Array.isArray(type)) {
  method if (line 140) | if (!validator(value)) {

FILE: vue-src/core/vdom/create-component.js
  method if (line 38) | if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
  method if (line 108) | if (isUndef(Ctor)) {
  method if (line 124) | if (process.env.NODE_ENV !== 'production') {

FILE: vue-src/core/vdom/create-element.js
  constant SIMPLE_NORMALIZE (line 21) | const SIMPLE_NORMALIZE = 1
  constant ALWAYS_NORMALIZE (line 22) | const ALWAYS_NORMALIZE = 2
  function createElement (line 26) | function createElement (
  method if (line 61) | if (isDef(data) && isDef((data: any).__ob__)) {
  method for (line 136) | for (let i = 0, l = vnode.children.length; i < l; i++) {

FILE: vue-src/core/vdom/create-functional-component.js
  method for (line 24) | for (const key in propOptions) {
  function mergeProps (line 53) | function mergeProps (to, from) {

FILE: vue-src/core/vdom/helpers/extract-props.js
  method for (line 27) | for (const key in propOptions) {

FILE: vue-src/core/vdom/helpers/merge-hook.js
  function mergeVNodeHook (line 6) | function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {

FILE: vue-src/core/vdom/helpers/normalize-children.js
  function simpleNormalizeChildren (line 18) | function simpleNormalizeChildren (children: any) {
  method if (line 50) | if (isDef(last) && isDef(last.text)) {
  method if (line 57) | if (isDef(c.text) && isDef(last) && isDef(last.text)) {

FILE: vue-src/core/vdom/helpers/resolve-async-component.js
  function ensureCtor (line 12) | function ensureCtor (comp, base) {
  function resolveAsyncComponent (line 18) | function resolveAsyncComponent (

FILE: vue-src/core/vdom/modules/directives.js
  function updateDirectives (line 15) | function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function _update (line 21) | function _update (oldVnode, vnode) {
  function normalizeDirectives (line 83) | function normalizeDirectives (
  function getRawDirName (line 103) | function getRawDirName (dir: VNodeDirective): string {
  function callHook (line 107) | function callHook (dir, hook, vnode, oldVnode, isDestroy) {

FILE: vue-src/core/vdom/patch.js
  function sameVnode (line 42) | function sameVnode (a, b) {
  function sameInputType (line 58) | function sameInputType (a, b) {
  function createKeyToOldIdx (line 71) | function createKeyToOldIdx (children, beginIdx, endIdx) {
  function createPatchFunction (line 82) | function createPatchFunction (backend) {

FILE: vue-src/core/vdom/vnode.js
  function createTextVNode (line 86) | function createTextVNode (val: string | number) {
  function cloneVNode (line 95) | function cloneVNode (vnode: VNode): VNode {

FILE: vue-src/platforms/web/compiler/directives/html.js
  function html (line 5) | function html (el: ASTElement, dir: ASTDirective) {

FILE: vue-src/platforms/web/compiler/directives/model.js
  constant RANGE_TOKEN (line 11) | const RANGE_TOKEN = '__r'
  constant CHECKBOX_RADIO_TOKEN (line 12) | const CHECKBOX_RADIO_TOKEN = '__c'
  function genCheckboxModel (line 68) | function genCheckboxModel (
  function genRadioModel (line 99) | function genRadioModel (
  function genSelect (line 111) | function genSelect (

FILE: vue-src/platforms/web/compiler/directives/text.js
  function text (line 5) | function text (el: ASTElement, dir: ASTDirective) {

FILE: vue-src/platforms/web/compiler/modules/class.js
  function transformNode (line 10) | function transformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 33) | function genData (el: ASTElement): string {

FILE: vue-src/platforms/web/compiler/modules/style.js
  function transformNode (line 11) | function transformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 36) | function genData (el: ASTElement): string {

FILE: vue-src/platforms/web/runtime/components/transition-group.js
  method if (line 47) | if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
  method if (line 127) | if (!hasTransition) {

FILE: vue-src/platforms/web/runtime/components/transition.js
  function componentOptions (line 30) | function getRealChild (vnode: ?VNode): ?VNode {
  function isSameChild (line 71) | function isSameChild (child: VNode, oldChild: VNode): boolean {

FILE: vue-src/platforms/web/runtime/directives/model.js
  method inserted (line 21) | inserted (el, binding, vnode) {
  method componentUpdated (line 50) | componentUpdated (el, binding, vnode) {
  function setSelected (line 67) | function setSelected (el, binding, vm) {
  function hasNoMatchingOption (line 102) | function hasNoMatchingOption (value, options) {
  function getValue (line 111) | function getValue (option) {
  function onCompositionStart (line 117) | function onCompositionStart (e) {
  function onCompositionEnd (line 121) | function onCompositionEnd (e) {
  function trigger (line 126) | function trigger (el, type) {

FILE: vue-src/platforms/web/runtime/directives/show.js
  function locateNode (line 7) | function locateNode (vnode: VNode): VNodeWithData {

FILE: vue-src/platforms/web/runtime/modules/attrs.js
  function updateAttrs (line 21) | function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function setAttr (line 64) | function setAttr (el: Element, key: string, value: any) {

FILE: vue-src/platforms/web/runtime/modules/class.js
  function updateClass (line 15) | function updateClass (oldVnode: any, vnode: any) {

FILE: vue-src/platforms/web/runtime/modules/dom-props.js
  function updateDOMProps (line 5) | function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function shouldUpdateValue (line 51) | function shouldUpdateValue (
  function isDirty (line 63) | function isDirty (elm: acceptValueElm, checkVal: string): boolean {
  function isInputChanged (line 68) | function isInputChanged (elm: any, newVal: string): boolean {

FILE: vue-src/platforms/web/runtime/modules/events.js
  function normalizeEvents (line 12) | function normalizeEvents (on) {
  function add (line 31) | function add (

FILE: vue-src/platforms/web/runtime/modules/style.js
  function updateStyle (line 47) | function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {

FILE: vue-src/platforms/web/runtime/modules/transition.js
  function leave (line 173) | function leave (vnode: VNodeWithData, rm: Function) {
  function checkDuration (line 278) | function checkDuration (val, name, vnode) {
  function isValidDuration (line 294) | function isValidDuration (val) {
  function getHookArgumentsLength (line 304) | function getHookArgumentsLength (fn: Function): boolean {
  function _enter (line 321) | function _enter (_: any, vnode: VNodeWithData) {
  method if (line 332) | if (vnode.data.show !== true) {

FILE: vue-src/platforms/web/runtime/node-ops.js
  function createElement (line 7) | function createElement (tagName: string, vnode: VNode): Element {
  function createElementNS (line 19) | function createElementNS (namespace: string, tagName: string): Element {
  function createTextNode (line 23) | function createTextNode (text: string): Text {
  function createComment (line 27) | function createComment (text: string): Comment {
  function insertBefore (line 31) | function insertBefore (parentNode: Node, newNode: Node, referenceNode: N...
  function removeChild (line 35) | function removeChild (node: Node, child: Node) {
  function appendChild (line 39) | function appendChild (node: Node, child: Node) {
  function tagName (line 51) | function tagName (node: Element): string {
  function setTextContent (line 55) | function setTextContent (node: Node, text: string) {
  function setAttribute (line 59) | function setAttribute (node: Element, key: string, val: string) {

FILE: vue-src/platforms/web/runtime/transition-util.js
  method if (line 8) | if (!def) {
  method if (line 46) | if (window.ontransitionend === undefined &&
  method if (line 130) | if (transitionTimeout > 0) {
  method if (line 136) | if (animationTimeout > 0) {
  method while (line 167) | while (delays.length < durations.length) {

FILE: vue-src/platforms/web/server/directives/show.js
  function show (line 3) | function show (node: VNodeWithData, dir: VNodeDirective) {

FILE: vue-src/platforms/web/server/modules/attrs.js
  function renderAttrs (line 16) | function renderAttrs (node: VNodeWithData): string {
  function renderAttr (line 42) | function renderAttr (key: string, value: string): string {

FILE: vue-src/platforms/web/server/modules/dom-props.js
  function renderDOMProps (line 8) | function renderDOMProps (node: VNodeWithData): string {
  function setText (line 42) | function setText (node, text, raw) {

FILE: vue-src/platforms/web/server/modules/style.js
  function genStyleText (line 7) | function genStyleText (vnode: VNode): string {

FILE: vue-src/platforms/web/util/class.js
  function genClassForVnode (line 5) | function genClassForVnode (vnode: VNode): string {
  function mergeClassData (line 23) | function mergeClassData (child: VNodeData, parent: VNodeData): {
  function genClassFromData (line 35) | function genClassFromData (data: Object): string {
  function stringifyClass (line 49) | function stringifyClass (value: any): string {

FILE: vue-src/platforms/web/util/compat.js
  function shouldDecode (line 6) | function shouldDecode (content: string, encoded: string): boolean {

FILE: vue-src/platforms/web/util/element.js
  function isUnknownElement (line 52) | function isUnknownElement (tag: string): boolean {

FILE: vue-src/platforms/web/util/index.js
  method if (line 14) | if (typeof el === 'string') {

FILE: vue-src/platforms/web/util/style.js
  function getStyle (line 43) | function getStyle (vnode: VNode, checkChild: boolean): Object {

FILE: vue-src/platforms/weex/compiler/modules/append.js
  function preTransformNode (line 3) | function preTransformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 13) | function genData (el: ASTElement): string {

FILE: vue-src/platforms/weex/compiler/modules/class.js
  function transformNode (line 15) | function transformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 37) | function genData (el: ASTElement): string {
  function parseStaticClass (line 48) | function parseStaticClass (staticClass: ?string, options: CompilerOption...

FILE: vue-src/platforms/weex/compiler/modules/props.js
  function normalizeKeyName (line 7) | function normalizeKeyName (str: string) : string {
  function transformNode (line 16) | function transformNode (el: ASTElement, options: CompilerOptions) {

FILE: vue-src/platforms/weex/compiler/modules/style.js
  function transformNode (line 18) | function transformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 40) | function genData (el: ASTElement): string {
  function parseStaticStyle (line 51) | function parseStaticStyle (staticStyle: ?string, options: CompilerOption...

FILE: vue-src/platforms/weex/framework.js
  function init (line 21) | function init (cfg) {
  function reset (line 31) | function reset () {
  function clear (line 45) | function clear (obj) {
  function createInstance (line 59) | function createInstance (
  function destroyInstance (line 117) | function destroyInstance (instanceId) {
  function refreshInstance (line 132) | function refreshInstance (instanceId, data) {
  function getRoot (line 148) | function getRoot (instanceId) {
  function receiveTasks (line 163) | function receiveTasks (instanceId, tasks) {
  function registerModules (line 197) | function registerModules (newModules) {
  function registerComponents (line 216) | function registerComponents (newComponents) {
  function createVueModuleInstance (line 234) | function createVueModuleInstance (instanceId, moduleGetter) {
  function genModuleGetter (line 297) | function genModuleGetter (instanceId) {
  function getInstanceTimer (line 323) | function getInstanceTimer (instanceId, moduleGetter) {
  function callFunction (line 357) | function callFunction (globalObjects, body) {
  function normalize (line 380) | function normalize (v, instance) {
  function typof (line 414) | function typof (v) {

FILE: vue-src/platforms/weex/runtime/components/transition-group.js
  method created (line 14) | created () {
  method render (line 32) | render (h) {
  method beforeUpdate (line 78) | beforeUpdate () {
  method updated (line 89) | updated () {
  method getMoveData (line 132) | getMoveData (context, moveClass) {

FILE: vue-src/platforms/weex/runtime/modules/attrs.js
  function updateAttrs (line 5) | function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {

FILE: vue-src/platforms/weex/runtime/modules/class.js
  function updateClass (line 5) | function updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function getStyle (line 42) | function getStyle (oldClassList: Array<string>, classList: Array<string>...

FILE: vue-src/platforms/weex/runtime/modules/events.js
  function add (line 7) | function add (

FILE: vue-src/platforms/weex/runtime/modules/style.js
  function createStyle (line 7) | function createStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function updateStyle (line 22) | function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function toObject (line 55) | function toObject (arr) {

FILE: vue-src/platforms/weex/runtime/modules/transition.js
  function enter (line 12) | function enter (_, vnode) {
  function leave (line 131) | function leave (vnode, rm) {
  function getEnterTargetState (line 227) | function getEnterTargetState (el, stylesheet, startClass, endClass, acti...

FILE: vue-src/platforms/weex/runtime/node-ops.js
  function createElement (line 8) | function createElement (tagName) {
  function createElementNS (line 12) | function createElementNS (namespace, tagName) {
  function createTextNode (line 16) | function createTextNode (text) {
  function createComment (line 20) | function createComment (text) {
  function insertBefore (line 24) | function insertBefore (node, target, before) {
  function removeChild (line 39) | function removeChild (node, child) {
  function appendChild (line 47) | function appendChild (node, child) {
  function parentNode (line 63) | function parentNode (node) {
  function nextSibling (line 67) | function nextSibling (node) {
  function tagName (line 71) | function tagName (node) {
  function setTextContent (line 75) | function setTextContent (node, text) {
  function setAttribute (line 79) | function setAttribute (node, key, val) {

FILE: vue-src/platforms/weex/runtime/text-node.js
  function TextNode (line 3) | function TextNode (text) {

FILE: vue-src/platforms/weex/util/index.js
  function mustUseProp (line 28) | function mustUseProp () { /* console.log('mustUseProp') */ }
  function getTagNamespace (line 29) | function getTagNamespace () { /* console.log('getTagNamespace') */ }
  function isUnknownElement (line 30) | function isUnknownElement () { /* console.log('isUnknownElement') */ }
  function query (line 32) | function query (el, document) {

FILE: vue-src/server/bundle-renderer/create-bundle-renderer.js
  constant INVALID_MSG (line 11) | const INVALID_MSG =
  method if (line 83) | if (typeof context === 'function') {

FILE: vue-src/server/bundle-renderer/create-bundle-runner.js
  function createContext (line 8) | function createContext (context) {
  function compileModule (line 25) | function compileModule (files, basedir) {
  function deepClone (line 75) | function deepClone (val) {
  function createBundleRunner (line 89) | function createBundleRunner (entry, files, basedir, runInNewContext) {

FILE: vue-src/server/bundle-renderer/source-map-support.js
  function createSourceMapConsumers (line 7) | function createSourceMapConsumers (rawMaps: Object) {
  function rewriteErrorTrace (line 15) | function rewriteErrorTrace (e: any, mapConsumers: {
  function rewriteTraceLine (line 25) | function rewriteTraceLine (trace: string, mapConsumers: {

FILE: vue-src/server/render-context.js
  function normalizeAsync (line 111) | function normalizeAsync (cache, method) {

FILE: vue-src/server/render-stream.js
  class RenderStream (line 16) | class RenderStream extends stream.Readable {
    method constructor (line 25) | constructor (render: Function) {
    method pushBySize (line 51) | pushBySize (n: number) {
    method tryRender (line 57) | tryRender () {
    method tryNext (line 65) | tryNext () {
    method _read (line 73) | _read (n: number) {

FILE: vue-src/server/render.js
  function renderNode (line 42) | function renderNode (node, isRoot, context) {
  function renderComponent (line 62) | function renderComponent (node, isRoot, context) {
  function renderComponentWithCache (line 127) | function renderComponentWithCache (node, isRoot, key, context) {
  function renderComponentInner (line 144) | function renderComponentInner (node, isRoot, context) {
  function renderElement (line 163) | function renderElement (el, isRoot, context) {
  function hasAncestorData (line 188) | function hasAncestorData (node: VNode) {
  function tmp (line 193) | function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
  function renderStartingTag (line 209) | function renderStartingTag (node: VNode, context) {

FILE: vue-src/server/template-renderer/create-async-file-mapper.js
  method for (line 23) | for (let j = 0; j < mapped.length; j++) {

FILE: vue-src/server/template-renderer/index.js
  method constructor (line 50) | constructor (options: TemplateRendererOptions) {
  method bindRenderFns (line 71) | bindRenderFns (context: Object) {
  method if (line 220) | if (!context._mappedfiles && context._registeredComponents && this.mapFi...
  method if (line 228) | if (!this.parsedTemplate) {

FILE: vue-src/server/template-renderer/parse-template.js
  method if (line 19) | if (typeof template === 'object') {

FILE: vue-src/server/template-renderer/template-stream.js
  class TemplateStream (line 7) | class TemplateStream extends Transform {
    method constructor (line 14) | constructor (
    method _transform (line 27) | _transform (data: Buffer | string, encoding: string, done: Function) {
    method start (line 36) | start () {
    method _flush (line 62) | _flush (done: Function) {

FILE: vue-src/server/webpack-plugin/client.js
  class VueSSRClientPlugin (line 5) | class VueSSRClientPlugin {
    method constructor (line 6) | constructor (options = {}) {
    method apply (line 12) | apply (compiler) {

FILE: vue-src/server/webpack-plugin/server.js
  class VueSSRServerPlugin (line 3) | class VueSSRServerPlugin {
    method constructor (line 4) | constructor (options = {}) {
    method apply (line 10) | apply (compiler) {

FILE: vue-src/server/write.js
  constant MAX_STACK_DEPTH (line 3) | const MAX_STACK_DEPTH = 1000
  function createWriteFunction (line 5) | function createWriteFunction (

FILE: vue-src/sfc/parser.js
  method for (line 66) | for (let i = 0; i < attrs.length; i++) {

FILE: vue-src/shared/constants.js
  constant SSR_ATTR (line 2) | const SSR_ATTR = 'data-server-rendered'
  constant ASSET_TYPES (line 5) | const ASSET_TYPES = [
  constant LIFECYCLE_HOOKS (line 12) | const LIFECYCLE_HOOKS = [

FILE: vue-src/shared/util.js
  function isPlainObject (line 40) | function isPlainObject (obj: any): boolean {
  function isRegExp (line 44) | function isRegExp (v: any): boolean {
  function toString (line 52) | function toString (val: any): string {
  method if (line 112) | if (arr.length) {
  method for (line 205) | for (const key in _from) {
  method if (line 218) | if (arr[i]) {

FILE: vuex-src/helpers.js
  function normalizeMap (line 117) | function normalizeMap (map) {
  function normalizeNamespace (line 123) | function normalizeNamespace (fn) {
  function getModuleByNamespace (line 138) | function getModuleByNamespace (store, helper, namespace) {

FILE: vuex-src/mixin.js
  function vuexInit (line 25) | function vuexInit () {

FILE: vuex-src/module/module-collection.js
  class ModuleCollection (line 5) | class ModuleCollection {
    method constructor (line 6) | constructor (rawRootModule) {
    method get (line 12) | get (path) {
    method getNamespace (line 24) | getNamespace (path) {
    method update (line 32) | update (rawRootModule) {
    method register (line 37) | register (path, rawModule, runtime = true) {
    method unregister (line 64) | unregister (path) {
  function update (line 74) | function update (path, targetModule, newModule) {
  function assertRawModule (line 105) | function assertRawModule (path, rawModule) {
  function makeAssertionMessage (line 118) | function makeAssertionMessage (path, key, type, value) {

FILE: vuex-src/module/module.js
  class Module (line 4) | class Module {
    method constructor (line 5) | constructor (rawModule, runtime) {
    method namespaced (line 16) | get namespaced () {
    method addChild (line 21) | addChild (key, module) {
    method removeChild (line 26) | removeChild (key) {
    method getChild (line 31) | getChild (key) {
    method update (line 36) | update (rawModule) {
    method forEachChild (line 50) | forEachChild (fn) {
    method forEachGetter (line 55) | forEachGetter (fn) {
    method forEachAction (line 62) | forEachAction (fn) {
    method forEachMutation (line 69) | forEachMutation (fn) {

FILE: vuex-src/plugins/devtool.js
  function devtoolPlugin (line 6) | function devtoolPlugin (store) {

FILE: vuex-src/plugins/logger.js
  function createLogger (line 5) | function createLogger ({
  function repeat (line 52) | function repeat (str, times) {
  function pad (line 56) | function pad (num, maxLength) {

FILE: vuex-src/store.js
  class Store (line 9) | class Store {
    method constructor (line 10) | constructor (options = {}) {
    method state (line 98) | get state () {
    method state (line 102) | set state (v) {
    method commit (line 109) | commit (_type, _payload, _options) {
    method dispatch (line 148) | dispatch (_type, _payload) {
    method subscribe (line 171) | subscribe (fn) {
    method watch (line 185) | watch (getter, cb, options) {
    method replaceState (line 193) | replaceState (state) {
    method registerModule (line 200) | registerModule (path, rawModule) {
    method unregisterModule (line 219) | unregisterModule (path) {
    method hotUpdate (line 240) | hotUpdate (newOptions) {
    method _withCommit (line 248) | _withCommit (fn) {
  function resetStore (line 258) | function resetStore (store, hot) {
  function resetStoreVM (line 271) | function resetStoreVM (store, state, hot) {
  function installModule (line 325) | function installModule (store, rootState, path, module, hot) {
  function makeLocalContext (line 379) | function makeLocalContext (store, namespace, path) {
  function makeLocalGetters (line 433) | function makeLocalGetters (store, namespace) {
  function registerMutation (line 457) | function registerMutation (store, type, handler, local) {
  function registerAction (line 466) | function registerAction (store, type, handler, local) {
  function registerGetter (line 496) | function registerGetter (store, type, rawGetter, local) {
  function enableStrictMode (line 517) | function enableStrictMode (store) {
  function getNestedState (line 526) | function getNestedState (state, path) {
  function unifyObjectStyle (line 532) | function unifyObjectStyle (type, payload, options) {
  function install (line 547) | function install (_Vue) {

FILE: vuex-src/util.js
  function find (line 9) | function find (list, f) {
  function deepCopy (line 22) | function deepCopy (obj, cache = []) {
  function forEachValue (line 52) | function forEachValue (obj, fn) {
  function isObject (line 56) | function isObject (obj) {
  function isPromise (line 61) | function isPromise (val) {
  function assert (line 65) | function assert (condition, msg) {
Condensed preview — 213 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (672K chars).
[
  {
    "path": "README.md",
    "chars": 1707,
    "preview": "# learnVue\n\n## 介绍\n\nVue.js源码分析,记录了个人学习Vue.js源码的过程中的一些心得以及收获。以及对于Vue框架,周边库的一些个人见解。\n\n在学习的过程中我为Vue.js(2.3.0)、Vuex(2.4.0)、Vue"
  },
  {
    "path": "docs/VNode节点.MarkDown",
    "chars": 10083,
    "preview": "## 抽象DOM树\n\n在刀耕火种的年代,我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。\n\n那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树,在修改抽象树数据后将抽象"
  },
  {
    "path": "docs/VirtualDOM与diff(Vue实现).MarkDown",
    "chars": 20577,
    "preview": "## VNode\n\n在刀耕火种的年代,我们需要在各个事件方法中直接操作DOM来达到修改视图的目的。但是当应用一大就会变得难以维护。\n\n那我们是不是可以把真实DOM树抽象成一棵以JavaScript对象构成的抽象树,在修改抽象树数据后将抽象树"
  },
  {
    "path": "docs/Vue.js异步更新DOM策略及nextTick.MarkDown",
    "chars": 11115,
    "preview": "## 操作DOM\n\n在使用vue.js的时候,有时候因为一些特定的业务场景,不得不去操作DOM,比如这样:\n\n```html\n<template>\n  <div>\n    <div ref=\"test\">{{test}}</div>\n   "
  },
  {
    "path": "docs/Vuex源码解析.MarkDown",
    "chars": 18699,
    "preview": "## Vuex\n\n我们在使用Vue.js开发复杂的应用时,经常会遇到多个组件共享同一个状态,亦或是多个组件会去更新同一个状态,在应用代码量较少的时候,我们可以组件间通信去维护修改数据,或者是通过事件总线来进行数据的传递以及修改。但是当应用逐"
  },
  {
    "path": "docs/Vue事件机制.MarkDown",
    "chars": 3906,
    "preview": "## Vue事件API\n\n众所周知,Vue.js为我们提供了四个事件API,分别是[$on](https://cn.vuejs.org/v2/api/#vm-on-event-callback),[$once](https://cn.vue"
  },
  {
    "path": "docs/Vue组件间通信.MarkDown",
    "chars": 3343,
    "preview": "## 什么是Vue组件?\n\n[组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以"
  },
  {
    "path": "docs/从template到DOM(Vue.js源码角度看内部运行机制).MarkDown",
    "chars": 26837,
    "preview": "## 从new一个Vue对象开始\n\n```javascript\nlet vm = new Vue({\n    el: '#app',\n    /*some options*/\n});\n```\n\n很多同学好奇,在new一个Vue对象的时候,内"
  },
  {
    "path": "docs/从源码角度再看数据绑定.MarkDown",
    "chars": 18777,
    "preview": "## 数据绑定原理\n\n前面已经讲过Vue数据绑定的原理了,现在从源码来看一下数据绑定在Vue中是如何实现的。\n\n首先看一下Vue.js官网介绍响应式原理的这张图。\n\n![](https://cn.vuejs.org/images/data."
  },
  {
    "path": "docs/依赖收集.MarkDown",
    "chars": 2631,
    "preview": "## 为什么要依赖收集\n\n先看下面这段代码\n\n```javascript\nnew Vue({\n    template: \n        `<div>\n            <span>text1:</span> {{text1}}\n "
  },
  {
    "path": "docs/响应式原理.MarkDown",
    "chars": 2290,
    "preview": "## 关于Vue.js\n\nVue.js是一款MVVM框架,上手快速简单易用,通过响应式在修改数据的时候更新视图。Vue.js的响应式原理依赖于[Object.defineProperty](https://developer.mozilla"
  },
  {
    "path": "docs/聊聊Vue的template编译.MarkDown",
    "chars": 14514,
    "preview": "## $mount\n\n首先看一下mount的代码\n\n```javascript\n/*把原本不带编译的$mount方法保存下来,在最后会调用。*/\nconst mount = Vue.prototype.$mount\n/*挂载组件,带模板编译"
  },
  {
    "path": "docs/聊聊keep-alive组件的使用及其实现原理.MarkDown",
    "chars": 5350,
    "preview": "## keep-alive\n\nkeep-alive是Vue.js的一个内置组件。它能够不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。\n\n它提供了include与excl"
  },
  {
    "path": "docs/说说element组件库broadcast与dispatch.MarkDown",
    "chars": 1830,
    "preview": "众所周知,Vue 在 2.0 版本中去除了$broadcast方法以及$dispatch 方法,最近在学习饿了么的[Element](https://github.com/ElemeFE/element)时重新实现了这两种方法,并以 min"
  },
  {
    "path": "vue-router-src/components/link.js",
    "chars": 3803,
    "preview": "/* @flow */\n\nimport { createRoute, isSameRoute, isIncludedRoute } from '../util/route'\nimport { _Vue } from '../install'"
  },
  {
    "path": "vue-router-src/components/view.js",
    "chars": 3982,
    "preview": "import { warn } from '../util/warn'\n\n/* router-view组件 */\nexport default {\n  name: 'RouterView',\n  /* \n    https://cn.vue"
  },
  {
    "path": "vue-router-src/create-matcher.js",
    "chars": 5703,
    "preview": "/* @flow */\n\nimport type VueRouter from './index'\nimport { resolvePath } from './util/path'\nimport { assert, warn } from"
  },
  {
    "path": "vue-router-src/create-route-map.js",
    "chars": 4847,
    "preview": "/* @flow */\n\nimport Regexp from 'path-to-regexp'\nimport { cleanPath } from './util/path'\nimport { assert, warn } from '."
  },
  {
    "path": "vue-router-src/history/abstract.js",
    "chars": 1260,
    "preview": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\n\nexport class AbstractHistory extends H"
  },
  {
    "path": "vue-router-src/history/base.js",
    "chars": 8568,
    "preview": "/* @flow */\n\nimport { _Vue } from '../install'\nimport type Router from '../index'\nimport { inBrowser } from '../util/dom"
  },
  {
    "path": "vue-router-src/history/hash.js",
    "chars": 3286,
    "preview": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\nimport { cleanPath } from '../util/path"
  },
  {
    "path": "vue-router-src/history/html5.js",
    "chars": 2422,
    "preview": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\nimport { cleanPath } from '../util/path"
  },
  {
    "path": "vue-router-src/index.js",
    "chars": 5865,
    "preview": "/* @flow */\n\nimport { install } from './install'\nimport { START } from './util/route'\nimport { assert } from './util/war"
  },
  {
    "path": "vue-router-src/install.js",
    "chars": 2092,
    "preview": "import View from './components/view'\nimport Link from './components/link'\n\nexport let _Vue\n\n/* Vue.use安装插件时候需要暴露的install"
  },
  {
    "path": "vue-router-src/util/async.js",
    "chars": 349,
    "preview": "/* @flow */\n\nexport function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {\n  const step = inde"
  },
  {
    "path": "vue-router-src/util/dom.js",
    "chars": 68,
    "preview": "/* @flow */\n\nexport const inBrowser = typeof window !== 'undefined'\n"
  },
  {
    "path": "vue-router-src/util/location.js",
    "chars": 1695,
    "preview": "/* @flow */\n\nimport type VueRouter from '../index'\nimport { parsePath, resolvePath } from './path'\nimport { resolveQuery"
  },
  {
    "path": "vue-router-src/util/params.js",
    "chars": 599,
    "preview": "/* @flow */\n\nimport { warn } from './warn'\nimport Regexp from 'path-to-regexp'\n\n// $flow-disable-line\nconst regexpCompil"
  },
  {
    "path": "vue-router-src/util/path.js",
    "chars": 1428,
    "preview": "/* @flow */\n\nexport function resolvePath (\n  relative: string,\n  base: string,\n  append?: boolean\n): string {\n  const fi"
  },
  {
    "path": "vue-router-src/util/push-state.js",
    "chars": 1415,
    "preview": "/* @flow */\n\nimport { inBrowser } from './dom'\nimport { saveScrollPosition } from './scroll'\n\nexport const supportsPushS"
  },
  {
    "path": "vue-router-src/util/query.js",
    "chars": 2167,
    "preview": "/* @flow */\n\nimport { warn } from './warn'\n\nconst encodeReserveRE = /[!'()*]/g\nconst encodeReserveReplacer = c => '%' + "
  },
  {
    "path": "vue-router-src/util/resolve-components.js",
    "chars": 2990,
    "preview": "/* @flow */\n\nimport { _Vue } from '../install'\nimport { warn, isError } from './warn'\n\nexport function resolveAsyncCompo"
  },
  {
    "path": "vue-router-src/util/route.js",
    "chars": 3217,
    "preview": "/* @flow */\n\nimport type VueRouter from '../index'\nimport { stringifyQuery } from './query'\n\nconst trailingSlashRE = /\\/"
  },
  {
    "path": "vue-router-src/util/scroll.js",
    "chars": 3224,
    "preview": "/* @flow */\n\nimport type Router from '../index'\nimport { assert } from './warn'\nimport { getStateKey, setStateKey } from"
  },
  {
    "path": "vue-router-src/util/warn.js",
    "chars": 464,
    "preview": "/* @flow */\n\nexport function assert (condition: any, message: string) {\n  if (!condition) {\n    throw new Error(`[vue-ro"
  },
  {
    "path": "vue-src/compiler/codegen/events.js",
    "chars": 3610,
    "preview": "/* @flow */\n\nconst fnExpRE = /^\\s*([\\w$_]+|\\([^)]*?\\))\\s*=>|^function\\s*\\(/\nconst simplePathRE = /^\\s*[A-Za-z_$][\\w$]*(?"
  },
  {
    "path": "vue-src/compiler/codegen/index.js",
    "chars": 12616,
    "preview": "/* @flow */\n\nimport { genHandlers } from './events'\nimport { baseWarn, pluckModuleFunction } from '../helpers'\nimport ba"
  },
  {
    "path": "vue-src/compiler/directives/bind.js",
    "chars": 278,
    "preview": "/* @flow */\n/*Github:https://github.com/answershuto*/\nexport default function bind (el: ASTElement, dir: ASTDirective) {"
  },
  {
    "path": "vue-src/compiler/directives/index.js",
    "chars": 157,
    "preview": "/* @flow */\n\nimport bind from './bind'\nimport { noop } from 'shared/util'\n/*Github:https://github.com/answershuto*/\nexpo"
  },
  {
    "path": "vue-src/compiler/directives/model.js",
    "chars": 2894,
    "preview": "/* @flow */\n\n/**\n * Cross-platform code generation for component v-model\n */\nexport function genComponentModel (\n  el: A"
  },
  {
    "path": "vue-src/compiler/error-detector.js",
    "chars": 3343,
    "preview": "/* @flow */\n\nimport { dirRE, onRE } from './parser/index'\n\n// these keywords should not appear inside expressions, but o"
  },
  {
    "path": "vue-src/compiler/helpers.js",
    "chars": 3236,
    "preview": "/* @flow */\n\nimport { parseFilters } from './parser/filter-parser'\n\n/*Vue 编译器警告*/\nexport function baseWarn (msg: string)"
  },
  {
    "path": "vue-src/compiler/index.js",
    "chars": 5360,
    "preview": "/* @flow */\n\nimport { parse } from './parser/index'\nimport { optimize } from './optimizer'\nimport { generate } from './c"
  },
  {
    "path": "vue-src/compiler/optimizer.js",
    "chars": 4258,
    "preview": "/* @flow */\n\nimport { makeMap, isBuiltInTag, cached, no } from 'shared/util'\n\n/*标记是否为静态属性*/\nlet isStaticKey\n/*标记是否是平台保留的"
  },
  {
    "path": "vue-src/compiler/parser/entity-decoder.js",
    "chars": 185,
    "preview": "/* @flow */\n\nlet decoder\n\nexport function decode (html: string): string {\n  decoder = decoder || document.createElement("
  },
  {
    "path": "vue-src/compiler/parser/filter-parser.js",
    "chars": 2764,
    "preview": "/* @flow */\n\nconst validDivisionCharRE = /[\\w).+\\-_$\\]]/\n\n/*解析过滤器*/\nexport function parseFilters (exp: string): string {"
  },
  {
    "path": "vue-src/compiler/parser/html-parser.js",
    "chars": 9022,
    "preview": "/**\n * Not type-checking this file because it's mostly vendor code.\n */\n\n/*!\n * HTML Parser By John Resig (ejohn.org)\n *"
  },
  {
    "path": "vue-src/compiler/parser/index.js",
    "chars": 20432,
    "preview": "/* @flow */\n\nimport { decode } from 'he'\nimport { parseHTML } from './html-parser'\nimport { parseText } from './text-par"
  },
  {
    "path": "vue-src/compiler/parser/text-parser.js",
    "chars": 1179,
    "preview": "/* @flow */\n\nimport { cached } from 'shared/util'\nimport { parseFilters } from './filter-parser'\n\nconst defaultTagRE = /"
  },
  {
    "path": "vue-src/core/components/index.js",
    "chars": 69,
    "preview": "import KeepAlive from './keep-alive'\n\nexport default {\n  KeepAlive\n}\n"
  },
  {
    "path": "vue-src/core/components/keep-alive.js",
    "chars": 3160,
    "preview": "/* @flow */\n\nimport { isRegExp } from 'shared/util'\nimport { getFirstComponentChild } from 'core/vdom/helpers/index'\n\nty"
  },
  {
    "path": "vue-src/core/config.js",
    "chars": 2366,
    "preview": "/* @flow */\n\nimport {\n  no,\n  noop,\n  identity\n} from 'shared/util'\n\nimport { LIFECYCLE_HOOKS } from 'shared/constants'\n"
  },
  {
    "path": "vue-src/core/global-api/assets.js",
    "chars": 1220,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { ASSET_TYPES } from 'shared/constants'\nimport { warn, isPlainObject "
  },
  {
    "path": "vue-src/core/global-api/extend.js",
    "chars": 4039,
    "preview": "/* @flow */\n\nimport { ASSET_TYPES } from 'shared/constants'\nimport { warn, extend, mergeOptions } from '../util/index'\ni"
  },
  {
    "path": "vue-src/core/global-api/index.js",
    "chars": 1541,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { initUse } from './use'\nimport { initMixin } from './mixin'\nimport {"
  },
  {
    "path": "vue-src/core/global-api/mixin.js",
    "chars": 295,
    "preview": "/* @flow */\n\nimport { mergeOptions } from '../util/index'\n\n/*初始化mixin*/\nexport function initMixin (Vue: GlobalAPI) {\n   "
  },
  {
    "path": "vue-src/core/global-api/use.js",
    "chars": 650,
    "preview": "/* @flow */\n\nimport { toArray } from '../util/index'\n\n/*初始化use*/\nexport function initUse (Vue: GlobalAPI) {\n  /*https://"
  },
  {
    "path": "vue-src/core/index.js",
    "chars": 286,
    "preview": "import Vue from './instance/index'\nimport { initGlobalAPI } from './global-api/index'\nimport { isServerRendering } from "
  },
  {
    "path": "vue-src/core/instance/events.js",
    "chars": 4083,
    "preview": "/* @flow */\n\nimport { updateListeners } from '../vdom/helpers/index'\nimport { toArray, tip, hyphenate, formatComponentNa"
  },
  {
    "path": "vue-src/core/instance/index.js",
    "chars": 597,
    "preview": "import { initMixin } from './init'\nimport { stateMixin } from './state'\nimport { renderMixin } from './render'\nimport { "
  },
  {
    "path": "vue-src/core/instance/init.js",
    "chars": 5155,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { initProxy } from './proxy'\nimport { initState } from './state'\nimpo"
  },
  {
    "path": "vue-src/core/instance/inject.js",
    "chars": 1872,
    "preview": "/* @flow */\n/*Github:https://github.com/answershuto*/\nimport { hasSymbol } from 'core/util/env'\nimport { warn } from '.."
  },
  {
    "path": "vue-src/core/instance/lifecycle.js",
    "chars": 9166,
    "preview": "/* @flow */\n/*Github:https://github.com/answershuto*/\nimport config from '../config'\nimport Watcher from '../observer/wa"
  },
  {
    "path": "vue-src/core/instance/proxy.js",
    "chars": 2270,
    "preview": "/* not type checking this file because flow doesn't play well with Proxy */\n/*Github:https://github.com/answershuto*/\nim"
  },
  {
    "path": "vue-src/core/instance/render-helpers/bind-object-props.js",
    "chars": 1134,
    "preview": "/* @flow */\n\nimport config from 'core/config'\nimport { isObject, warn, toObject } from 'core/util/index'\n\n/**\n * Runtime"
  },
  {
    "path": "vue-src/core/instance/render-helpers/check-keycodes.js",
    "chars": 464,
    "preview": "/* @flow */\n\nimport config from 'core/config'\n\n/**\n * Runtime helper for checking keyCodes from config.\n */\n /*从config配置"
  },
  {
    "path": "vue-src/core/instance/render-helpers/render-list.js",
    "chars": 800,
    "preview": "/* @flow */\n\nimport { isObject } from 'core/util/index'\n\n/**\n * Runtime helper for rendering v-for lists.\n */\n /*处理v-for"
  },
  {
    "path": "vue-src/core/instance/render-helpers/render-slot.js",
    "chars": 876,
    "preview": "/* @flow */\n\nimport { extend, warn } from 'core/util/index'\n\n/**\n * Runtime helper for rendering <slot>\n */\n /*处理slot的渲染"
  },
  {
    "path": "vue-src/core/instance/render-helpers/render-static.js",
    "chars": 1640,
    "preview": "/* @flow */\n\nimport { cloneVNode, cloneVNodes } from 'core/vdom/vnode'\n\n/**\n * Runtime helper for rendering static trees"
  },
  {
    "path": "vue-src/core/instance/render-helpers/resolve-filter.js",
    "chars": 261,
    "preview": "/* @flow */\n\nimport { identity, resolveAsset } from 'core/util/index'\n\n/**\n * Runtime helper for resolving filters\n */\n "
  },
  {
    "path": "vue-src/core/instance/render-helpers/resolve-slots.js",
    "chars": 1320,
    "preview": "/* @flow */\n\n/**\n * Runtime helper for resolving raw children VNodes into a slot object.\n */\nexport function resolveSlot"
  },
  {
    "path": "vue-src/core/instance/render.js",
    "chars": 4818,
    "preview": "/* @flow */\n\nimport {\n  warn,\n  nextTick,\n  toNumber,\n  toString,\n  looseEqual,\n  emptyObject,\n  handleError,\n  looseInd"
  },
  {
    "path": "vue-src/core/instance/state.js",
    "chars": 11169,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport Dep from '../observer/dep'\nimport Watcher from '../observer/watcher'\n"
  },
  {
    "path": "vue-src/core/observer/array.js",
    "chars": 1440,
    "preview": "/*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototyp"
  },
  {
    "path": "vue-src/core/observer/dep.js",
    "chars": 1350,
    "preview": "/* @flow */\n\nimport type Watcher from './watcher'\nimport { remove } from '../util/index'\n\nlet uid = 0\n/*Github:https://g"
  },
  {
    "path": "vue-src/core/observer/index.js",
    "chars": 8590,
    "preview": "/* @flow */\n\nimport Dep from './dep'\nimport { arrayMethods } from './array'\nimport {\n  def,\n  isObject,\n  isPlainObject,"
  },
  {
    "path": "vue-src/core/observer/scheduler.js",
    "chars": 4996,
    "preview": "/* @flow */\n\nimport type Watcher from './watcher'\nimport config from '../config'\nimport { callHook, activateChildCompone"
  },
  {
    "path": "vue-src/core/observer/watcher.js",
    "chars": 7233,
    "preview": "/* @flow */\n\nimport { queueWatcher } from './scheduler'\nimport Dep, { pushTarget, popTarget } from './dep'\n\nimport {\n  w"
  },
  {
    "path": "vue-src/core/util/debug.js",
    "chars": 2598,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { noop } from 'shared/util'\n\nexport let warn = noop\nexport let tip = "
  },
  {
    "path": "vue-src/core/util/env.js",
    "chars": 6496,
    "preview": "/* @flow */\n/* globals MutationObserver */\n\nimport { noop } from 'shared/util'\nimport { handleError } from './error'\n\n//"
  },
  {
    "path": "vue-src/core/util/error.js",
    "chars": 529,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { inBrowser } from './env'\n\nexport fun"
  },
  {
    "path": "vue-src/core/util/index.js",
    "chars": 235,
    "preview": "/* @flow */\n\nexport * from 'shared/util'\nexport * from './lang'\nexport * from './env'\nexport * from './options'\nexport *"
  },
  {
    "path": "vue-src/core/util/lang.js",
    "chars": 810,
    "preview": "/* @flow */\n\nexport const emptyObject = Object.freeze({})\n\n/**\n * Check if a string starts with $ or _\n */\nexport functi"
  },
  {
    "path": "vue-src/core/util/options.js",
    "chars": 8725,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { set } from '../observer/index'\n\nimpo"
  },
  {
    "path": "vue-src/core/util/perf.js",
    "chars": 523,
    "preview": "import { inBrowser } from './env'\n\nexport let mark\nexport let measure\n\nif (process.env.NODE_ENV !== 'production') {\n  co"
  },
  {
    "path": "vue-src/core/util/props.js",
    "chars": 5261,
    "preview": "/* @flow */\n\nimport { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'\nimport { observe, obse"
  },
  {
    "path": "vue-src/core/vdom/create-component.js",
    "chars": 7144,
    "preview": "/* @flow */\n\nimport VNode from './vnode'\nimport { resolveConstructorOptions } from 'core/instance/init'\nimport { queueAc"
  },
  {
    "path": "vue-src/core/vdom/create-element.js",
    "chars": 3940,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport VNode, { createEmptyVNode } from './vnode'\nimport { createComponent }"
  },
  {
    "path": "vue-src/core/vdom/create-functional-component.js",
    "chars": 1569,
    "preview": "/* @flow */\n\nimport VNode from './vnode'\nimport { createElement } from './create-element'\nimport { resolveInject } from "
  },
  {
    "path": "vue-src/core/vdom/helpers/extract-props.js",
    "chars": 1852,
    "preview": "/* @flow */\n\nimport {\n  tip,\n  hasOwn,\n  isDef,\n  isUndef,\n  hyphenate,\n  formatComponentName\n} from 'core/util/index'\n\n"
  },
  {
    "path": "vue-src/core/vdom/helpers/get-first-component-child.js",
    "chars": 337,
    "preview": "/* @flow */\n\nimport { isDef } from 'shared/util'\n\n/* 获取第一个子组件 */\nexport function getFirstComponentChild (children: ?Arra"
  },
  {
    "path": "vue-src/core/vdom/helpers/index.js",
    "chars": 232,
    "preview": "/* @flow */\n\nexport * from './merge-hook'\nexport * from './extract-props'\nexport * from './update-listeners'\nexport * fr"
  },
  {
    "path": "vue-src/core/vdom/helpers/merge-hook.js",
    "chars": 901,
    "preview": "/* @flow */\n\nimport { createFnInvoker } from './update-listeners'\nimport { remove, isDef, isUndef, isTrue } from 'shared"
  },
  {
    "path": "vue-src/core/vdom/helpers/normalize-children.js",
    "chars": 2629,
    "preview": "/* @flow */\n\nimport VNode, { createTextVNode } from 'core/vdom/vnode'\nimport { isDef, isUndef, isPrimitive } from 'share"
  },
  {
    "path": "vue-src/core/vdom/helpers/resolve-async-component.js",
    "chars": 2920,
    "preview": "/* @flow */\n\nimport {\n  warn,\n  once,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject\n} from 'core/util/index'\n\nfunction ensure"
  },
  {
    "path": "vue-src/core/vdom/helpers/update-listeners.js",
    "chars": 2055,
    "preview": "/* @flow */\n\nimport { warn } from 'core/util/index'\nimport { cached, isUndef } from 'shared/util'\n\nconst normalizeEvent "
  },
  {
    "path": "vue-src/core/vdom/modules/directives.js",
    "chars": 3103,
    "preview": "/* @flow */\n\nimport { emptyNode } from 'core/vdom/patch'\nimport { resolveAsset, handleError } from 'core/util/index'\nimp"
  },
  {
    "path": "vue-src/core/vdom/modules/index.js",
    "chars": 102,
    "preview": "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",
    "chars": 1190,
    "preview": "/* @flow */\n\nimport { remove } from 'shared/util'\n\nexport default {\n  create (_: any, vnode: VNodeWithData) {\n    regist"
  },
  {
    "path": "vue-src/core/vdom/patch.js",
    "chars": 26051,
    "preview": "/**\n * Virtual DOM patching algorithm based on Snabbdom by\n * Simon Friis Vindum (@paldepind)\n * Licensed under the MIT "
  },
  {
    "path": "vue-src/core/vdom/vnode.js",
    "chars": 3253,
    "preview": "/* @flow */\n\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  t"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/html.js",
    "chars": 239,
    "preview": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n/*Github:https://github.com/answershuto*/\nexport default functio"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/index.js",
    "chars": 124,
    "preview": "import model from './model'\nimport text from './text'\nimport html from './html'\n\nexport default {\n  model,\n  text,\n  htm"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/model.js",
    "chars": 5116,
    "preview": "/* @flow */\n\nimport config from 'core/config'\nimport { addHandler, addProp, getBindingAttr } from 'compiler/helpers'\nimp"
  },
  {
    "path": "vue-src/platforms/web/compiler/directives/text.js",
    "chars": 241,
    "preview": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n/*Github:https://github.com/answershuto*/\nexport default functio"
  },
  {
    "path": "vue-src/platforms/web/compiler/index.js",
    "chars": 708,
    "preview": "/* @flow */\n\nimport { isUnaryTag, canBeLeftOpenTag } from './util'\nimport { genStaticKeys } from 'shared/util'\nimport { "
  },
  {
    "path": "vue-src/platforms/web/compiler/modules/class.js",
    "chars": 1241,
    "preview": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  ba"
  },
  {
    "path": "vue-src/platforms/web/compiler/modules/index.js",
    "chars": 93,
    "preview": "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",
    "chars": 1344,
    "preview": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport { parseStyleText } from 'web/util/style'\nimp"
  },
  {
    "path": "vue-src/platforms/web/compiler/util.js",
    "chars": 986,
    "preview": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n/*Github:https://github.com/answershuto*/\nexport const isUnaryTag = m"
  },
  {
    "path": "vue-src/platforms/web/compiler.js",
    "chars": 120,
    "preview": "/* @flow */\n\nexport { parseComponent } from 'sfc/parser'\nexport { compile, compileToFunctions } from './compiler/index'\n"
  },
  {
    "path": "vue-src/platforms/web/runtime/class-util.js",
    "chars": 1282,
    "preview": "/* @flow */\n\n/**\n * Add class with compatibility for SVG since classList is not supported on\n * SVG elements in IE\n */\ne"
  },
  {
    "path": "vue-src/platforms/web/runtime/components/index.js",
    "chars": 139,
    "preview": "import Transition from './transition'\nimport TransitionGroup from './transition-group'\n\nexport default {\n  Transition,\n "
  },
  {
    "path": "vue-src/platforms/web/runtime/components/transition-group.js",
    "chars": 5543,
    "preview": "/* @flow */\n\n// Provides transition support for list items.\n// supports move transitions using the FLIP technique.\n\n// B"
  },
  {
    "path": "vue-src/platforms/web/runtime/components/transition.js",
    "chars": 5278,
    "preview": "/* @flow */\n\n// Provides transition support for a single element/component.\n// supports transition mode (out-in / in-out"
  },
  {
    "path": "vue-src/platforms/web/runtime/directives/index.js",
    "chars": 90,
    "preview": "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",
    "chars": 3688,
    "preview": "/**\n * Not type checking this file because flow doesn't like attaching\n * properties to Elements.\n */\n\nimport { looseEqu"
  },
  {
    "path": "vue-src/platforms/web/runtime/directives/show.js",
    "chars": 1703,
    "preview": "/* @flow */\n\nimport { isIE9 } from 'core/util/env'\nimport { enter, leave } from '../modules/transition'\n\n// recursively "
  },
  {
    "path": "vue-src/platforms/web/runtime/index.js",
    "chars": 2084,
    "preview": "/* @flow */\n\nimport Vue from 'core/index'\nimport config from 'core/config'\nimport { extend, noop } from 'shared/util'\nim"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/attrs.js",
    "chars": 2229,
    "preview": "/* @flow */\n\nimport { isIE9 } from 'core/util/env'\n\nimport {\n  extend,\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/class.js",
    "chars": 907,
    "preview": "/* @flow */\n\nimport {\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  concat,\n  stringifyClass,\n  genClassForVnode\n}"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/dom-props.js",
    "chars": 2470,
    "preview": "/* @flow */\n\nimport { isDef, isUndef, extend, toNumber } from 'shared/util'\n\nfunction updateDOMProps (oldVnode: VNodeWit"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/events.js",
    "chars": 2250,
    "preview": "/* @flow */\n\nimport { isDef, isUndef } from 'shared/util'\nimport { updateListeners } from 'core/vdom/helpers/index'\nimpo"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/index.js",
    "chars": 269,
    "preview": "import attrs from './attrs'\nimport klass from './class'\nimport events from './events'\nimport domProps from './dom-props'"
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/style.js",
    "chars": 2686,
    "preview": "/* @flow */\n\nimport { getStyle, normalizeStyleBinding } from 'web/util/style'\nimport { cached, camelize, extend, isDef, "
  },
  {
    "path": "vue-src/platforms/web/runtime/modules/transition.js",
    "chars": 8130,
    "preview": "/* @flow */\n\nimport { inBrowser, isIE9, warn } from 'core/util/index'\nimport { mergeVNodeHook } from 'core/vdom/helpers/"
  },
  {
    "path": "vue-src/platforms/web/runtime/node-ops.js",
    "chars": 1610,
    "preview": "/* @flow */\n\n/*这个文件是根据平台(web或者weex)操作真实节点(如web上的Dom)的操作函数进行了一层适配,对外可以统一提供操作真实节点的接口,内部实现根据平台的变化而变化*/\n\nimport { namespaceM"
  },
  {
    "path": "vue-src/platforms/web/runtime/patch.js",
    "chars": 445,
    "preview": "/* @flow */\n\nimport * as nodeOps from 'web/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimpor"
  },
  {
    "path": "vue-src/platforms/web/runtime/transition-util.js",
    "chars": 4940,
    "preview": "/* @flow */\n\nimport { inBrowser, isIE9 } from 'core/util/index'\nimport { addClass, removeClass } from './class-util'\nimp"
  },
  {
    "path": "vue-src/platforms/web/runtime-with-compiler.js",
    "chars": 3265,
    "preview": "/* @flow */\n\nimport config from 'core/config'\nimport { warn, cached } from 'core/util/index'\nimport { mark, measure } fr"
  },
  {
    "path": "vue-src/platforms/web/runtime.js",
    "chars": 108,
    "preview": "/* @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",
    "chars": 53,
    "preview": "import show from './show'\n\nexport default {\n  show\n}\n"
  },
  {
    "path": "vue-src/platforms/web/server/directives/show.js",
    "chars": 246,
    "preview": "/* @flow */\n/*Github:https://github.com/answershuto*/\nexport default function show (node: VNodeWithData, dir: VNodeDirec"
  },
  {
    "path": "vue-src/platforms/web/server/modules/attrs.js",
    "chars": 1152,
    "preview": "/* @flow */\n\nimport { escape } from 'he'\n\nimport {\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  isBooleanAttr,\n  "
  },
  {
    "path": "vue-src/platforms/web/server/modules/class.js",
    "chars": 279,
    "preview": "/* @flow */\n\nimport { escape } from 'he'\nimport { genClassForVnode } from 'web/util/index'\n\nexport default function rend"
  },
  {
    "path": "vue-src/platforms/web/server/modules/dom-props.js",
    "chars": 1209,
    "preview": "/* @flow */\n\nimport VNode from 'core/vdom/vnode'\nimport { renderAttr } from './attrs'\nimport { isDef, isUndef } from 'sh"
  },
  {
    "path": "vue-src/platforms/web/server/modules/index.js",
    "chars": 177,
    "preview": "import attrs from './attrs'\nimport domProps from './dom-props'\nimport klass from './class'\nimport style from './style'\n\n"
  },
  {
    "path": "vue-src/platforms/web/server/modules/style.js",
    "chars": 773,
    "preview": "/* @flow */\n\nimport { escape } from 'he'\nimport { hyphenate } from 'shared/util'\nimport { getStyle } from 'web/util/styl"
  },
  {
    "path": "vue-src/platforms/web/server/util.js",
    "chars": 1400,
    "preview": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\nconst isAttr = makeMap(\n  'accept,accept-charset,accesskey,action,al"
  },
  {
    "path": "vue-src/platforms/web/server-renderer.js",
    "chars": 863,
    "preview": "/* @flow */\n\nprocess.env.VUE_ENV = 'server'\n\nimport modules from './server/modules/index'\nimport baseDirectives from './"
  },
  {
    "path": "vue-src/platforms/web/util/attrs.js",
    "chars": 1564,
    "preview": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\n// these are reserved for web because they are directly compiled awa"
  },
  {
    "path": "vue-src/platforms/web/util/class.js",
    "chars": 1872,
    "preview": "/* @flow */\n\nimport { isDef, isUndef, isObject } from 'shared/util'\n\nexport function genClassForVnode (vnode: VNode): st"
  },
  {
    "path": "vue-src/platforms/web/util/compat.js",
    "chars": 497,
    "preview": "/* @flow */\n\nimport { inBrowser } from 'core/util/index'\n\n// check whether current browser encodes a char inside attribu"
  },
  {
    "path": "vue-src/platforms/web/util/element.js",
    "chars": 2423,
    "preview": "/* @flow */\n\nimport { inBrowser } from 'core/util/env'\nimport { makeMap } from 'shared/util'\n\nexport const namespaceMap "
  },
  {
    "path": "vue-src/platforms/web/util/index.js",
    "chars": 572,
    "preview": "/* @flow */\n\nimport { warn } from 'core/util/index'\n\nexport * from './attrs'\nexport * from './class'\nexport * from './el"
  },
  {
    "path": "vue-src/platforms/web/util/style.js",
    "chars": 1895,
    "preview": "/* @flow */\n\nimport { cached, extend, toObject } from 'shared/util'\n\nexport const parseStyleText = cached(function (cssT"
  },
  {
    "path": "vue-src/platforms/weex/compiler/directives/index.js",
    "chars": 56,
    "preview": "import model from './model'\n\nexport default {\n  model\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/compiler/directives/model.js",
    "chars": 902,
    "preview": "/* @flow */\n\nimport { addHandler, addAttr } from 'compiler/helpers'\nimport { genComponentModel, genAssignmentCode } from"
  },
  {
    "path": "vue-src/platforms/weex/compiler/index.js",
    "chars": 638,
    "preview": "/* @flow */\n\nimport { genStaticKeys } from 'shared/util'\nimport { createCompiler } from 'compiler/index'\n\nimport modules"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/append.js",
    "chars": 512,
    "preview": "/* @flow */\n\nfunction preTransformNode (el: ASTElement, options: CompilerOptions) {\n  if (el.tag === 'cell' && !el.attrs"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/class.js",
    "chars": 1923,
    "preview": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  ba"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/index.js",
    "chars": 170,
    "preview": "import klass from './class'\nimport style from './style'\nimport props from './props'\nimport append from './append'\n\nexpor"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/props.js",
    "chars": 793,
    "preview": "/* @flow */\n\nimport { cached, camelize } from 'shared/util'\n\nconst normalize = cached(camelize)\n\nfunction normalizeKeyNa"
  },
  {
    "path": "vue-src/platforms/weex/compiler/modules/style.js",
    "chars": 2249,
    "preview": "/* @flow */\n\nimport { cached, camelize } from 'shared/util'\nimport { parseText } from 'compiler/parser/text-parser'\nimpo"
  },
  {
    "path": "vue-src/platforms/weex/compiler.js",
    "chars": 46,
    "preview": "export { compile } from 'weex/compiler/index'\n"
  },
  {
    "path": "vue-src/platforms/weex/framework.js",
    "chars": 11916,
    "preview": "import TextNode from 'weex/runtime/text-node'\n\n// this will be preserved during build\nconst VueFactory = require('./fact"
  },
  {
    "path": "vue-src/platforms/weex/runtime/components/index.js",
    "chars": 139,
    "preview": "import Transition from './transition'\nimport TransitionGroup from './transition-group'\n\nexport default {\n  Transition,\n "
  },
  {
    "path": "vue-src/platforms/weex/runtime/components/transition-group.js",
    "chars": 3965,
    "preview": "import { warn, extend } from 'core/util/index'\nimport { transitionProps, extractTransitionData } from './transition'\n\nco"
  },
  {
    "path": "vue-src/platforms/weex/runtime/components/transition.js",
    "chars": 232,
    "preview": "// reuse same transition component logic from web\nexport {\n  transitionProps,\n  extractTransitionData\n} from 'web/runtim"
  },
  {
    "path": "vue-src/platforms/weex/runtime/directives/index.js",
    "chars": 19,
    "preview": "export default {\n}\n"
  },
  {
    "path": "vue-src/platforms/weex/runtime/index.js",
    "chars": 945,
    "preview": "/* @flow */\n\nimport Vue from 'core/index'\nimport { patch } from 'weex/runtime/patch'\nimport { mountComponent } from 'cor"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/attrs.js",
    "chars": 744,
    "preview": "/* @flow */\n\nimport { extend } from 'shared/util'\n\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) "
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/class.js",
    "chars": 1687,
    "preview": "/* @flow */\n\nimport { extend } from 'shared/util'\n\nfunction updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) "
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/events.js",
    "chars": 1165,
    "preview": "/* @flow */\n\nimport { updateListeners } from 'core/vdom/helpers/update-listeners'\n\nlet target: any\n\nfunction add (\n  eve"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/index.js",
    "chars": 222,
    "preview": "import attrs from './attrs'\nimport klass from './class'\nimport events from './events'\nimport style from './style'\nimport"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/style.js",
    "chars": 1483,
    "preview": "/* @flow */\n\nimport { extend, cached, camelize } from 'shared/util'\n\nconst normalize = cached(camelize)\n\nfunction create"
  },
  {
    "path": "vue-src/platforms/weex/runtime/modules/transition.js",
    "chars": 7394,
    "preview": "import { warn } from 'core/util/debug'\nimport { extend, once, noop } from 'shared/util'\nimport { activeInstance } from '"
  },
  {
    "path": "vue-src/platforms/weex/runtime/node-ops.js",
    "chars": 1779,
    "preview": "/* globals renderer */\n// renderer is injected by weex factory wrapper\n\n/*这个文件是根据平台(web或者weex)操作真实节点(如web上的Dom)的操作函数进行了一"
  },
  {
    "path": "vue-src/platforms/weex/runtime/patch.js",
    "chars": 478,
    "preview": "/* @flow */\n\nimport * as nodeOps from 'weex/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimpo"
  },
  {
    "path": "vue-src/platforms/weex/runtime/text-node.js",
    "chars": 184,
    "preview": "let latestNodeId = 1\n\nexport default function TextNode (text) {\n  this.instanceId = ''\n  this.nodeId = latestNodeId++\n  "
  },
  {
    "path": "vue-src/platforms/weex/runtime-factory.js",
    "chars": 178,
    "preview": "// this entry is built and wrapped with a factory function\n// used to generate a fresh copy of Vue for every Weex instan"
  },
  {
    "path": "vue-src/platforms/weex/util/index.js",
    "chars": 1354,
    "preview": "/* globals renderer */\n\nimport { makeMap } from 'shared/util'\n\n/*判断是否是保留的标签*/\nexport const isReservedTag = makeMap(\n  't"
  },
  {
    "path": "vue-src/server/bundle-renderer/create-bundle-renderer.js",
    "chars": 4039,
    "preview": "/* @flow */\n\nimport { createBundleRunner } from './create-bundle-runner'\nimport type { Renderer, RenderOptions } from '."
  },
  {
    "path": "vue-src/server/bundle-renderer/create-bundle-runner.js",
    "chars": 3998,
    "preview": "import { isPlainObject } from 'shared/util'\n\nconst vm = require('vm')\nconst path = require('path')\nconst resolve = requi"
  },
  {
    "path": "vue-src/server/bundle-renderer/source-map-support.js",
    "chars": 1238,
    "preview": "/* @flow */\n\nconst SourceMapConsumer = require('source-map').SourceMapConsumer\n\nconst filenameRE = /\\(([^)]+\\.js):(\\d+):"
  },
  {
    "path": "vue-src/server/create-renderer.js",
    "chars": 2621,
    "preview": "/* @flow */\n\nimport RenderStream from './render-stream'\nimport TemplateRenderer from './template-renderer/index'\nimport "
  },
  {
    "path": "vue-src/server/render-context.js",
    "chars": 3318,
    "preview": "/* @flow */\n\nimport { isUndef } from 'shared/util'\n\ntype RenderState = {\n  type: 'Element';\n  rendered: number;\n  total:"
  },
  {
    "path": "vue-src/server/render-stream.js",
    "chars": 2132,
    "preview": "/* @flow */\n\n/**\n * Original RenderStream implementation by Sasha Aickin (@aickin)\n * Licensed under the Apache License,"
  },
  {
    "path": "vue-src/server/render.js",
    "chars": 7999,
    "preview": "/* @flow */\n\nconst { escape } = require('he')\n\nimport { SSR_ATTR } from 'shared/constants'\nimport { RenderContext } from"
  },
  {
    "path": "vue-src/server/template-renderer/create-async-file-mapper.js",
    "chars": 1469,
    "preview": "/* @flow */\n\n/**\n * Creates a mapper that maps components used during a server-side render\n * to async chunk files in th"
  },
  {
    "path": "vue-src/server/template-renderer/index.js",
    "chars": 7552,
    "preview": "/* @flow */\n\nconst path = require('path')\nconst serialize = require('serialize-javascript')\n\nimport { isJS, isCSS } from"
  },
  {
    "path": "vue-src/server/template-renderer/parse-template.js",
    "chars": 951,
    "preview": "/* @flow */\n\nconst compile = require('lodash.template')\nconst compileOptions = {\n  escape: /{{[^{]([\\s\\S]+?)[^}]}}/g,\n  "
  },
  {
    "path": "vue-src/server/template-renderer/template-stream.js",
    "chars": 1929,
    "preview": "/* @flow */\n\nconst Transform = require('stream').Transform\nimport type TemplateRenderer from './index'\nimport type { Par"
  },
  {
    "path": "vue-src/server/util.js",
    "chars": 168,
    "preview": "/* @flow */\n\nexport const isJS = (file: string): boolean => /\\.js(\\?[^.]+)?$/.test(file)\n\nexport const isCSS = (file: st"
  },
  {
    "path": "vue-src/server/webpack-plugin/client.js",
    "chars": 2215,
    "preview": "const hash = require('hash-sum')\nconst uniq = require('lodash.uniq')\nimport { isJS } from './util'\n\nexport default class"
  },
  {
    "path": "vue-src/server/webpack-plugin/server.js",
    "chars": 1775,
    "preview": "import { validate, isJS } from './util'\n\nexport default class VueSSRServerPlugin {\n  constructor (options = {}) {\n    th"
  },
  {
    "path": "vue-src/server/webpack-plugin/util.js",
    "chars": 769,
    "preview": "const { red, yellow } = require('chalk')\n\nconst prefix = `[vue-server-renderer-webpack-plugin]`\nconst warn = exports.war"
  },
  {
    "path": "vue-src/server/write.js",
    "chars": 789,
    "preview": "/* @flow */\n\nconst MAX_STACK_DEPTH = 1000\n\nexport function createWriteFunction (\n  write: (text: string, next: Function)"
  },
  {
    "path": "vue-src/sfc/parser.js",
    "chars": 2880,
    "preview": "/* @flow */\n\nimport deindent from 'de-indent'\nimport { parseHTML } from 'compiler/parser/html-parser'\nimport { makeMap }"
  },
  {
    "path": "vue-src/shared/constants.js",
    "chars": 353,
    "preview": "/*用来标记是否是服务端渲染*/\nexport const SSR_ATTR = 'data-server-rendered'\n\n/*选项/资源集合*/\nexport const ASSET_TYPES = [\n  'component',"
  },
  {
    "path": "vue-src/shared/util.js",
    "chars": 6422,
    "preview": "/* @flow */\n\n// these helpers produces better vm code in JS engines due to their\n// explicitness and function inlining\ne"
  },
  {
    "path": "vuex-src/helpers.js",
    "chars": 4352,
    "preview": "/* https://vuex.vuejs.org/zh-cn/state.html#mapstate-辅助函数 */\nexport const mapState = normalizeNamespace((namespace, state"
  },
  {
    "path": "vuex-src/index.esm.js",
    "chars": 403,
    "preview": "import { Store, install } from './store'\nimport { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelper"
  },
  {
    "path": "vuex-src/index.js",
    "chars": 289,
    "preview": "import { Store, install } from './store'\nimport { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelper"
  },
  {
    "path": "vuex-src/mixin.js",
    "chars": 1155,
    "preview": "export default function (Vue) {\n  /*获取Vue版本,鉴别Vue1.0还是Vue2.0*/\n  const version = Number(Vue.version.split('.')[0])\n\n  if"
  },
  {
    "path": "vuex-src/module/module-collection.js",
    "chars": 3180,
    "preview": "import Module from './module'\nimport { assert, forEachValue } from '../util'\n\n/*module收集类*/\nexport default class ModuleC"
  },
  {
    "path": "vuex-src/module/module.js",
    "chars": 1570,
    "preview": "import { forEachValue } from '../util'\n\n/*Module构造类*/\nexport default class Module {\n  constructor (rawModule, runtime) {"
  },
  {
    "path": "vuex-src/plugins/devtool.js",
    "chars": 672,
    "preview": "/* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件 */\nconst devtoolHook =\n  typeof window !== 'undefined' &&\n  window"
  },
  {
    "path": "vuex-src/plugins/logger.js",
    "chars": 1732,
    "preview": "// Credits: borrowed code from fcomb/redux-logger\n\nimport { deepCopy } from '../util'\n\nexport default function createLog"
  },
  {
    "path": "vuex-src/store.js",
    "chars": 15909,
    "preview": "import applyMixin from './mixin'\nimport devtoolPlugin from './plugins/devtool'\nimport ModuleCollection from './module/mo"
  }
]

// ... and 13 more files (download for full content)

About this extraction

This page contains the full source code of the answershuto/learnVue GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 213 files (619.7 KB), approximately 188.1k tokens, and a symbol index with 445 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!