Full Code of ustbhuangyi/vue-analysis for AI

master 813cfe1a8fe9 cached
569 files
2.0 MB
549.9k tokens
1141 symbols
1 requests
Download .txt
Showing preview only (2,187K chars total). Download the full file or copy to clipboard to get everything.
Repository: ustbhuangyi/vue-analysis
Branch: master
Commit: 813cfe1a8fe9
Files: 569
Total size: 2.0 MB

Directory structure:
gitextract_7kf9tf8l/

├── .flowconfig
├── .gitignore
├── LICENSE
├── README.md
├── build.sh
├── docs/
│   ├── .vuepress/
│   │   ├── config.js
│   │   └── public/
│   │       └── manifest.json
│   ├── README.md
│   ├── v2/
│   │   ├── compile/
│   │   │   ├── codegen.md
│   │   │   ├── entrance.md
│   │   │   ├── index.md
│   │   │   ├── optimize.md
│   │   │   └── parse.md
│   │   ├── components/
│   │   │   ├── async-component.md
│   │   │   ├── component-register.md
│   │   │   ├── create-component.md
│   │   │   ├── index.md
│   │   │   ├── lifecycle.md
│   │   │   ├── merge-option.md
│   │   │   └── patch.md
│   │   ├── data-driven/
│   │   │   ├── create-element.md
│   │   │   ├── index.md
│   │   │   ├── mounted.md
│   │   │   ├── new-vue.md
│   │   │   ├── render.md
│   │   │   ├── update.md
│   │   │   └── virtual-dom.md
│   │   ├── extend/
│   │   │   ├── event.md
│   │   │   ├── index.md
│   │   │   ├── keep-alive.md
│   │   │   ├── slot.md
│   │   │   ├── tansition-group.md
│   │   │   ├── tansition.md
│   │   │   └── v-model.md
│   │   ├── prepare/
│   │   │   ├── build.md
│   │   │   ├── directory.md
│   │   │   ├── entrance.md
│   │   │   ├── flow.md
│   │   │   └── index.md
│   │   ├── reactive/
│   │   │   ├── component-update.md
│   │   │   ├── computed-watcher.md
│   │   │   ├── getters.md
│   │   │   ├── index.md
│   │   │   ├── next-tick.md
│   │   │   ├── props.md
│   │   │   ├── questions.md
│   │   │   ├── reactive-object.md
│   │   │   ├── setters.md
│   │   │   └── summary.md
│   │   ├── vue-router/
│   │   │   ├── index.md
│   │   │   ├── install.md
│   │   │   ├── matcher.md
│   │   │   ├── router.md
│   │   │   └── transition-to.md
│   │   └── vuex/
│   │       ├── api.md
│   │       ├── index.md
│   │       ├── init.md
│   │       └── plugin.md
│   └── v3/
│       ├── guide/
│       │   └── index.md
│       └── new/
│           └── index.md
├── package.json
├── vue/
│   ├── flow/
│   │   ├── compiler.js
│   │   ├── component.js
│   │   ├── global-api.js
│   │   ├── modules.js
│   │   ├── options.js
│   │   ├── ssr.js
│   │   ├── vnode.js
│   │   └── weex.js
│   ├── package.json
│   ├── scripts/
│   │   ├── alias.js
│   │   ├── build.js
│   │   ├── config.js
│   │   ├── gen-release-note.js
│   │   ├── get-weex-version.js
│   │   ├── git-hooks/
│   │   │   ├── commit-msg
│   │   │   └── pre-commit
│   │   ├── release-weex.sh
│   │   ├── release.sh
│   │   └── verify-commit-msg.js
│   ├── src/
│   │   ├── compiler/
│   │   │   ├── codegen/
│   │   │   │   ├── events.js
│   │   │   │   └── index.js
│   │   │   ├── create-compiler.js
│   │   │   ├── directives/
│   │   │   │   ├── bind.js
│   │   │   │   ├── index.js
│   │   │   │   ├── model.js
│   │   │   │   └── on.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
│   │   │   └── to-function.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-listeners.js
│   │   │   │   │   ├── bind-object-props.js
│   │   │   │   │   ├── check-keycodes.js
│   │   │   │   │   ├── index.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
│   │   │   │   ├── traverse.js
│   │   │   │   └── watcher.js
│   │   │   ├── util/
│   │   │   │   ├── debug.js
│   │   │   │   ├── env.js
│   │   │   │   ├── error.js
│   │   │   │   ├── index.js
│   │   │   │   ├── lang.js
│   │   │   │   ├── next-tick.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
│   │   │       │   ├── is-async-placeholder.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
│   │   │   │   │   │   ├── model.js
│   │   │   │   │   │   └── style.js
│   │   │   │   │   ├── options.js
│   │   │   │   │   └── util.js
│   │   │   │   ├── entry-compiler.js
│   │   │   │   ├── entry-runtime-with-compiler.js
│   │   │   │   ├── entry-runtime.js
│   │   │   │   ├── entry-server-basic-renderer.js
│   │   │   │   ├── entry-server-renderer.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
│   │   │   │   ├── server/
│   │   │   │   │   ├── compiler.js
│   │   │   │   │   ├── directives/
│   │   │   │   │   │   ├── index.js
│   │   │   │   │   │   ├── model.js
│   │   │   │   │   │   └── show.js
│   │   │   │   │   ├── modules/
│   │   │   │   │   │   ├── attrs.js
│   │   │   │   │   │   ├── class.js
│   │   │   │   │   │   ├── dom-props.js
│   │   │   │   │   │   ├── index.js
│   │   │   │   │   │   └── style.js
│   │   │   │   │   └── util.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
│   │   │       │       ├── recycle-list/
│   │   │       │       │   ├── component-root.js
│   │   │       │       │   ├── component.js
│   │   │       │       │   ├── index.js
│   │   │       │       │   ├── recycle-list.js
│   │   │       │       │   ├── text.js
│   │   │       │       │   ├── v-bind.js
│   │   │       │       │   ├── v-for.js
│   │   │       │       │   ├── v-if.js
│   │   │       │       │   ├── v-on.js
│   │   │       │       │   └── v-once.js
│   │   │       │       └── style.js
│   │   │       ├── entry-compiler.js
│   │   │       ├── entry-framework.js
│   │   │       ├── entry-runtime-factory.js
│   │   │       ├── runtime/
│   │   │       │   ├── components/
│   │   │       │   │   ├── index.js
│   │   │       │   │   ├── richtext.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
│   │   │       │   ├── recycle-list/
│   │   │       │   │   ├── render-component-template.js
│   │   │       │   │   └── virtual-component.js
│   │   │       │   └── text-node.js
│   │   │       └── util/
│   │   │           ├── element.js
│   │   │           ├── index.js
│   │   │           └── parser.js
│   │   ├── server/
│   │   │   ├── bundle-renderer/
│   │   │   │   ├── create-bundle-renderer.js
│   │   │   │   ├── create-bundle-runner.js
│   │   │   │   └── source-map-support.js
│   │   │   ├── create-basic-renderer.js
│   │   │   ├── create-renderer.js
│   │   │   ├── optimizing-compiler/
│   │   │   │   ├── codegen.js
│   │   │   │   ├── index.js
│   │   │   │   ├── modules.js
│   │   │   │   ├── optimizer.js
│   │   │   │   └── runtime-helpers.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
│   └── test/
│       ├── e2e/
│       │   ├── .eslintrc
│       │   ├── nightwatch.config.js
│       │   ├── runner.js
│       │   └── specs/
│       │       ├── async-edge-cases.html
│       │       ├── async-edge-cases.js
│       │       ├── basic-ssr.html
│       │       ├── basic-ssr.js
│       │       ├── commits.js
│       │       ├── grid.js
│       │       ├── markdown.js
│       │       ├── modal.js
│       │       ├── select2.js
│       │       ├── svg.js
│       │       ├── todomvc.js
│       │       └── tree.js
│       ├── helpers/
│       │   ├── .eslintrc
│       │   ├── classlist.js
│       │   ├── test-object-option.js
│       │   ├── to-equal.js
│       │   ├── to-have-been-warned.js
│       │   ├── trigger-event.js
│       │   ├── vdom.js
│       │   └── wait-for-update.js
│       ├── ssr/
│       │   ├── .eslintrc
│       │   ├── async-loader.js
│       │   ├── compile-with-webpack.js
│       │   ├── fixtures/
│       │   │   ├── app.js
│       │   │   ├── async-bar.js
│       │   │   ├── async-foo.js
│       │   │   ├── cache.js
│       │   │   ├── error.js
│       │   │   ├── nested-cache.js
│       │   │   ├── promise-rejection.js
│       │   │   ├── split.js
│       │   │   └── test.css
│       │   ├── jasmine.json
│       │   ├── ssr-basic-renderer.spec.js
│       │   ├── ssr-bundle-render.spec.js
│       │   ├── ssr-stream.spec.js
│       │   ├── ssr-string.spec.js
│       │   └── ssr-template.spec.js
│       ├── unit/
│       │   ├── .eslintrc
│       │   ├── features/
│       │   │   ├── component/
│       │   │   │   ├── component-async.spec.js
│       │   │   │   ├── component-keep-alive.spec.js
│       │   │   │   ├── component-scoped-slot.spec.js
│       │   │   │   ├── component-slot.spec.js
│       │   │   │   └── component.spec.js
│       │   │   ├── debug.spec.js
│       │   │   ├── directives/
│       │   │   │   ├── bind.spec.js
│       │   │   │   ├── class.spec.js
│       │   │   │   ├── cloak.spec.js
│       │   │   │   ├── for.spec.js
│       │   │   │   ├── html.spec.js
│       │   │   │   ├── if.spec.js
│       │   │   │   ├── model-checkbox.spec.js
│       │   │   │   ├── model-component.spec.js
│       │   │   │   ├── model-dynamic.spec.js
│       │   │   │   ├── model-file.spec.js
│       │   │   │   ├── model-parse.spec.js
│       │   │   │   ├── model-radio.spec.js
│       │   │   │   ├── model-select.spec.js
│       │   │   │   ├── model-text.spec.js
│       │   │   │   ├── on.spec.js
│       │   │   │   ├── once.spec.js
│       │   │   │   ├── pre.spec.js
│       │   │   │   ├── show.spec.js
│       │   │   │   ├── static-style-parser.spec.js
│       │   │   │   ├── style.spec.js
│       │   │   │   └── text.spec.js
│       │   │   ├── error-handling.spec.js
│       │   │   ├── filter/
│       │   │   │   └── filter.spec.js
│       │   │   ├── global-api/
│       │   │   │   ├── assets.spec.js
│       │   │   │   ├── compile.spec.js
│       │   │   │   ├── config.spec.js
│       │   │   │   ├── extend.spec.js
│       │   │   │   ├── mixin.spec.js
│       │   │   │   ├── set-delete.spec.js
│       │   │   │   └── use.spec.js
│       │   │   ├── instance/
│       │   │   │   ├── init.spec.js
│       │   │   │   ├── methods-data.spec.js
│       │   │   │   ├── methods-events.spec.js
│       │   │   │   ├── methods-lifecycle.spec.js
│       │   │   │   ├── properties.spec.js
│       │   │   │   └── render-proxy.spec.js
│       │   │   ├── options/
│       │   │   │   ├── _scopeId.spec.js
│       │   │   │   ├── comments.spec.js
│       │   │   │   ├── components.spec.js
│       │   │   │   ├── computed.spec.js
│       │   │   │   ├── data.spec.js
│       │   │   │   ├── delimiters.spec.js
│       │   │   │   ├── directives.spec.js
│       │   │   │   ├── el.spec.js
│       │   │   │   ├── errorCaptured.spec.js
│       │   │   │   ├── extends.spec.js
│       │   │   │   ├── functional.spec.js
│       │   │   │   ├── inheritAttrs.spec.js
│       │   │   │   ├── inject.spec.js
│       │   │   │   ├── lifecycle.spec.js
│       │   │   │   ├── methods.spec.js
│       │   │   │   ├── mixins.spec.js
│       │   │   │   ├── name.spec.js
│       │   │   │   ├── parent.spec.js
│       │   │   │   ├── props.spec.js
│       │   │   │   ├── propsData.spec.js
│       │   │   │   ├── render.spec.js
│       │   │   │   ├── renderError.spec.js
│       │   │   │   ├── template.spec.js
│       │   │   │   └── watch.spec.js
│       │   │   ├── ref.spec.js
│       │   │   └── transition/
│       │   │       ├── inject-styles.js
│       │   │       ├── transition-group.spec.js
│       │   │       ├── transition-mode.spec.js
│       │   │       └── transition.spec.js
│       │   ├── index.js
│       │   ├── karma.base.config.js
│       │   ├── karma.cover.config.js
│       │   ├── karma.dev.config.js
│       │   ├── karma.sauce.config.js
│       │   ├── karma.unit.config.js
│       │   └── modules/
│       │       ├── compiler/
│       │       │   ├── codegen.spec.js
│       │       │   ├── compiler-options.spec.js
│       │       │   ├── optimizer.spec.js
│       │       │   └── parser.spec.js
│       │       ├── observer/
│       │       │   ├── dep.spec.js
│       │       │   ├── observer.spec.js
│       │       │   ├── scheduler.spec.js
│       │       │   └── watcher.spec.js
│       │       ├── server-compiler/
│       │       │   └── optimizer.spec.js
│       │       ├── sfc/
│       │       │   └── sfc-parser.spec.js
│       │       ├── util/
│       │       │   └── next-tick.spec.js
│       │       └── vdom/
│       │           ├── create-component.spec.js
│       │           ├── create-element.spec.js
│       │           ├── modules/
│       │           │   ├── attrs.spec.js
│       │           │   ├── class.spec.js
│       │           │   ├── directive.spec.js
│       │           │   ├── dom-props.spec.js
│       │           │   ├── events.spec.js
│       │           │   └── style.spec.js
│       │           └── patch/
│       │               ├── children.spec.js
│       │               ├── edge-cases.spec.js
│       │               ├── element.spec.js
│       │               ├── hooks.spec.js
│       │               └── hydration.spec.js
│       └── weex/
│           ├── .eslintrc
│           ├── cases/
│           │   ├── cases.spec.js
│           │   ├── event/
│           │   │   ├── click.after.vdom.js
│           │   │   ├── click.before.vdom.js
│           │   │   └── click.vue
│           │   ├── recycle-list/
│           │   │   ├── attrs.vdom.js
│           │   │   ├── attrs.vue
│           │   │   ├── classname.vdom.js
│           │   │   ├── classname.vue
│           │   │   ├── components/
│           │   │   │   ├── banner.vue
│           │   │   │   ├── counter.vue
│           │   │   │   ├── editor.vue
│           │   │   │   ├── footer.vue
│           │   │   │   ├── lifecycle.vue
│           │   │   │   ├── poster.vue
│           │   │   │   ├── stateful-lifecycle.vdom.js
│           │   │   │   ├── stateful-lifecycle.vue
│           │   │   │   ├── stateful-v-model.vdom.js
│           │   │   │   ├── stateful-v-model.vue
│           │   │   │   ├── stateful.vdom.js
│           │   │   │   ├── stateful.vue
│           │   │   │   ├── stateless-multi-components.vdom.js
│           │   │   │   ├── stateless-multi-components.vue
│           │   │   │   ├── stateless-with-props.vdom.js
│           │   │   │   ├── stateless-with-props.vue
│           │   │   │   ├── stateless.vdom.js
│           │   │   │   └── stateless.vue
│           │   │   ├── inline-style.vdom.js
│           │   │   ├── inline-style.vue
│           │   │   ├── text-node.vdom.js
│           │   │   ├── text-node.vue
│           │   │   ├── v-else-if.vdom.js
│           │   │   ├── v-else-if.vue
│           │   │   ├── v-else.vdom.js
│           │   │   ├── v-else.vue
│           │   │   ├── v-for-iterator.vdom.js
│           │   │   ├── v-for-iterator.vue
│           │   │   ├── v-for.vdom.js
│           │   │   ├── v-for.vue
│           │   │   ├── v-if.vdom.js
│           │   │   ├── v-if.vue
│           │   │   ├── v-on-inline.vdom.js
│           │   │   ├── v-on-inline.vue
│           │   │   ├── v-on.vdom.js
│           │   │   ├── v-on.vue
│           │   │   ├── v-once.vdom.js
│           │   │   └── v-once.vue
│           │   └── render/
│           │       ├── sample.vdom.js
│           │       └── sample.vue
│           ├── compiler/
│           │   ├── append.spec.js
│           │   ├── class.spec.js
│           │   ├── compile.spec.js
│           │   ├── parser.spec.js
│           │   ├── props.spec.js
│           │   ├── style.spec.js
│           │   └── v-model.spec.js
│           ├── helpers/
│           │   └── index.js
│           ├── jasmine.json
│           └── runtime/
│               ├── attrs.spec.js
│               ├── class.spec.js
│               ├── components/
│               │   └── richtext.spec.js
│               ├── events.spec.js
│               ├── framework.spec.js
│               ├── node.spec.js
│               └── style.spec.js
├── vue-router/
│   ├── build/
│   │   ├── build.js
│   │   ├── configs.js
│   │   ├── release.sh
│   │   ├── rollup.dev.config.js
│   │   └── update-docs.sh
│   ├── flow/
│   │   └── declarations.js
│   ├── 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
│   └── test/
│       ├── e2e/
│       │   ├── nightwatch.config.js
│       │   ├── runner.js
│       │   └── specs/
│       │       ├── active-links.js
│       │       ├── auth-flow.js
│       │       ├── basic.js
│       │       ├── data-fetching.js
│       │       ├── hash-mode.js
│       │       ├── hash-scroll-behavior.js
│       │       ├── lazy-loading.js
│       │       ├── named-routes.js
│       │       ├── named-views.js
│       │       ├── navigation-guards.js
│       │       ├── nested-router.js
│       │       ├── nested-routes.js
│       │       ├── redirect.js
│       │       ├── route-alias.js
│       │       ├── route-matching.js
│       │       ├── route-props.js
│       │       ├── scroll-behavior.js
│       │       └── transitions.js
│       └── unit/
│           ├── jasmine.json
│           └── specs/
│               ├── api.spec.js
│               ├── async.spec.js
│               ├── create-map.spec.js
│               ├── create-matcher.spec.js
│               ├── custom-query.spec.js
│               ├── discrete-components.spec.js
│               ├── error-handling.spec.js
│               ├── location.spec.js
│               ├── node.spec.js
│               ├── path.spec.js
│               ├── query.spec.js
│               └── route.spec.js
└── vuex/
    ├── build/
    │   ├── build.main.js
    │   ├── configs.js
    │   ├── release.sh
    │   ├── rollup.dev.config.js
    │   └── rollup.logger.config.js
    ├── 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
    └── test/
        ├── e2e/
        │   ├── nightwatch.config.js
        │   ├── runner.js
        │   └── specs/
        │       ├── cart.js
        │       ├── chat.js
        │       ├── counter.js
        │       └── todomvc.js
        └── unit/
            ├── .eslintrc
            ├── helpers.spec.js
            ├── hot-reload.spec.js
            ├── jasmine.json
            ├── module/
            │   ├── module-collection.spec.js
            │   └── module.spec.js
            ├── modules.spec.js
            ├── setup.js
            ├── store.spec.js
            └── util.spec.js

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

================================================
FILE: .flowconfig
================================================
[ignore]
.*/node_modules/.*
.*/test/.*
.*/build/.*
.*/examples/.*
.*/benchmarks/.*

[include]

[libs]
flow

[options]
unsafe.enable_getters_and_setters=true
module.name_mapper='^compiler/\(.*\)$' -> '<PROJECT_ROOT>/src/compiler/\1'
module.name_mapper='^core/\(.*\)$' -> '<PROJECT_ROOT>/src/core/\1'
module.name_mapper='^shared/\(.*\)$' -> '<PROJECT_ROOT>/src/shared/\1'
module.name_mapper='^web/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/web/\1'
module.name_mapper='^weex/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/weex/\1'
module.name_mapper='^server/\(.*\)$' -> '<PROJECT_ROOT>/src/server/\1'
module.name_mapper='^entries/\(.*\)$' -> '<PROJECT_ROOT>/src/entries/\1'
module.name_mapper='^sfc/\(.*\)$' -> '<PROJECT_ROOT>/src/sfc/\1'
suppress_comment= \\(.\\|\n\\)*\\$flow-disable-line


================================================
FILE: .gitignore
================================================
.idea/
node_modules
dist
example/
.DS_Store


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 HuangYi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Vue.js 技术揭秘

[电子书](https://ustbhuangyi.github.io/vue-analysis/)

目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理,让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0,并且之后会随着版本升级而做相应的更新,充分发挥电子书的优势。

这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的,同学们可以免费阅读,视频是收费的,25+小时纯干货课程,如果有需要的同学可以购买来学习,**但请务必支持正版,请尊重作者的劳动成果**。

## 章节目录

为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心、编译、扩展、生态四个方面去讲,并拆成了八个章节,如下图:

<img src="https://ustbhuangyi.github.io/vue-analysis/assets/mind.png">

**第一章:准备工作**

介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式,以及从入口开始分析了 Vue.js 的初始化过程。

**第二章:数据驱动**

详细讲解了模板数据到 DOM 渲染的过程,从 `new Vue` 开始,分析了 `mount`、`render`、`update`、`patch` 等流程。

**第三章:组件化**

分析了组件化的实现原理,并且分析了组件周边的原理实现,包括合并配置、生命周期、组件注册、异步组件。

**第四章:深入响应式原理**

详细讲解了数据的变化如何驱动视图的变化,分析了响应式对象的创建,依赖收集、派发更新的实现过程,一些特殊情况的处理,并对比了计算属性和侦听属性的实现,最后分析了组件更新的过程。

**第五章:编译**

从编译的入口函数开始,分析了编译的三个核心流程的实现:`parse` -> `optimize` -> `codegen`。

**第六章:扩展**

详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现,该章节作为一个可扩展章节,未来会分析更多 Vue 提供的特性。

**第七章:Vue-Router**

分析了 Vue-Router 的实现原理,从路由注册开始,分析了路由对象、`matcher`,并深入分析了整个路径切换的实现过程和细节。

**第八章:Vuex**

分析了 Vuex 的实现原理,深入分析了它的初始化过程,常用 API 以及插件部分的实现。




================================================
FILE: build.sh
================================================
#!/usr/bin/env sh

set -e
npm run build

cd dist

git init
git add -A
git commit -m 'deploy'

git push -f git@github.com:ustbhuangyi/vue-analysis.git master:gh-pages

cd -


================================================
FILE: docs/.vuepress/config.js
================================================
module.exports = {
  base: '/vue-analysis/',
  dest: 'dist',
  title: 'Vue.js 技术揭秘',
  description: 'Analysis vue.js deeply',
  head: [
    ['link', { rel: 'icon', href: `/logo.png` }],
    ['link', { rel: 'manifest', href: '/manifest.json' }],
    ['meta', { name: 'theme-color', content: '#3eaf7c' }],
    ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
    ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
    ['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }],
    ['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }],
    ['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }],
    ['meta', { name: 'msapplication-TileColor', content: '#000000' }]
  ],
  serviceWorker: false,
  themeConfig: {
    repo: 'ustbhuangyi/vue-analysis',
    editLinks: true,
    docsDir: 'docs',
    editLinkText: '在 GitHub 上编辑此页',
    lastUpdated: '上次更新',
    nav: [
      {
        text: '2.x 版本',
        link: '/v2/prepare/'
      },
      {
        text: '3.x 版本',
        link: '/v3/new/'
      },
      {
        text: '2.x 源码配套视频',
        link: 'https://coding.imooc.com/class/228.html'
      },
      {
        text: '3.x 源码解析课程',
        link: 'https://kaiwu.lagou.com/course/courseInfo.htm?courseId=326#/content'
      },
      {
        text: 'React技术揭秘',
        link: 'https://react.iamkasong.com/'
      }
    ],
    sidebar: {
      '/v2/': [
        {
          title: '准备工作',
          collapsable: false,
          children: [
            ['prepare/', 'Introduction'],
            'prepare/flow',
            'prepare/directory',
            'prepare/build',
            'prepare/entrance'
          ]
        },
        {
          title: '数据驱动',
          collapsable: false,
          children: [
            ['data-driven/', 'Introduction'],
            'data-driven/new-vue',
            'data-driven/mounted',
            'data-driven/render',
            'data-driven/virtual-dom',
            'data-driven/create-element',
            'data-driven/update'
          ]
        },
        {
          title: '组件化',
          collapsable: false,
          children: [
            ['components/', 'Introduction'],
            'components/create-component',
            'components/patch',
            'components/merge-option',
            'components/lifecycle',
            'components/component-register',
            'components/async-component'
          ]
        },
        {
          title: '深入响应式原理',
          collapsable: false,
          children: [
            ['reactive/', 'Introduction'],
            'reactive/reactive-object',
            'reactive/getters',
            'reactive/setters',
            'reactive/next-tick',
            'reactive/questions',
            'reactive/computed-watcher',
            'reactive/component-update',
            'reactive/props',
            'reactive/summary'
          ]
        },
        {
          title: '编译',
          collapsable: false,
          children: [
            ['compile/', 'Introduction'],
            'compile/entrance',
            'compile/parse',
            'compile/optimize',
            'compile/codegen'
          ]
        },
        {
          title: '扩展',
          collapsable: false,
          children: [
            ['extend/', 'Introduction'],
            'extend/event',
            'extend/v-model',
            'extend/slot',
            'extend/keep-alive',
            'extend/tansition',
            'extend/tansition-group'
          ]
        },
        {
          title: 'Vue Router',
          collapsable: false,
          children: [
            ['vue-router/', 'Introduction'],
            'vue-router/install',
            'vue-router/router',
            'vue-router/matcher',
            'vue-router/transition-to'
          ]
        },
        {
          title: 'Vuex',
          collapsable: false,
          children: [
            ['vuex/', 'Introduction'],
            'vuex/init',
            'vuex/api',
            'vuex/plugin'
          ]
        }
      ],
      '/v3/': [
        {
          title: '先导篇',
          collapsable: false,
          children: [
            ['guide/', 'Introduction']
          ]
        },
        {
          title: 'Vue.js 3.0 核心源码解析​',
          collapsable: false,
          children: [
            ['new/', 'Introduction']
          ]
        }
      ]
    }
  }
}


================================================
FILE: docs/.vuepress/public/manifest.json
================================================
{
  "name": "VueAnalysis",
  "short_name": "VueAnalysis",
  "icons": [
    {
      "src": "/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/vue-analysis/",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#3eaf7c"
}


================================================
FILE: docs/README.md
================================================
## 前言

目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理,让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0,并且之后会随着版本升级而做相应的更新,充分发挥电子书的优势。

这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的,同学们可以免费阅读,视频是收费的,25+小时纯干货课程,如果有需要的同学可以购买来学习,**但请务必支持正版,请尊重作者的劳动成果**。

## 章节目录

为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心、编译、扩展、生态四个方面去讲,并拆成了八个章节,如下图:

<img :src="$withBase('/assets/mind.png')">

**第一章:准备工作**

介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式,以及从入口开始分析了 Vue.js 的初始化过程。

**第二章:数据驱动**

详细讲解了模板数据到 DOM 渲染的过程,从 `new Vue` 开始,分析了 `mount`、`render`、`update`、`patch` 等流程。

**第三章:组件化**

分析了组件化的实现原理,并且分析了组件周边的原理实现,包括合并配置、生命周期、组件注册、异步组件。

**第四章:深入响应式原理**

详细讲解了数据的变化如何驱动视图的变化,分析了响应式对象的创建,依赖收集、派发更新的实现过程,一些特殊情况的处理,并对比了计算属性和侦听属性的实现,最后分析了组件更新的过程。

**第五章:编译**

从编译的入口函数开始,分析了编译的三个核心流程的实现:`parse` -> `optimize` -> `codegen`。

**第六章:扩展**

详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现,该章节作为一个可扩展章节,未来会分析更多 Vue 提供的特性。

**第七章:Vue-Router**

分析了 Vue-Router 的实现原理,从路由注册开始,分析了路由对象、`matcher`,并深入分析了整个路径切换的实现过程和细节。

**第八章:Vuex**

分析了 Vuex 的实现原理,深入分析了它的初始化过程,常用 API 以及插件部分的实现。




================================================
FILE: docs/v2/compile/codegen.md
================================================
# codegen

编译的最后一步就是把优化后的 AST 树转换成可执行的代码,这部分内容也比较多,我并不打算把所有的细节都讲了,了解整体流程即可。部分细节我们会在之后的章节配合一个具体 case 去详细讲。

为了方便理解,我们还是用之前的例子:

```html
<ul :class="bindCls" class="list" v-if="isShow">
    <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
```

它经过编译,执行 `const code = generate(ast, options)`,生成的 `render` 代码串如下:

```js
with(this){
  return (isShow) ?
    _c('ul', {
        staticClass: "list",
        class: bindCls
      },
      _l((data), function(item, index) {
        return _c('li', {
          on: {
            "click": function($event) {
              clickItem(index)
            }
          }
        },
        [_v(_s(item) + ":" + _s(index))])
      })
    ) : _e()
}
```

这里的 `_c` 函数定义在 `src/core/instance/render.js` 中。

```js
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
```

而 `_l`、`_v` 定义在 `src/core/instance/render-helpers/index.js` 中:

```js
export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
}
```
顾名思义,`_c` 就是执行 `createElement` 去创建 VNode,而 `_l` 对应 `renderList` 渲染列表;`_v` 对应 `createTextVNode` 创建文本 VNode;`_e` 对于 `createEmptyVNode`创建空的 VNode。 

在 `compileToFunctions` 中,会把这个 `render` 代码串转换成函数,它的定义在 `src/compler/to-function.js` 中:

```js
const compiled = compile(template, options)
res.render = createFunction(compiled.render, fnGenErrors)

function createFunction (code, errors) {
  try {
    return new Function(code)
  } catch (err) {
    errors.push({ err, code })
    return noop
  }
}
```

实际上就是把 `render` 代码串通过 `new Function` 的方式转换成可执行的函数,赋值给 `vm.options.render`,这样当组件通过 `vm._render` 的时候,就会执行这个 `render` 函数。那么接下来我们就重点关注一下这个 `render` 代码串的生成过程。

## generate

```js
const code = generate(ast, options)
```

`generate` 函数的定义在 `src/compiler/codegen/index.js` 中:

```js
export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}
```

`generate` 函数首先通过 `genElement(ast, state)` 生成 `code`,再把 `code` 用 `with(this){return ${code}}}` 包裹起来。这里的 `state` 是 `CodegenState` 的一个实例,稍后我们在用到它的时候会介绍它。先来看一下 `genElement`:

```js
export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element
    let code
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      const data = el.plain ? undefined : genData(el, state)

      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`
    }
    // module transforms
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}
```
基本就是判断当前 AST 元素节点的属性执行不同的代码生成函数,在我们的例子中,我们先了解一下 `genFor` 和 `genIf`。

## `genIf`

```js
export function genIf (
  el: any,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {
  el.ifProcessed = true // avoid recursion
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions (
  conditions: ASTIfConditions,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {
  if (!conditions.length) {
    return altEmpty || '_e()'
  }

  const condition = conditions.shift()
  if (condition.exp) {
    return `(${condition.exp})?${
      genTernaryExp(condition.block)
    }:${
      genIfConditions(conditions, state, altGen, altEmpty)
    }`
  } else {
    return `${genTernaryExp(condition.block)}`
  }

  // v-if with v-once should generate code like (a)?_m(0):_m(1)
  function genTernaryExp (el) {
    return altGen
      ? altGen(el, state)
      : el.once
        ? genOnce(el, state)
        : genElement(el, state)
  }
}
```

`genIf` 主要是通过执行 `genIfConditions`,它是依次从 `conditions` 获取第一个 `condition`,然后通过对 `condition.exp` 去生成一段三元运算符的代码,`:` 后是递归调用 `genIfConditions`,这样如果有多个 `conditions`,就生成多层三元运算逻辑。这里我们暂时不考虑 `v-once` 的情况,所以
`genTernaryExp` 最终是调用了 `genElement`。

在我们的例子中,只有一个 `condition`,`exp` 为 `isShow`,因此生成如下伪代码:

```js
return (isShow) ? genElement(el, state) : _e()
```

## `genFor`

```js
export function genFor (
  el: any,
  state: CodegenState,
  altGen?: Function,
  altHelper?: string
): string {
  const exp = el.for
  const alias = el.alias
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''

  if (process.env.NODE_ENV !== 'production' &&
    state.maybeComponent(el) &&
    el.tag !== 'slot' &&
    el.tag !== 'template' &&
    !el.key
  ) {
    state.warn(
      `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
      `v-for should have explicit keys. ` +
      `See https://vuejs.org/guide/list.html#key for more info.`,
      true /* tip */
    )
  }

  el.forProcessed = true // avoid recursion
  return `${altHelper || '_l'}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
      `return ${(altGen || genElement)(el, state)}` +
    '})'
}
```

`genFor` 的逻辑很简单,首先 AST 元素节点中获取了和 `for` 相关的一些属性,然后返回了一个代码字符串。

在我们的例子中,`exp` 是 `data`,`alias` 是 `item`,`iterator1` ,因此生成如下伪代码:

```js
_l((data), function(item, index) {
  return genElememt(el, state)
})
```

## `genData` & `genChildren`

再次回顾我们的例子,它的最外层是 `ul`,首先执行 `genIf`,它最终调用了 `genElement(el, state)` 去生成子节点,注意,这里的 `el` 仍然指向的是 `ul` 对应的 AST 节点,但是此时的 `el.ifProcessed` 为 true,所以命中最后一个 else 逻辑:

```js
// component or element
let code
if (el.component) {
  code = genComponent(el.component, el, state)
} else {
  const data = el.plain ? undefined : genData(el, state)

  const children = el.inlineTemplate ? null : genChildren(el, state, true)
  code = `_c('${el.tag}'${
    data ? `,${data}` : '' // data
  }${
    children ? `,${children}` : '' // children
  })`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
  code = state.transforms[i](el, code)
}
return code
```

这里我们只关注 2 个逻辑,`genData` 和 `genChildren`:

- genData

```js
export function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'

  // directives first.
  // directives may mutate the el's other properties before they are generated.
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ','

  // key
  if (el.key) {
    data += `key:${el.key},`
  }
  // ref
  if (el.ref) {
    data += `ref:${el.ref},`
  }
  if (el.refInFor) {
    data += `refInFor:true,`
  }
  // pre
  if (el.pre) {
    data += `pre:true,`
  }
  // record original tag name for components using "is" attribute
  if (el.component) {
    data += `tag:"${el.tag}",`
  }
  // module data generation functions
  for (let i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el)
  }
  // attributes
  if (el.attrs) {
    data += `attrs:{${genProps(el.attrs)}},`
  }
  // DOM props
  if (el.props) {
    data += `domProps:{${genProps(el.props)}},`
  }
  // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false, state.warn)},`
  }
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true, state.warn)},`
  }
  // slot target
  // only for non-scoped slots
  if (el.slotTarget && !el.slotScope) {
    data += `slot:${el.slotTarget},`
  }
  // scoped slots
  if (el.scopedSlots) {
    data += `${genScopedSlots(el.scopedSlots, state)},`
  }
  // component v-model
  if (el.model) {
    data += `model:{value:${
      el.model.value
    },callback:${
      el.model.callback
    },expression:${
      el.model.expression
    }},`
  }
  // inline-template
  if (el.inlineTemplate) {
    const inlineTemplate = genInlineTemplate(el, state)
    if (inlineTemplate) {
      data += `${inlineTemplate},`
    }
  }
  data = data.replace(/,$/, '') + '}'
  // v-bind data wrap
  if (el.wrapData) {
    data = el.wrapData(data)
  }
  // v-on data wrap
  if (el.wrapListeners) {
    data = el.wrapListeners(data)
  }
  return data
}
```

`genData` 函数就是根据 AST 元素节点的属性构造出一个 `data` 对象字符串,这个在后面创建 VNode 的时候的时候会作为参数传入。

之前我们提到了 `CodegenState` 的实例 `state`,这里有一段关于 `state` 的逻辑:

```js
for (let i = 0; i < state.dataGenFns.length; i++) {
  data += state.dataGenFns[i](el)
}
```

`state.dataGenFns` 的初始化在它的构造器中。

```js
export class CodegenState {
  constructor (options: CompilerOptions) {
    // ...
    this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
    // ...
  }
}
```

实际上就是获取所有 `modules` 中的 `genData` 函数,其中,`class module` 和 `style module` 定义了 `genData` 函数。比如定义在 `src/platforms/web/compiler/modules/class.js` 中的 `genData` 方法:

```js
function genData (el: ASTElement): string {
  let data = ''
  if (el.staticClass) {
    data += `staticClass:${el.staticClass},`
  }
  if (el.classBinding) {
    data += `class:${el.classBinding},`
  }
  return data
}
```

在我们的例子中,`ul` AST 元素节点定义了 `el.staticClass` 和 `el.classBinding`,因此最终生成的 `data` 字符串如下:

```js
{
  staticClass: "list",
  class: bindCls
}
```

- genChildren

接下来我们再来看一下 `genChildren`,它的定义在 `src/compiler/codegen/index.js` 中:

```js
export function genChildren (
  el: ASTElement,
  state: CodegenState,
  checkSkip?: boolean,
  altGenElement?: Function,
  altGenNode?: Function
): string | void {
  const children = el.children
  if (children.length) {
    const el: any = children[0]
    if (children.length === 1 &&
      el.for &&
      el.tag !== 'template' &&
      el.tag !== 'slot'
    ) {
      return (altGenElement || genElement)(el, state)
    }
    const normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0
    const gen = altGenNode || genNode
    return `[${children.map(c => gen(c, state)).join(',')}]${
      normalizationType ? `,${normalizationType}` : ''
    }`
  }
}
```

在我们的例子中,`li` AST 元素节点是 `ul` AST 元素节点的 `children` 之一,满足 `(children.length === 1 && el.for && el.tag !== 'template' && el.tag !== 'slot')` 条件,因此通过 `genElement(el, state)` 生成 `li` AST元素节点的代码,也就回到了我们之前调用 `genFor` 生成的代码,把它们拼在一起生成的伪代码如下:

```js
return (isShow) ?
    _c('ul', {
        staticClass: "list",
        class: bindCls
      },
      _l((data), function(item, index) {
        return genElememt(el, state)
      })
    ) : _e()
```

在我们的例子中,在执行 `genElememt(el, state)` 的时候,`el` 还是 `li` AST 元素节点,`el.forProcessed` 已为 true,所以会继续执行 `genData` 和 `genChildren` 的逻辑。由于 `el.events` 不为空,在执行 `genData` 的时候,会执行 如下逻辑:

```js
if (el.events) {
  data += `${genHandlers(el.events, false, state.warn)},`
}
```

`genHandlers` 的定义在 `src/compiler/codegen/events.js` 中:

```js
export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean,
  warn: Function
): string {
  let res = isNative ? 'nativeOn:{' : 'on:{'
  for (const name in events) {
    res += `"${name}":${genHandler(name, events[name])},`
  }
  return res.slice(0, -1) + '}'
}
```
`genHandler` 的逻辑就不介绍了,很大部分都是对修饰符 `modifier` 的处理,感兴趣同学可以自己看,对于我们的例子,它最终 `genData` 生成的 `data` 字符串如下:

```js
{
  on: {
    "click": function($event) {
      clickItem(index)
    }
  }
}
```

`genChildren` 的时候,会执行到如下逻辑:

```js
export function genChildren (
  el: ASTElement,
  state: CodegenState,
  checkSkip?: boolean,
  altGenElement?: Function,
  altGenNode?: Function
): string | void {
  // ...
  const normalizationType = checkSkip
    ? getNormalizationType(children, state.maybeComponent)
    : 0
  const gen = altGenNode || genNode
  return `[${children.map(c => gen(c, state)).join(',')}]${
    normalizationType ? `,${normalizationType}` : ''
  }`
}

function genNode (node: ASTNode, state: CodegenState): string {
  if (node.type === 1) {
    return genElement(node, state)
  } if (node.type === 3 && node.isComment) {
    return genComment(node)
  } else {
    return genText(node)
  }
}
```
`genChildren` 的就是遍历 `children`,然后执行 `genNode` 方法,根据不同的 `type` 执行具体的方法。在我们的例子中,`li` AST 元素节点的 `children` 是 type 为 2 的表达式 AST 元素节点,那么会执行到 `genText(node)` 逻辑。

```js
export function genText (text: ASTText | ASTExpression): string {
  return `_v(${text.type === 2
    ? text.expression
    : transformSpecialNewlines(JSON.stringify(text.text))
  })`
}
```

因此在我们的例子中,`genChildren` 生成的代码串如下:

```js
[_v(_s(item) + ":" + _s(index))]
```

和之前拼在一起,最终生成的 `code` 如下:

```js
 return (isShow) ?
    _c('ul', {
        staticClass: "list",
        class: bindCls
      },
      _l((data), function(item, index) {
        return _c('li', {
          on: {
            "click": function($event) {
              clickItem(index)
            }
          }
        },
        [_v(_s(item) + ":" + _s(index))])
      })
    ) : _e()
```

## 总结

这一节通过例子配合解析,我们对从 `ast -> code ` 这一步有了一些了解,编译后生成的代码就是在运行时执行的代码。由于 `genCode` 的内容有很多,所以我对大家的建议是没必要把所有的细节都一次性看完,我们应该根据具体一个 case,走完一条主线即可。

在之后的章节我们会对 `slot` 的实现做解析,我们会重新复习编译的章节,针对具体问题做具体分析,有利于我们排除干扰,对编译过程的学习有更深入的理解。


================================================
FILE: docs/v2/compile/entrance.md
================================================
# 编译入口

当我们使用 Runtime + Compiler 的 Vue.js,它的入口是 `src/platforms/web/entry-runtime-with-compiler.js`,看一下它对 `$mount` 函数的定义:

```js
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
  if (!options.render) {
    let template = options.template
    if (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 = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}
```

这段函数逻辑之前分析过,关于编译的入口就是在这里:

```js
const { render, staticRenderFns } =  compileToFunctions(template, {
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
  }, this)
options.render = render
options.staticRenderFns = staticRenderFns
```

`compileToFunctions` 方法就是把模板 `template` 编译生成 `render` 以及 `staticRenderFns`,它的定义在 `src/platforms/web/compiler/index.js` 中:

```js
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'

const { compile, compileToFunctions } = createCompiler(baseOptions)

export { compile, compileToFunctions }
```
可以看到 `compileToFunctions` 方法实际上是 `createCompiler` 方法的返回值,该方法接收一个编译配置参数,接下来我们来看一下 `createCompiler` 方法的定义,在 `src/compiler/index.js` 中:

```js
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options)
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
```

`createCompiler` 方法实际上是通过调用 `createCompilerCreator` 方法返回的,该方法传入的参数是一个函数,真正的编译过程都在这个 `baseCompile` 函数里执行,那么 `createCompilerCreator` 又是什么呢,它的定义在 `src/compiler/create-compiler.js` 中:

```js
export function createCompilerCreator (baseCompile: Function): Function {
  return function createCompiler (baseOptions: CompilerOptions) {
    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)
      }

      if (options) {
        // merge custom modules
        if (options.modules) {
          finalOptions.modules =
            (baseOptions.modules || []).concat(options.modules)
        }
        // merge custom directives
        if (options.directives) {
          finalOptions.directives = extend(
            Object.create(baseOptions.directives || null),
            options.directives
          )
        }
        // copy other options
        for (const key in options) {
          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
    }

    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
```

可以看到该方法返回了一个 `createCompiler` 的函数,它接收一个 `baseOptions` 的参数,返回的是一个对象,包括 `compile` 方法属性和 `compileToFunctions` 属性,这个 `compileToFunctions` 对应的就是 `$mount` 函数调用的 `compileToFunctions` 方法,它是调用 `createCompileToFunctionFn` 方法的返回值,我们接下来看一下 `createCompileToFunctionFn` 方法,它的定义在 `src/compiler/to-function/js` 中:

```js
export function createCompileToFunctionFn (compile: Function): Function {
  const cache = Object.create(null)

  return function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = extend({}, options)
    const warn = options.warn || baseWarn
    delete options.warn

    /* 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.'
          )
        }
      }
    }

    // check cache
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[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 = []
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, 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 (cache[key] = res)
  }
}
```

至此我们总算找到了 `compileToFunctions` 的最终定义,它接收 3 个参数、编译模板 `template`,编译配置 `options` 和 Vue 实例 `vm`。核心的编译过程就一行代码:

```js
const compiled = compile(template, options)
```

`compile` 函数在执行 `createCompileToFunctionFn` 的时候作为参数传入,它是 `createCompiler` 函数中定义的 `compile` 函数,如下:

```js
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)
  }

  if (options) {
    // merge custom modules
    if (options.modules) {
      finalOptions.modules =
        (baseOptions.modules || []).concat(options.modules)
    }
    // merge custom directives
    if (options.directives) {
      finalOptions.directives = extend(
        Object.create(baseOptions.directives || null),
        options.directives
      )
    }
    // copy other options
    for (const key in options) {
      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` 函数执行的逻辑是先处理配置参数,真正执行编译过程就一行代码:

```js
const compiled = baseCompile(template, finalOptions)
```

`baseCompile` 在执行 `createCompilerCreator` 方法时作为参数传入,如下:

````js
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options)
  optimize(ast, options)
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
````

所以编译的入口我们终于找到了,它主要就是执行了如下几个逻辑:

- 解析模板字符串生成 AST

```js
const ast = parse(template.trim(), options)
```

- 优化语法树

```js
optimize(ast, options)
```

- 生成代码

```js
const code = generate(ast, options)
```

那么接下来的章节我会带大家去逐步分析这几个过程。
 
## 总结

编译入口逻辑之所以这么绕,是因为 Vue.js 在不同的平台下都会有编译的过程,因此编译过程中的依赖的配置 `baseOptions` 会有所不同。而编译过程会多次执行,但这同一个平台下每一次的编译过程配置又是相同的,为了不让这些配置在每次编译过程都通过参数传入,Vue.js 利用了函数柯里化的技巧很好的实现了 `baseOptions` 的参数保留。同样,Vue.js 也是利用函数柯里化技巧把基础的编译过程函数抽出来,通过 `createCompilerCreator(baseCompile)` 的方式把真正编译的过程和其它逻辑如对编译配置处理、缓存处理等剥离开,这样的设计还是非常巧妙的。

================================================
FILE: docs/v2/compile/index.md
================================================
# 编译

之前我们分析过模板到真实 DOM 渲染的过程,中间有一个环节是把模板编译成 `render` 函数,这个过程我们把它称作编译。

虽然我们可以直接为组件编写 `render` 函数,但是编写 `template` 模板更加直观,也更符合我们的开发习惯。

Vue.js 提供了 2 个版本,一个是 Runtime + Compiler 的,一个是 Runtime only 的,前者是包含编译代码的,可以把编译过程放在运行时做,后者是不包含编译代码的,需要借助 webpack 的 `vue-loader` 事先把模板编译成 `render `函数。

这一章我们就来分析编译的过程,对编译过程的了解会让我们对 Vue 的指令、内置组件等有更好的理解。不过由于编译的过程是一个相对复杂的过程,我们只要求理解整体的流程、输入和输出即可,对于细节我们不必抠太细。有些细节比如对于 `slot` 的处理我们可以在之后去分析插槽实现的时候再详细分析。

================================================
FILE: docs/v2/compile/optimize.md
================================================
# optimize

当我们的模板 `template` 经过 `parse` 过程后,会输出生成 AST 树,那么接下来我们需要对这颗树做优化,`optimize` 的逻辑是远简单于 `parse` 的逻辑,所以理解起来会轻松很多。

为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 `patch` 的过程跳过对他们的比对。

来看一下 `optimize` 方法的定义,在 `src/compiler/optimizer.js` 中:

```js
/**
 * Goal of the optimizer: walk the generated template AST tree
 * and detect sub-trees that are purely static, i.e. parts of
 * the DOM that never needs to change.
 *
 * Once we detect these sub-trees, we can:
 *
 * 1. Hoist them into constants, so that we no longer need to
 *    create fresh nodes for them on each re-render;
 * 2. Completely skip them in the patching process.
 */
export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  // first pass: mark all non-static nodes.
  markStatic(root)
  // second pass: mark static roots.
  markStaticRoots(root, false)
}

function genStaticKeys (keys: string): Function {
  return makeMap(
    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
    (keys ? ',' + keys : '')
  )
}
```

我们在编译阶段可以把一些 AST 节点优化成静态节点,所以整个 `optimize` 的过程实际上就干 2 件事情,`markStatic(root)` 标记静态节点 ,`markStaticRoots(root, false)` 标记静态根。

## 标记静态节点

```js
function markStatic (node: ASTNode) {
  node.static = isStatic(node)
  if (node.type === 1) {
    // do not make component slot content static. this avoids
    // 1. components not able to mutate slot nodes
    // 2. static slot content fails for hot-reloading
    if (
      !isPlatformReservedTag(node.tag) &&
      node.tag !== 'slot' &&
      node.attrsMap['inline-template'] == null
    ) {
      return
    }
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      if (!child.static) {
        node.static = false
      }
    }
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        const block = node.ifConditions[i].block
        markStatic(block)
        if (!block.static) {
          node.static = false
        }
      }
    }
  }
}

function isStatic (node: ASTNode): boolean {
  if (node.type === 2) { // expression
    return false
  }
  if (node.type === 3) { // text
    return true
  }
  return !!(node.pre || (
    !node.hasBindings && // no dynamic bindings
    !node.if && !node.for && // not v-if or v-for or v-else
    !isBuiltInTag(node.tag) && // not a built-in
    isPlatformReservedTag(node.tag) && // not a component
    !isDirectChildOfTemplateFor(node) &&
    Object.keys(node).every(isStaticKey)
  ))
}
```

首先执行 `node.static = isStatic(node)`

`isStatic` 是对一个 AST 元素节点是否是静态的判断,如果是表达式,就是非静态;如果是纯文本,就是静态;对于一个普通元素,如果有 pre 属性,那么它使用了 `v-pre` 指令,是静态,否则要同时满足以下条件:没有使用 `v-if`、`v-for`,没有使用其它指令(不包括 `v-once`),非内置组件,是平台保留的标签,非带有 `v-for` 的 `template` 标签的直接子节点,节点的所有属性的 `key` 都满足静态 key;这些都满足则这个 AST 节点是一个静态节点。

如果这个节点是一个普通元素,则遍历它的所有 `children`,递归执行 `markStatic`。因为所有的 `elseif` 和 `else` 节点都不在 `children` 中, 如果节点的 `ifConditions` 不为空,则遍历 `ifConditions` 拿到所有条件中的 `block`,也就是它们对应的 AST 节点,递归执行 `markStatic`。在这些递归过程中,一旦子节点有不是 `static` 的情况,则它的父节点的 `static` 均变成 false。

## 标记静态根

```js
function markStaticRoots (node: ASTNode, isInFor: boolean) {
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor
    }
    // For a node to qualify as a static root, it should have children that
    // are not just static text. Otherwise the cost of hoisting out will
    // outweigh the benefits and it's better off to just always render it fresh.
    if (node.static && node.children.length && !(
      node.children.length === 1 &&
      node.children[0].type === 3
    )) {
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
    if (node.children) {
      for (let i = 0, l = node.children.length; i < l; i++) {
        markStaticRoots(node.children[i], isInFor || !!node.for)
      }
    }
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        markStaticRoots(node.ifConditions[i].block, isInFor)
      }
    }
  }
}
```

`markStaticRoots` 第二个参数是 `isInFor`,对于已经是 `static` 的节点或者是 `v-once` 指令的节点,`node.staticInFor = isInFor`。
接着就是对于 `staticRoot` 的判断逻辑,从注释中我们可以看到,对于有资格成为 `staticRoot` 的节点,除了本身是一个静态节点外,必须满足拥有 `children`,并且 `children` 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了。

接下来和标记静态节点的逻辑一样,遍历 `children` 以及 `ifConditions`,递归执行 `markStaticRoots`。

回归我们之前的例子,经过 `optimize` 后,AST 树变成了如下:

```js
ast = {
  'type': 1,
  'tag': 'ul',
  'attrsList': [],
  'attrsMap': {
    ':class': 'bindCls',
    'class': 'list',
    'v-if': 'isShow'
  },
  'if': 'isShow',
  'ifConditions': [{
    'exp': 'isShow',
    'block': // ul ast element
  }],
  'parent': undefined,
  'plain': false,
  'staticClass': 'list',
  'classBinding': 'bindCls',
  'static': false,
  'staticRoot': false,
  'children': [{
    'type': 1,
    'tag': 'li',
    'attrsList': [{
      'name': '@click',
      'value': 'clickItem(index)'
    }],
    'attrsMap': {
      '@click': 'clickItem(index)',
      'v-for': '(item,index) in data'
     },
    'parent': // ul ast element
    'plain': false,
    'events': {
      'click': {
        'value': 'clickItem(index)'
      }
    },
    'hasBindings': true,
    'for': 'data',
    'alias': 'item',
    'iterator1': 'index',
    'static': false,
    'staticRoot': false,
    'children': [
      'type': 2,
      'expression': '_s(item)+":"+_s(index)'
      'text': '{{item}}:{{index}}',
      'tokens': [
        {'@binding':'item'},
        ':',
        {'@binding':'index'}
      ],
      'static': false
    ]
  }]
}
```

我们发现每一个 AST 元素节点都多了 `staic` 属性,并且 `type` 为 1 的普通元素 AST 节点多了 `staticRoot` 属性。

## 总结

那么至此我们分析完了 `optimize` 的过程,就是深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用。

我们通过 `optimize` 我们把整个 AST 树中的每一个 AST 元素节点标记了 `static` 和 `staticRoot`,它会影响我们接下来执行代码生成的过程。

================================================
FILE: docs/v2/compile/parse.md
================================================
# parse

编译过程首先就是对模板做解析,生成 AST,它是一种抽象语法树,是对源代码的抽象语法结构的树状表现形式。在很多编译技术中,如 babel 编译 ES6 的代码都会先生成 AST。

这个过程是比较复杂的,它会用到大量正则表达式对字符串解析,如果对正则不是很了解,建议先去补习正则表达式的知识。为了直观地演示 `parse` 的过程,我们先来看一个例子:

```html
<ul :class="bindCls" class="list" v-if="isShow">
    <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
```

经过 `parse` 过程后,生成的 AST 如下:

```js
ast = {
  'type': 1,
  'tag': 'ul',
  'attrsList': [],
  'attrsMap': {
    ':class': 'bindCls',
    'class': 'list',
    'v-if': 'isShow'
  },
  'if': 'isShow',
  'ifConditions': [{
    'exp': 'isShow',
    'block': // ul ast element
  }],
  'parent': undefined,
  'plain': false,
  'staticClass': 'list',
  'classBinding': 'bindCls',
  'children': [{
    'type': 1,
    'tag': 'li',
    'attrsList': [{
      'name': '@click',
      'value': 'clickItem(index)'
    }],
    'attrsMap': {
      '@click': 'clickItem(index)',
      'v-for': '(item,index) in data'
     },
    'parent': // ul ast element
    'plain': false,
    'events': {
      'click': {
        'value': 'clickItem(index)'
      }
    },
    'hasBindings': true,
    'for': 'data',
    'alias': 'item',
    'iterator1': 'index',
    'children': [
      'type': 2,
      'expression': '_s(item)+":"+_s(index)'
      'text': '{{item}}:{{index}}',
      'tokens': [
        {'@binding':'item'},
        ':',
        {'@binding':'index'}
      ]
    ]
  }]
}
```

可以看到,生成的 AST 是一个树状结构,每一个节点都是一个 `ast element`,除了它自身的一些属性,还维护了它的父子关系,如 `parent` 指向它的父节点,`children` 指向它的所有子节点。先对 AST 有一些直观的印象,那么接下来我们来分析一下这个 AST 是如何得到的。

## 整体流程

首先来看一下 `parse` 的定义,在 `src/compiler/parser/index.js` 中:

```js
export function parse (
  template: string,
  options: CompilerOptions
): ASTElement | void {
  getFnsAndConfigFromOptions(options)

  parseHTML(template, {
    // options ...
    start (tag, attrs, unary) {
      let element = createASTElement(tag, attrs)
      processElement(element)
      treeManagement()
    },

    end () {
      treeManagement()
      closeElement()
    },

    chars (text: string) {
      handleText()
      createChildrenASTOfText()
    },
    comment (text: string) {
      createChildrenASTOfComment()
    }
  })
  return astRootElement
}
```

`parse` 函数的代码很长,贴一遍对同学的理解没有好处,我先把它拆成伪代码的形式,方便同学们对整体流程先有一个大致的了解。接下来我们就来分解分析每段伪代码的作用。

### 从 options 中获取方法和配置

对应伪代码:

```js
getFnsAndConfigFromOptions(options)
```

`parse` 函数的输入是 `template` 和 `options`,输出是 AST 的根节点。`template` 就是我们的模板字符串,而 `options` 实际上是和平台相关的一些配置,它的定义在 `src/platforms/web/compiler/options` 中:

```js
import {
  isPreTag,
  mustUseProp,
  isReservedTag,
  getTagNamespace
} from '../util/index'

import modules from './modules/index'
import directives from './directives/index'
import { genStaticKeys } from 'shared/util'
import { isUnaryTag, canBeLeftOpenTag } from './util'

export const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules,
  directives,
  isPreTag,
  isUnaryTag,
  mustUseProp,
  canBeLeftOpenTag,
  isReservedTag,
  getTagNamespace,
  staticKeys: genStaticKeys(modules)
}
```
这些属性和方法之所以放到 `platforms` 目录下是因为它们在不同的平台(web 和 weex)的实现是不同的。

我们用伪代码 `getFnsAndConfigFromOptions` 表示了这一过程,它的实际代码如下:

```js
warn = options.warn || baseWarn

platformIsPreTag = options.isPreTag || no
platformMustUseProp = options.mustUseProp || no
platformGetTagNamespace = options.getTagNamespace || no

transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')

delimiters = options.delimiters
```

这些方法和配置都是后续解析时候需要的,可以不用去管它们的具体作用,我们先往后看。

### 解析 HTML 模板

对应伪代码:

```js
parseHTML(template, options)
```

对于 `template` 模板的解析主要是通过 `parseHTML` 函数,它的定义在 `src/compiler/parser/html-parser` 中:

```js
export function parseHTML (html, options) {
  let lastTag
  while (html) {
    if (!lastTag || !isPlainTextElement(lastTag)){
      let textEnd = html.indexOf('<')
      if (textEnd === 0) {
         if(matchComment) {
           advance(commentLength)
           continue
         }
         if(matchDoctype) {
           advance(doctypeLength)
           continue
         }
         if(matchEndTag) {
           advance(endTagLength)
           parseEndTag()
           continue
         }
         if(matchStartTag) {
           parseStartTag()
           handleStartTag()
           continue
         }
      }
      handleText()
      advance(textLength)
    } else {
       handlePlainTextElement()
       parseEndTag()
    }
  }
}
```
由于 `parseHTML` 的逻辑也非常复杂,因此我也用了伪代码的方式表达,整体来说它的逻辑就是循环解析 `template` ,用正则做各种匹配,对于不同情况分别进行不同的处理,直到整个 template 被解析完毕。
在匹配的过程中会利用 `advance` 函数不断前进整个模板字符串,直到字符串末尾。

```js
function advance (n) {
  index += n
  html = html.substring(n)
}
```

为了更加直观地说明 `advance` 的作用,可以通过一副图表示:

<img :src="$withBase('/assets/advance-1.png')">

调用 `advance` 函数:

```js
advance(4)
```

得到结果:


<img :src="$withBase('/assets/advance-2.png')">


匹配的过程中主要利用了正则表达式,如下:

```js
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
```
通过这些正则表达式,我们可以匹配注释节点、文档类型节点、开始闭合标签等。

- 注释节点、文档类型节点

对于注释节点和文档类型节点的匹配,如果匹配到我们仅仅做的是做前进即可。

```js
if (comment.test(html)) {
  const commentEnd = html.indexOf('-->')

  if (commentEnd >= 0) {
    if (options.shouldKeepComment) {
      options.comment(html.substring(4, commentEnd))
    }
    advance(commentEnd + 3)
    continue
  }
}

if (conditionalComment.test(html)) {
  const conditionalEnd = html.indexOf(']>')

  if (conditionalEnd >= 0) {
    advance(conditionalEnd + 2)
    continue
  }
}

const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
  advance(doctypeMatch[0].length)
  continue
}
```

对于注释和条件注释节点,前进至它们的末尾位置;对于文档类型节点,则前进它自身长度的距离。

- 开始标签

```js
const startTagMatch = parseStartTag()
if (startTagMatch) {
  handleStartTag(startTagMatch)
  if (shouldIgnoreFirstNewline(lastTag, html)) {
    advance(1)
  }
  continue
}
```

首先通过 `parseStartTag` 解析开始标签:

```js
function parseStartTag () {
  const start = html.match(startTagOpen)
  if (start) {
    const match = {
      tagName: start[1],
      attrs: [],
      start: index
    }
    advance(start[0].length)
    let end, attr
    while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
      advance(attr[0].length)
      match.attrs.push(attr)
    }
    if (end) {
      match.unarySlash = end[1]
      advance(end[0].length)
      match.end = index
      return match
    }
  }
}
```
对于开始标签,除了标签名之外,还有一些标签相关的属性。函数先通过正则表达式 `startTagOpen` 匹配到开始标签,然后定义了 `match` 对象,接着循环去匹配开始标签中的属性并添加到 `match.attrs` 中,直到匹配的开始标签的闭合符结束。如果匹配到闭合符,则获取一元斜线符,前进到闭合符尾,并把当前索引赋值给 `match.end`。

`parseStartTag` 对开始标签解析拿到 `match` 后,紧接着会执行 `handleStartTag` 对 `match` 做处理:

```js
function handleStartTag (match) {
  const tagName = match.tagName
  const unarySlash = match.unarySlash
  
  if (expectHTML) {
    if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
      parseEndTag(lastTag)
    }
    if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
      parseEndTag(tagName)
    }
  }
  
  const unary = isUnaryTag(tagName) || !!unarySlash
  
  const l = match.attrs.length
  const attrs = new Array(l)
  for (let i = 0; i < l; i++) {
    const args = match.attrs[i]
    if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
      if (args[3] === '') { delete args[3] }
      if (args[4] === '') { delete args[4] }
      if (args[5] === '') { delete args[5] }
    }
    const value = args[3] || args[4] || args[5] || ''
    const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
      ? options.shouldDecodeNewlinesForHref
      : options.shouldDecodeNewlines
    attrs[i] = {
      name: args[1],
      value: decodeAttr(value, shouldDecodeNewlines)
    }
  }
  
  if (!unary) {
    stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
    lastTag = tagName
  }
  
  if (options.start) {
    options.start(tagName, attrs, unary, match.start, match.end)
  }
}
```

`handleStartTag` 的核心逻辑很简单,先判断开始标签是否是一元标签,类似 `<img>、<br/>` 这样,接着对 `match.attrs` 遍历并做了一些处理,最后判断如果非一元标签,则往 `stack` 里 push 一个对象,并且把 `tagName` 赋值给 `lastTag`。至于 `stack` 的作用,稍后我会介绍。

最后调用了 `options.start` 回调函数,并传入一些参数,这个回调函数的作用稍后我会详细介绍。
 
- 闭合标签

```js
const endTagMatch = html.match(endTag)
if (endTagMatch) {
  const curIndex = index
  advance(endTagMatch[0].length)
  parseEndTag(endTagMatch[1], curIndex, index)
  continue
}
```

先通过正则 `endTag` 匹配到闭合标签,然后前进到闭合标签末尾,然后执行 `parseEndTag` 方法对闭合标签做解析。

```js

function parseEndTag (tagName, start, end) {
  let pos, lowerCasedTagName
  if (start == null) start = index
  if (end == null) end = index
  
  if (tagName) {
    lowerCasedTagName = tagName.toLowerCase()
  }
  
  if (tagName) {
    for (pos = stack.length - 1; pos >= 0; pos--) {
      if (stack[pos].lowerCasedTag === lowerCasedTagName) {
        break
      }
    }
  } else {
    pos = 0
  }
  
  if (pos >= 0) {
    for (let i = stack.length - 1; i >= pos; i--) {
      if (process.env.NODE_ENV !== 'production' &&
        (i > pos || !tagName) &&
        options.warn
      ) {
        options.warn(
          `tag <${stack[i].tag}> has no matching end tag.`
        )
      }
      if (options.end) {
        options.end(stack[i].tag, start, end)
      }
    }
    stack.length = pos
    lastTag = pos && stack[pos - 1].tag
  } else if (lowerCasedTagName === 'br') {
    if (options.start) {
      options.start(tagName, [], true, start, end)
    }
  } else if (lowerCasedTagName === 'p') {
    if (options.start) {
      options.start(tagName, [], false, start, end)
    }
    if (options.end) {
      options.end(tagName, start, end)
    }
  }
}
```

`parseEndTag` 的核心逻辑很简单,在介绍之前我们回顾一下在执行 `handleStartTag` 的时候,对于非一元标签(有 endTag)我们都把它构造成一个对象压入到 `stack` 中,如图所示:

<img :src="$withBase('/assets/stack.png')">

那么对于闭合标签的解析,就是倒序 `stack`,找到第一个和当前 `endTag` 匹配的元素。如果是正常的标签匹配,那么 `stack` 的最后一个元素应该和当前的 `endTag` 匹配,但是考虑到如下错误情况:

```html
<div><span></div>
```
这个时候当 `endTag` 为 `</div>` 的时候,从 `stack` 尾部找到的标签是 `<span>`,就不能匹配,因此这种情况会报警告。匹配后把栈到 `pos` 位置的都弹出,并从 `stack` 尾部拿到 `lastTag`。

最后调用了 `options.end` 回调函数,并传入一些参数,这个回调函数的作用稍后我会详细介绍。

- 文本

```js
let text, rest, next
if (textEnd >= 0) {
  rest = html.slice(textEnd)
  while (
    !endTag.test(rest) &&
    !startTagOpen.test(rest) &&
    !comment.test(rest) &&
    !conditionalComment.test(rest)
  ) {
    next = rest.indexOf('<', 1)
    if (next < 0) break
    textEnd += next
    rest = html.slice(textEnd)
  }
  text = html.substring(0, textEnd)
  advance(textEnd)
}

if (textEnd < 0) {
  text = html
  html = ''
}

if (options.chars && text) {
  options.chars(text)
}
```

接下来判断 `textEnd` 是否大于等于 0 的,满足则说明到从当前位置到 `textEnd` 位置都是文本,并且如果 `<` 是纯文本中的字符,就继续找到真正的文本结束的位置,然后前进到结束的位置。

再继续判断 `textEnd` 小于 0 的情况,则说明整个 `template` 解析完毕了,把剩余的 `html` 都赋值给了 `text`。

最后调用了 `options.chars` 回调函数,并传 `text` 参数,这个回调函数的作用稍后我会详细介绍。

因此,在循环解析整个 `template` 的过程中,会根据不同的情况,去执行不同的回调函数,下面我们来看看这些回调函数的作用。

### 处理开始标签 

对应伪代码:

```js
start (tag, attrs, unary) {
  let element = createASTElement(tag, attrs)
  processElement(element)
  treeManagement()
}
```

当解析到开始标签的时候,最后会执行 `start` 回调函数,函数主要就做 3 件事情,创建 AST 元素,处理 AST 元素,AST 树管理。下面我们来分别来看这几个过程。

- 创建 AST 元素

```js
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
  attrs = guardIESVGBug(attrs)
}

let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
  element.ns = ns
}

export function createASTElement (
  tag: string,
  attrs: Array<Attr>,
  parent: ASTElement | void
): ASTElement {
  return {
    type: 1,
    tag,
    attrsList: attrs,
    attrsMap: makeAttrsMap(attrs),
    parent,
    children: []
  }
}
```

通过 `createASTElement` 方法去创建一个 AST 元素,并添加了 namespace。可以看到,每一个 AST 元素就是一个普通的 JavaScript 对象,其中,`type` 表示 AST 元素类型,`tag` 表示标签名,`attrsList` 表示属性列表,`attrsMap` 表示属性映射表,`parent` 表示父的 AST 元素,`children` 表示子 AST 元素集合。

- 处理 AST 元素

```js
if (isForbiddenTag(element) && !isServerRendering()) {
  element.forbidden = true
  process.env.NODE_ENV !== 'production' && warn(
    'Templates should only be responsible for mapping the state to the ' +
    'UI. Avoid placing tags with side-effects in your templates, such as ' +
    `<${tag}>` + ', as they will not be parsed.'
  )
}

// apply pre-transforms
for (let i = 0; i < preTransforms.length; i++) {
  element = preTransforms[i](element, options) || element
}

if (!inVPre) {
  processPre(element)
  if (element.pre) {
    inVPre = true
  }
}
if (platformIsPreTag(element.tag)) {
  inPre = true
}
if (inVPre) {
  processRawAttrs(element)
} else if (!element.processed) {
  // structural directives
  processFor(element)
  processIf(element)
  processOnce(element)
  // element-scope stuff
  processElement(element, options)
}
```
首先是对模块 `preTransforms` 的调用,其实所有模块的 `preTransforms`、 `transforms` 和 `postTransforms` 的定义都在 `src/platforms/web/compiler/modules` 目录中,这部分我们暂时不会介绍,之后会结合具体的例子说。接着判断 `element` 是否包含各种指令通过 `processXXX` 做相应的处理,处理的结果就是扩展 AST 元素的属性。这里我并不会一一介绍所有的指令处理,而是结合我们当前的例子,我们来看一下 `processFor` 和 `processIf`:

```js
export function processFor (el: ASTElement) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    const res = parseFor(exp)
    if (res) {
      extend(el, res)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `Invalid v-for expression: ${exp}`
      )
    }
  }
}

export const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/
export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
export function parseFor (exp: string): ?ForParseResult {
  const inMatch = exp.match(forAliasRE)
  if (!inMatch) return
  const res = {}
  res.for = inMatch[2].trim()
  const alias = inMatch[1].trim().replace(stripParensRE, '')
  const iteratorMatch = alias.match(forIteratorRE)
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, '')
    res.iterator1 = iteratorMatch[1].trim()
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim()
    }
  } else {
    res.alias = alias
  }
  return res
}
```

`processFor` 就是从元素中拿到 `v-for` 指令的内容,然后分别解析出 `for`、`alias`、`iterator1`、`iterator2` 等属性的值添加到 AST 的元素上。就我们的示例 `v-for="(item,index) in data"` 而言,解析出的的 `for` 是 `data`,`alias` 是 `item`,`iterator1` 是 `index`,没有 `iterator2`。

```js
function processIf (el) {
  const exp = getAndRemoveAttr(el, 'v-if')
  if (exp) {
    el.if = exp
    addIfCondition(el, {
      exp: exp,
      block: el
    })
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) {
      el.else = true
    }
    const elseif = getAndRemoveAttr(el, 'v-else-if')
    if (elseif) {
      el.elseif = elseif
    }
  }
}
export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
  if (!el.ifConditions) {
    el.ifConditions = []
  }
  el.ifConditions.push(condition)
}
```

`processIf` 就是从元素中拿 `v-if` 指令的内容,如果拿到则给 AST 元素添加 `if` 属性和 `ifConditions` 属性;否则尝试拿 `v-else` 指令及 `v-else-if` 指令的内容,如果拿到则给 AST 元素分别添加 `else` 和 `elseif` 属性。 

- AST 树管理

我们在处理开始标签的时候为每一个标签创建了一个 AST 元素,在不断解析模板创建 AST 元素的时候,我们也要为它们建立父子关系,就像 DOM 元素的父子关系那样。

AST 树管理相关代码如下:

```js
function checkRootConstraints (el) {
  if (process.env.NODE_ENV !== 'production') {
    if (el.tag === 'slot' || el.tag === 'template') {
      warnOnce(
        `Cannot use <${el.tag}> as component root element because it may ` +
        'contain multiple nodes.'
      )
    }
    if (el.attrsMap.hasOwnProperty('v-for')) {
      warnOnce(
        'Cannot use v-for on stateful component root element because ' +
        'it renders multiple elements.'
      )
    }
  }
}


// tree management
if (!root) {
  root = element
  checkRootConstraints(root)
} else if (!stack.length) {
  // allow root elements with v-if, v-else-if and v-else
  if (root.if && (element.elseif || element.else)) {
    checkRootConstraints(element)
    addIfCondition(root, {
      exp: element.elseif,
      block: element
    })
  } else if (process.env.NODE_ENV !== 'production') {
    warnOnce(
      `Component template should contain exactly one root element. ` +
      `If you are using v-if on multiple elements, ` +
      `use v-else-if to chain them instead.`
    )
  }
}
if (currentParent && !element.forbidden) {
  if (element.elseif || element.else) {
    processIfConditions(element, currentParent)
  } else if (element.slotScope) { // scoped slot
    currentParent.plain = false
    const name = element.slotTarget || '"default"'
    ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
  } else {
    currentParent.children.push(element)
    element.parent = currentParent
  }
}
if (!unary) {
  currentParent = element
  stack.push(element)
} else {
  closeElement(element)
}
```

AST 树管理的目标是构建一颗 AST 树,本质上它要维护 `root` 根节点和当前父节点 `currentParent`。为了保证元素可以正确闭合,这里也利用了 `stack` 栈的数据结构,和我们之前解析模板时用到的 `stack` 类似。

当我们在处理开始标签的时候,判断如果有 `currentParent`,会把当前 AST 元素 push 到 `currentParent.chilldren` 中,同时把 AST 元素的 `parent` 指向 `currentParent`。

 接着就是更新 `currentParent` 和 `stack` ,判断当前如果不是一个一元标签,我们要把它生成的 AST 元素 push 到 `stack` 中,并且把当前的 AST 元素赋值给 `currentParent`。

`stack` 和 `currentParent` 除了在处理开始标签的时候会变化,在处理闭合标签的时候也会变化,因此整个 AST 树管理要结合闭合标签的处理逻辑看。

### 处理闭合标签

对应伪代码:

```js
end () {
  treeManagement()
  closeElement()
}
```

当解析到闭合标签的时候,最后会执行 `end` 回调函数:

```js
// remove trailing whitespace
const element = stack[stack.length - 1]
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
  element.children.pop()
}
// pop stack
stack.length -= 1
currentParent = stack[stack.length - 1]
closeElement(element)
```

首先处理了尾部空格的情况,然后把 `stack` 的元素弹一个出栈,并把 `stack` 最后一个元素赋值给 `currentParent`,这样就保证了当遇到闭合标签的时候,可以正确地更新 `stack` 的长度以及 `currentParent` 的值,这样就维护了整个 AST 树。

最后执行了 `closeElement(element)`:

```js
function closeElement (element) {
  // check pre state
  if (element.pre) {
    inVPre = false
  }
  if (platformIsPreTag(element.tag)) {
    inPre = false
  }
  // apply post-transforms
  for (let i = 0; i < postTransforms.length; i++) {
    postTransforms[i](element, options)
  }
}
```
`closeElement` 逻辑很简单,就是更新一下 `inVPre` 和 `inPre` 的状态,以及执行 `postTransforms` 函数,这些我们暂时都不必了解。

### 处理文本内容

对应伪代码:

```js
chars (text: string) {
  handleText()
  createChildrenASTOfText()
}
```

除了处理开始标签和闭合标签,我们还会在解析模板的过程中去处理一些文本内容:

```js
const children = currentParent.children
text = inPre || text.trim()
  ? isTextTag(currentParent) ? text : decodeHTMLCached(text)
  // only preserve whitespace if its not right after a starting tag
  : preserveWhitespace && children.length ? ' ' : ''
if (text) {
  let res
  if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
    children.push({
      type: 2,
      expression: res.expression,
      tokens: res.tokens,
      text
    })
  } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
    children.push({
      type: 3,
      text
    })
  }
}
```
文本构造的 AST 元素有 2 种类型,一种是有表达式的,`type` 为 2,一种是纯文本,`type` 为 3。在我们的例子中,文本就是 `{{item}}:{{index}}`,是个表达式,通过执行 `parseText(text, delimiters)` 对文本解析,它的定义在 `src/compiler/parser/text-parser.js` 中:

```js
const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g

const buildRegex = cached(delimiters => {
  const open = delimiters[0].replace(regexEscapeRE, '\\$&')
  const close = delimiters[1].replace(regexEscapeRE, '\\$&')
  return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
})

export function parseText (
  text: string,
  delimiters?: [string, string]
): TextParseResult | void {
  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
  if (!tagRE.test(text)) {
    return
  }
  const tokens = []
  const rawTokens = []
  let lastIndex = tagRE.lastIndex = 0
  let match, index, tokenValue
  while ((match = tagRE.exec(text))) {
    index = match.index
    // push text token
    if (index > lastIndex) {
      rawTokens.push(tokenValue = text.slice(lastIndex, index))
      tokens.push(JSON.stringify(tokenValue))
    }
    // tag token
    const exp = parseFilters(match[1].trim())
    tokens.push(`_s(${exp})`)
    rawTokens.push({ '@binding': exp })
    lastIndex = index + match[0].length
  }
  if (lastIndex < text.length) {
    rawTokens.push(tokenValue = text.slice(lastIndex))
    tokens.push(JSON.stringify(tokenValue))
  }
  return {
    expression: tokens.join('+'),
    tokens: rawTokens
  }
}
```

`parseText` 首先根据分隔符(默认是 `{{}}`)构造了文本匹配的正则表达式,然后再循环匹配文本,遇到普通文本就 push 到 `rawTokens` 和 `tokens` 中,如果是表达式就转换成 `_s(${exp})` push 到 `tokens` 中,以及转换成 `{@binding:exp}` push 到 `rawTokens` 中。
  
 对于我们的例子 `{{item}}:{{index}}`,`tokens` 就是 `[_s(item),'":"',_s(index)]`;`rawTokens` 就是 `[{'@binding':'item'},':',{'@binding':'index'}]`。那么返回的对象如下:
 
 ```js
return {
  expression: '_s(item)+":"+_s(index)',
  tokens: [{'@binding':'item'},':',{'@binding':'index'}]
}
```

## 流程图

<img :src="$withBase('/assets/parse.png')">

## 总结

那么至此,`parse` 的过程就分析完了,看似复杂,但我们可以抛开细节理清它的整体流程。`parse` 的目标是把 `template` 模板字符串转换成 AST 树,它是一种用 JavaScript 对象的形式来描述整个模板。那么整个 `parse` 的过程是利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造 AST 树的目的。

AST 元素节点总共有 3 种类型,`type` 为 1 表示是普通元素,为 2 表示是表达式,为 3 表示是纯文本。其实这里我觉得源码写的不够友好,这种是典型的魔术数字,如果转换成用常量表达会更利于源码阅读。

当 AST 树构造完毕,下一步就是 `optimize` 优化这颗树。


================================================
FILE: docs/v2/components/async-component.md
================================================
# 异步组件

在我们平时的开发工作中,为了减少首屏代码体积,往往会把一些非首屏的组件设计成异步组件,按需加载。Vue 也原生支持了异步组件的能力,如下:

```js
Vue.component('async-example', function (resolve, reject) {
   // 这个特殊的 require 语法告诉 webpack
   // 自动将编译后的代码分割成不同的块,
   // 这些块将通过 Ajax 请求自动下载。
   require(['./my-async-component'], resolve)
})
```

示例中可以看到,Vue 注册的组件不再是一个对象,而是一个工厂函数,函数有两个参数 `resolve` 和 `reject`,函数内部用 `setTimout` 模拟了异步,实际使用可能是通过动态请求异步组件的 JS 地址,最终通过执行 `resolve` 方法,它的参数就是我们的异步组件对象。

在了解了异步组件如何注册后,我们从源码的角度来分析一下它的实现。

上一节我们分析了组件的注册逻辑,由于组件的定义并不是一个普通对象,所以不会执行 `Vue.extend` 的逻辑把它变成一个组件的构造函数,但是它仍然可以执行到 `createComponent` 函数,我们再来对这个函数做回顾,它的定义在 `src/core/vdom/create-component/js` 中:

```js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

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

  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
}
```

我们省略了不必要的逻辑,只保留关键逻辑,由于我们这个时候传入的 `Ctor` 是一个函数,那么它也并不会执行 `Vue.extend` 逻辑,因此它的 `cid` 是 `undefiend`,进入了异步组件创建的逻辑。这里首先执行了 `Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)` 方法,它的定义在 `src/core/vdom/helpers/resolve-async-component.js` 中:

```js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>,
  context: Component
): Class<Component> | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {
    return factory.resolved
  }

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

  if (isDef(factory.contexts)) {
    // already pending
    factory.contexts.push(context)
  } else {
    const contexts = factory.contexts = [context]
    let sync = true

    const forceRender = () => {
      for (let i = 0, l = contexts.length; i < l; i++) {
        contexts[i].$forceUpdate()
      }
    }

    const resolve = once((res: Object | Class<Component>) => {
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender()
      }
    })

    const reject = once(reason => {
      process.env.NODE_ENV !== 'production' && warn(
        `Failed to resolve async component: ${String(factory)}` +
        (reason ? `\nReason: ${reason}` : '')
      )
      if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender()
      }
    })

    const res = factory(resolve, reject)

    if (isObject(res)) {
      if (typeof res.then === 'function') {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isDef(res.component) && typeof res.component.then === 'function') {
        res.component.then(resolve, reject)

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

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

        if (isDef(res.timeout)) {
          setTimeout(() => {
            if (isUndef(factory.resolved)) {
              reject(
                process.env.NODE_ENV !== 'production'
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }

    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}
```

`resolveAsyncComponent` 函数的逻辑略复杂,因为它实际上处理了 3 种异步组件的创建方式,除了刚才示例的组件注册方式,还支持 2 种,一种是支持 `Promise` 创建组件的方式,如下:

```js
Vue.component(
  'async-webpack-example',
  // 该 `import` 函数返回一个 `Promise` 对象。
  () => import('./my-async-component')
)
```

另一种是高级异步组件,如下:

```js
const AsyncComp = () => ({
  // 需要加载的组件。应当是一个 Promise
  component: import('./MyComp.vue'),
  // 加载中应当渲染的组件
  loading: LoadingComp,
  // 出错时渲染的组件
  error: ErrorComp,
  // 渲染加载中组件前的等待时间。默认:200ms。
  delay: 200,
  // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
  timeout: 3000
})
Vue.component('async-example', AsyncComp)
```

那么解下来,我们就根据这 3 种异步组件的情况,来分别去分析 `resolveAsyncComponent` 的逻辑。

## 普通函数异步组件

针对普通函数的情况,前面几个 if 判断可以忽略,它们是为高级组件所用,对于 `factory.contexts` 的判断,是考虑到多个地方同时初始化一个异步组件,那么它的实际加载应该只有一次。接着进入实际加载逻辑,定义了 `forceRender`、`resolve` 和 `reject` 函数,注意 `resolve` 和 `reject` 函数用 `once` 函数做了一层包装,它的定义在 `src/shared/util.js` 中:

````js
/**
 * Ensure a function is called only once.
 */
export function once (fn: Function): Function {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}
````
`once` 逻辑非常简单,传入一个函数,并返回一个新函数,它非常巧妙地利用闭包和一个标志位保证了它包装的函数只会执行一次,也就是确保 `resolve` 和 `reject` 函数只执行一次。

接下来执行 `const res = factory(resolve, reject)` 逻辑,这块儿就是执行我们组件的工厂函数,同时把 `resolve` 和 `reject` 函数作为参数传入,组件的工厂函数通常会先发送请求去加载我们的异步组件的 JS 文件,拿到组件定义的对象 `res` 后,执行 `resolve(res)` 逻辑,它会先执行 `factory.resolved = ensureCtor(res, baseCtor)`:

```js
function ensureCtor (comp: any, base) {
  if (
    comp.__esModule ||
    (hasSymbol && comp[Symbol.toStringTag] === 'Module')
  ) {
    comp = comp.default
  }
  return isObject(comp)
    ? base.extend(comp)
    : comp
}
```
这个函数目的是为了保证能找到异步组件 JS 定义的组件对象,并且如果它是一个普通对象,则调用 `Vue.extend` 把它转换成一个组件的构造函数。

`resolve` 逻辑最后判断了 `sync`,显然我们这个场景下 `sync` 为 false,那么就会执行 `forceRender` 函数,它会遍历 `factory.contexts`,拿到每一个调用异步组件的实例 `vm`, 执行 `vm.$forceUpdate()` 方法,它的定义在 `src/core/instance/lifecycle.js` 中:
 
```js
Vue.prototype.$forceUpdate = function () {
  const vm: Component = this
  if (vm._watcher) {
    vm._watcher.update()
  }
}
```

`$forceUpdate` 的逻辑非常简单,就是调用渲染 `watcher` 的 `update` 方法,让渲染 `watcher` 对应的回调函数执行,也就是触发了组件的重新渲染。之所以这么做是因为 Vue 通常是数据驱动视图重新渲染,但是在整个异步组件加载过程中是没有数据发生变化的,所以通过执行 `$forceUpdate` 可以强制组件重新渲染一次。

## `Promise` 异步组件

```js
Vue.component(
  'async-webpack-example',
  // 该 `import` 函数返回一个 `Promise` 对象。
  () => import('./my-async-component')
)
```
webpack 2+ 支持了异步加载的语法糖:`() => import('./my-async-component')`,当执行完 `res = factory(resolve, reject)`,返回的值就是 ` import('./my-async-component')` 的返回值,它是一个 `Promise` 对象。接着进入 if 条件,又判断了 `typeof res.then === 'function')`,条件满足,执行:

```js
if (isUndef(factory.resolved)) {
  res.then(resolve, reject)
}
```
当组件异步加载成功后,执行 `resolve`,加载失败则执行 `reject`,这样就非常巧妙地实现了配合 webpack 2+ 的异步加载组件的方式(`Promise`)加载异步组件。

## 高级异步组件

由于异步加载组件需要动态加载 JS,有一定网络延时,而且有加载失败的情况,所以通常我们在开发异步组件相关逻辑的时候需要设计 loading 组件和 error 组件,并在适当的时机渲染它们。Vue.js 2.3+ 支持了一种高级异步组件的方式,它通过一个简单的对象配置,帮你搞定 loading 组件和 error 组件的渲染时机,你完全不用关心细节,非常方便。接下来我们就从源码的角度来分析高级异步组件是怎么实现的。

```js
const AsyncComp = () => ({
  // 需要加载的组件。应当是一个 Promise
  component: import('./MyComp.vue'),
  // 加载中应当渲染的组件
  loading: LoadingComp,
  // 出错时渲染的组件
  error: ErrorComp,
  // 渲染加载中组件前的等待时间。默认:200ms。
  delay: 200,
  // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
  timeout: 3000
})
Vue.component('async-example', AsyncComp)
```
高级异步组件的初始化逻辑和普通异步组件一样,也是执行 `resolveAsyncComponent`,当执行完 `res = factory(resolve, reject)`,返回值就是定义的组件对象,显然满足 `else if (isDef(res.component) && typeof res.component.then === 'function')` 的逻辑,接着执行 `res.component.then(resolve, reject)`,当异步组件加载成功后,执行 `resolve`,失败执行 `reject`。

因为异步组件加载是一个异步过程,它接着又同步执行了如下逻辑:

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

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

if (isDef(res.timeout)) {
  setTimeout(() => {
    if (isUndef(factory.resolved)) {
      reject(
        process.env.NODE_ENV !== 'production'
          ? `timeout (${res.timeout}ms)`
          : null
      )
    }
  }, res.timeout)
}
```
先判断 `res.error` 是否定义了 error 组件,如果有的话则赋值给 `factory.errorComp`。
接着判断 `res.loading` 是否定义了 loading 组件,如果有的话则赋值给 `factory.loadingComp`,如果设置了 `res.delay` 且为 0,则设置 `factory.loading = true`,否则延时 `delay` 的时间执行:

```js
if (isUndef(factory.resolved) && isUndef(factory.error)) {
    factory.loading = true
    forceRender()
}
```

最后判断 `res.timeout`,如果配置了该项,则在 `res.timout` 时间后,如果组件没有成功加载,执行 `reject`。

在 `resolveAsyncComponent` 的最后有一段逻辑:

```js
sync = false
return factory.loading
  ? factory.loadingComp
  : factory.resolved
```

如果 `delay` 配置为 0,则这次直接渲染 loading 组件,否则则延时 `delay` 执行 `forceRender`,那么又会再一次执行到 `resolveAsyncComponent`。

那么这时候我们有几种情况,按逻辑的执行顺序,对不同的情况做判断。

### 异步组件加载失败
当异步组件加载失败,会执行 `reject` 函数:

```js
const reject = once(reason => {
  process.env.NODE_ENV !== 'production' && warn(
    `Failed to resolve async component: ${String(factory)}` +
    (reason ? `\nReason: ${reason}` : '')
  )
  if (isDef(factory.errorComp)) {
    factory.error = true
    forceRender()
  }
})
```
这个时候会把 `factory.error` 设置为 `true`,同时执行 `forceRender()` 再次执行到 `resolveAsyncComponent`:

```js
if (isTrue(factory.error) && isDef(factory.errorComp)) {
  return factory.errorComp
}
```

那么这个时候就返回 `factory.errorComp`,直接渲染 error 组件。
  
### 异步组件加载成功

当异步组件加载成功,会执行 `resolve` 函数:

```js
const resolve = once((res: Object | Class<Component>) => {
  factory.resolved = ensureCtor(res, baseCtor)
  if (!sync) {
    forceRender()
  }
})
```
首先把加载结果缓存到 `factory.resolved` 中,这个时候因为 `sync` 已经为 false,则执行 `forceRender()` 再次执行到 `resolveAsyncComponent`:

```js
if (isDef(factory.resolved)) {
  return factory.resolved
}
```
那么这个时候直接返回 `factory.resolved`,渲染成功加载的组件。

### 异步组件加载中

如果异步组件加载中并未返回,这时候会走到这个逻辑:

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

那么则会返回 `factory.loadingComp`,渲染 loading 组件。

### 异步组件加载超时

如果超时,则走到了 `reject` 逻辑,之后逻辑和加载失败一样,渲染 error 组件。

## 异步组件 patch

回到 `createComponent` 的逻辑:

```js
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
  return createAsyncPlaceholder(
    asyncFactory,
    data,
    context,
    children,
    tag
  )
}
```

如果是第一次执行 `resolveAsyncComponent`,除非使用高级异步组件 `0 delay` 去创建了一个 loading 组件,否则返回是 `undefiend`,接着通过 `createAsyncPlaceholder` 创建一个注释节点作为占位符。它的定义在 `src/core/vdom/helpers/resolve-async-components.js` 中:

```js
export function createAsyncPlaceholder (
  factory: Function,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag: ?string
): VNode {
  const node = createEmptyVNode()
  node.asyncFactory = factory
  node.asyncMeta = { data, context, children, tag }
  return node
}
```

实际上就是就是创建了一个占位的注释 VNode,同时把 `asyncFactory` 和 `asyncMeta` 赋值给当前 `vnode`。

当执行 `forceRender` 的时候,会触发组件的重新渲染,那么会再一次执行 `resolveAsyncComponent`,这时候就会根据不同的情况,可能返回 loading、error 或成功加载的异步组件,返回值不为 `undefined`,因此就走正常的组件 `render`、`patch` 过程,与组件第一次渲染流程不一样,这个时候是存在新旧 `vnode` 的,下一章我会分析组件更新的 `patch` 过程。


## 总结

通过以上代码分析,我们对 Vue 的异步组件的实现有了深入的了解,知道了 3 种异步组件的实现方式,并且看到高级异步组件的实现是非常巧妙的,它实现了 loading、resolve、reject、timeout 4 种状态。异步组件实现的本质是 2 次渲染,除了 0 delay 的高级异步组件第一次直接渲染成 loading 组件外,其它都是第一次渲染生成一个注释节点,当异步获取组件成功后,再通过 `forceRender` 强制重新渲染,这样就能正确渲染出我们异步加载的组件了。

================================================
FILE: docs/v2/components/component-register.md
================================================
# 组件注册

在 Vue.js 中,除了它内置的组件如 `keep-alive`、`component`、`transition`、`transition-group` 等,其它用户自定义组件在使用前必须注册。很多同学在开发过程中可能会遇到如下报错信息:

``` 
'Unknown custom element: <xxx> - did you register the component correctly?
 For recursive components, make sure to provide the "name" option.'
```

一般报这个错的原因都是我们使用了未注册的组件。Vue.js 提供了 2 种组件的注册方式,全局注册和局部注册。接下来我们从源码分析的角度来分析这两种注册方式。

## 全局注册

要注册一个全局组件,可以使用 `Vue.component(tagName, options)`。例如:

```js
Vue.component('my-component', {
  // 选项
})
```

那么,`Vue.component` 函数是在什么时候定义的呢,它的定义过程发生在最开始初始化 Vue 的全局函数的时候,代码在 `src/core/global-api/assets.js` 中:

```js
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
```
函数首先遍历 `ASSET_TYPES`,得到 `type` 后挂载到 Vue 上 。`ASSET_TYPES` 的定义在 `src/shared/constants.js` 中:

```js
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
```
所以实际上 Vue 是初始化了 3 个全局函数,并且如果 `type` 是 `component` 且 `definition` 是一个对象的话,通过 `this.opitons._base.extend`, 相当于 `Vue.extend` 把这个对象转换成一个继承于 Vue 的构造函数,最后通过 `this.options[type + 's'][id] = definition` 把它挂载到 `Vue.options.components` 上。

由于我们每个组件的创建都是通过 `Vue.extend` 继承而来,我们之前分析过在继承的过程中有这么一段逻辑:

```js
Sub.options = mergeOptions(
  Super.options,
  extendOptions
)
```

也就是说它会把 `Vue.options` 合并到 `Sub.options`,也就是组件的 `options` 上, 然后在组件的实例化阶段,会执行 `merge options` 逻辑,把 `Sub.options.components` 合并到 `vm.$options.components` 上。

然后在创建 `vnode` 的过程中,会执行 `_createElement` 方法,我们再来回顾一下这部分的逻辑,它的定义在 `src/core/vdom/create-element.js` 中:

```js
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // ...
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.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
      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
    vnode = createComponent(tag, data, context, children)
  }
  // ...
}
```
这里有一个判断逻辑 `isDef(Ctor = resolveAsset(context.$options, 'components', tag))`,先来看一下 `resolveAsset` 的定义,在 `src/core/utils/options.js` 中:

```js
/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}
```
这段逻辑很简单,先通过 `const assets = options[type]` 拿到 `assets`,然后再尝试拿 `assets[id]`,这里有个顺序,先直接使用 `id` 拿,如果不存在,则把 `id` 变成驼峰的形式再拿,如果仍然不存在则在驼峰的基础上把首字母再变成大写的形式再拿,如果仍然拿不到则报错。这样说明了我们在使用 `Vue.component(id, definition)` 全局注册组件的时候,id 可以是连字符、驼峰或首字母大写的形式。

那么回到我们的调用 `resolveAsset(context.$options, 'components', tag)`,即拿 `vm.$options.components[tag]`,这样我们就可以在 `resolveAsset` 的时候拿到这个组件的构造函数,并作为 `createComponent` 的钩子的参数。

## 局部注册

Vue.js 也同样支持局部注册,我们可以在一个组件内部使用 `components` 选项做组件的局部注册,例如:

```js
import HelloWorld from './components/HelloWorld'

export default {
  components: {
    HelloWorld
  }
}
```

其实理解了全局注册的过程,局部注册是非常简单的。在组件的 Vue 的实例化阶段有一个合并 `option` 的逻辑,之前我们也分析过,所以就把 `components` 合并到 `vm.$options.components` 上,这样我们就可以在 `resolveAsset` 的时候拿到这个组件的构造函数,并作为 `createComponent` 的钩子的参数。

注意,局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 `Vue.options` 下,所以在所有组件创建的过程中,都会从全局的 `Vue.options.components` 扩展到当前组件的 `vm.$options.components` 下,这就是全局注册的组件能被任意使用的原因。
 
## 总结

通过这一小节的分析,我们对组件的注册过程有了认识,并理解了全局注册和局部注册的差异。其实在平时的工作中,当我们使用到组件库的时候,往往更通用基础组件都是全局注册的,而编写的特例场景的业务组件都是局部注册的。了解了它们的原理,对我们在工作中到底使用全局注册组件还是局部注册组件是有这非常好的指导意义的。


================================================
FILE: docs/v2/components/create-component.md
================================================
# createComponent

上一章我们在分析 `createElement` 的实现的时候,它最终会调用 `_createElement` 方法,其中有一段逻辑是对参数 `tag` 的判断,如果是一个普通的 html 标签,像上一章的例子那样是一个普通的 div,则会实例化一个普通 VNode 节点,否则通过 `createComponent` 方法创建一个组件 VNode。

```js
if (typeof tag === 'string') {
  let Ctor
  ns = (context.$vnode && context.$vnode.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
    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
  vnode = createComponent(tag, data, context, children)
}
```

在我们这一章传入的是一个 App 对象,它本质上是一个 `Component` 类型,那么它会走到上述代码的 else 逻辑,直接通过 `createComponent` 方法来创建 `vnode`。所以接下来我们来看一下 `createComponent` 方法的实现,它定义在 `src/core/vdom/create-component.js` 文件中:

```js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

  // 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.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  data = data || {}

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

  // 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
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

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

    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // install component management hooks onto the placeholder node
  installComponentHooks(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 },
    asyncFactory
  )

  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  /* istanbul ignore if */
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }

  return vnode
}
```

可以看到,`createComponent` 的逻辑也会有一些复杂,但是分析源码比较推荐的是只分析核心流程,分支流程可以之后针对性的看,所以这里针对组件渲染这个 case 主要就 3 个关键步骤:

构造子类构造函数,安装组件钩子函数和实例化 `vnode`。

## 构造子类构造函数

```js
const baseCtor = context.$options._base

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

我们在编写一个组件的时候,通常都是创建一个普通对象,还是以我们的 App.vue 为例,代码如下:

```js
import HelloWorld from './components/HelloWorld'

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}
```
这里 export 的是一个对象,所以 `createComponent` 里的代码逻辑会执行到 `baseCtor.extend(Ctor)`,在这里 `baseCtor` 实际上就是 Vue,这个的定义是在最开始初始化 Vue 的阶段,在 `src/core/global-api/index.js` 中的 `initGlobalAPI` 函数有这么一段逻辑:

```js
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
```
细心的同学会发现,这里定义的是 `Vue.options`,而我们的 `createComponent` 取的是 `context.$options`,实际上在 `src/core/instance/init.js` 里 Vue 原型上的 `_init` 函数中有这么一段逻辑:

```js
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)
```

这样就把 Vue 上的一些 `option` 扩展到了 vm.$options 上,所以我们也就能通过 `vm.$options._base` 拿到 Vue 这个构造函数了。`mergeOptions` 的实现我们会在后续章节中具体分析,现在只需要理解它的功能是把 Vue 构造函数的 `options` 和用户传入的 `options` 做一层合并,到 `vm.$options ` 上。

在了解了 `baseCtor` 指向了 Vue 之后,我们来看一下 `Vue.extend` 函数的定义,在 `src/core/global-api/extend.js` 中。

```js
/**
 * Class inheritance
 */
Vue.extend = function (extendOptions: Object): Function {
  extendOptions = extendOptions || {}
  const Super = this
  const SuperId = Super.cid
  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
  if (cachedCtors[SuperId]) {
    return cachedCtors[SuperId]
  }

  const name = extendOptions.name || Super.options.name
  if (process.env.NODE_ENV !== 'production' && name) {
    validateComponentName(name)
  }

  const Sub = function VueComponent (options) {
    this._init(options)
  }
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.cid = cid++
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub['super'] = Super

  // For props and computed properties, we define the proxy getters on
  // the Vue instances at extension time, on the extended prototype. This
  // avoids Object.defineProperty calls for each instance created.
  if (Sub.options.props) {
    initProps(Sub)
  }
  if (Sub.options.computed) {
    initComputed(Sub)
  }

  // allow further extension/mixin/plugin usage
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use

  // create asset registers, so extended classes
  // can have their private assets too.
  ASSET_TYPES.forEach(function (type) {
    Sub[type] = Super[type]
  })
  // enable recursive self-lookup
  if (name) {
    Sub.options.components[name] = Sub
  }

  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super's options have
  // been updated.
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

  // cache constructor
  cachedCtors[SuperId] = Sub
  return Sub
}
```

`Vue.extend` 的作用就是构造一个 `Vue` 的子类,它使用一种非常经典的原型继承的方式把一个纯对象转换一个继承于 `Vue` 的构造器 `Sub` 并返回,然后对 `Sub` 这个对象本身扩展了一些属性,如扩展 `options`、添加全局 API 等;并且对配置中的 `props` 和 `computed` 做了初始化工作;最后对于这个 `Sub` 构造函数做了缓存,避免多次执行 `Vue.extend` 的时候对同一个子组件重复构造。

这样当我们去实例化 `Sub` 的时候,就会执行 `this._init` 逻辑再次走到了 `Vue` 实例的初始化逻辑,实例化子组件的逻辑在之后的章节会介绍。

```js
const Sub = function VueComponent (options) {
  this._init(options)
}
```

## 安装组件钩子函数

```js
// install component management hooks onto the placeholder node
installComponentHooks(data)
```

我们之前提到 Vue.js 使用的 Virtual DOM 参考的是开源库 [snabbdom](https://github.com/snabbdom/snabbdom),它的一个特点是在 VNode 的 patch 流程中对外暴露了各种时机的钩子函数,方便我们做一些额外的事情,Vue.js 也是充分利用这一点,在初始化一个 Component 类型的 VNode 的过程中实现了几个钩子函数:

```js
const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },

  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },

  destroy (vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else {
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}

const hooksToMerge = Object.keys(componentVNodeHooks)

function installComponentHooks (data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    const existing = hooks[key]
    const toMerge = componentVNodeHooks[key]
    if (existing !== toMerge && !(existing && existing._merged)) {
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}

function mergeHook (f1: any, f2: any): Function {
  const merged = (a, b) => {
    // flow complains about extra args which is why we use any
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}
```
整个 `installComponentHooks` 的过程就是把 `componentVNodeHooks` 的钩子函数合并到 `data.hook` 中,在 VNode 执行 `patch` 的过程中执行相关的钩子函数,具体的执行我们稍后在介绍 `patch` 过程中会详细介绍。这里要注意的是合并策略,在合并过程中,如果某个时机的钩子已经存在 `data.hook` 中,那么通过执行 `mergeHook` 函数做合并,这个逻辑很简单,就是在最终执行的时候,依次执行这两个钩子函数即可。

## 实例化 VNode

```js
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 },
  asyncFactory
)
return vnode
```

最后一步非常简单,通过 `new VNode` 实例化一个 `vnode` 并返回。需要注意的是和普通元素节点的 `vnode` 不同,组件的 `vnode` 是没有 `children` 的,这点很关键,在之后的 `patch` 过程中我们会再提。

## 总结

这一节我们分析了 `createComponent` 的实现,了解到它在渲染一个组件的时候的 3 个关键逻辑:构造子类构造函数,安装组件钩子函数和实例化 `vnode`。`createComponent` 后返回的是组件 `vnode`,它也一样走到 `vm._update` 方法,进而执行了 `patch` 函数,我们在上一章对 `patch` 函数做了简单的分析,那么下一节我们会对它做进一步的分析。


================================================
FILE: docs/v2/components/index.md
================================================
# 组件化

Vue.js 另一个核心思想是组件化。所谓组件化,就是把页面拆分成多个组件 (component),每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用,组件和组件之间可以嵌套。

我们在用 Vue.js 开发实际项目的时候,就是像搭积木一样,编写一堆组件拼装生成页面。在 Vue.js 的官网中,也是花了大篇幅来介绍什么是组件,如何编写组件以及组件拥有的属性和特性。

那么在这一章节,我们将从源码的角度来分析 Vue 的组件内部是如何工作的,只有了解了内部的工作原理,才能让我们使用它的时候更加得心应手。

接下来我们会用 Vue-cli 初始化的代码为例,来分析一下 Vue 组件初始化的一个过程。

```js
import Vue from 'vue'
import App from './App.vue'

var app = new Vue({
  el: '#app',
  // 这里的 h 是 createElement 方法
  render: h => h(App)
})
```
这段代码相信很多同学都很熟悉,它和我们上一章相同的点也是通过 `render` 函数去渲染的,不同的这次通过 `createElement` 传的参数是一个组件而不是一个原生的标签,那么接下来我们就开始分析这一过程。

================================================
FILE: docs/v2/components/lifecycle.md
================================================
# 生命周期

每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码。

<img :src="$withBase('/assets/lifecycle.png')"/>

在我们实际项目开发过程中,会非常频繁地和 Vue 组件的生命周期打交道,接下来我们就从源码的角度来看一下这些生命周期的钩子函数是如何被执行的。

源码中最终执行生命周期的函数都是调用 `callHook` 方法,它的定义在 `src/core/instance/lifecycle` 中:

```js
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}
```

`callHook` 函数的逻辑很简单,根据传入的字符串 `hook`,去拿到 `vm.$options[hook]` 对应的回调函数数组,然后遍历执行,执行的时候把 `vm` 作为函数执行的上下文。

在上一节中,我们详细地介绍了 Vue.js 合并 `options` 的过程,各个阶段的生命周期的函数也被合并到 `vm.$options` 里,并且是一个数组。因此 `callhook` 函数的功能就是调用某个生命周期钩子注册的所有回调函数。

了解了生命周期的执行方式后,接下来我们会具体介绍每一个生命周期函数它的调用时机。

## beforeCreate & created

`beforeCreate` 和 `created` 函数都是在实例化 `Vue` 的阶段,在 `_init` 方法中执行的,它的定义在 `src/core/instance/init.js` 中:

```js
Vue.prototype._init = function (options?: Object) {
  // ...
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')
  // ...
}
```

可以看到 `beforeCreate` 和 `created` 的钩子调用是在 `initState` 的前后,`initState` 的作用是初始化 `props`、`data`、`methods`、`watch`、`computed` 等属性,之后我们会详细分析。那么显然 `beforeCreate` 的钩子函数中就不能获取到 `props`、`data` 中定义的值,也不能调用 `methods` 中定义的函数。

在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能够访问 DOM,一般来说,如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以,如果是需要访问 `props`、`data` 等数据的话,就需要使用 `created` 钩子函数。之后我们会介绍 vue-router 和 vuex 的时候会发现它们都混合了 `beforeCreate` 钩子函数。

## beforeMount & mounted

顾名思义,`beforeMount` 钩子函数发生在 `mount`,也就是 DOM 挂载之前,它的调用时机是在 `mountComponent` 函数中,定义在 `src/core/instance/lifecycle.js` 中:

```js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // ...
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
```
在执行 `vm._render()` 函数渲染 VNode 之前,执行了 `beforeMount` 钩子函数,在执行完 `vm._update()` 把 VNode patch 到真实 DOM 后,执行 `mounted` 钩子。注意,这里对 `mounted` 钩子函数执行有一个判断逻辑,`vm.$vnode` 如果为 `null`,则表明这不是一次组件的初始化过程,而是我们通过外部 `new Vue` 初始化过程。那么对于组件,它的 `mounted` 时机在哪儿呢?

之前我们提到过,组件的 VNode patch 到 DOM 后,会执行 `invokeInsertHook` 函数,把 `insertedVnodeQueue` 里保存的钩子函数依次执行一遍,它的定义在 `src/core/vdom/patch.js` 中:

```js
function invokeInsertHook (vnode, queue, initial) {
  // delay insert hooks for component root nodes, invoke them after the
  // element is really inserted
  if (isTrue(initial) && isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}
```
该函数会执行 `insert` 这个钩子函数,对于组件而言,`insert` 钩子函数的定义在 `src/core/vdom/create-component.js` 中的 `componentVNodeHooks` 中:

```js
const componentVNodeHooks = {
  // ...
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    // ...
  },
}
```
我们可以看到,每个子组件都是在这个钩子函数中执行 `mounted` 钩子函数,并且我们之前分析过,`insertedVnodeQueue` 的添加顺序是先子后父,所以对于同步渲染的子组件而言,`mounted` 钩子函数的执行顺序也是先子后父。 

## beforeUpdate & updated

顾名思义,`beforeUpdate` 和 `updated` 的钩子函数执行时机都应该是在数据更新的时候,到目前为止,我们还没有分析 Vue 的数据双向绑定、更新相关,下一章我会详细介绍这个过程。

`beforeUpdate` 的执行时机是在渲染 Watcher 的 `before` 函数中,我们刚才提到过:

```js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // ...

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  // ...
}

```
注意这里有个判断,也就是在组件已经 `mounted` 之后,才会去调用这个钩子函数。

`update` 的执行时机是在`flushSchedulerQueue` 函数调用的时候,它的定义在 `src/core/observer/scheduler.js` 中:

```js
function flushSchedulerQueue () {
  // ...
  // 获取到 updatedQueue
  callUpdatedHooks(updatedQueue)
}

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated')
    }
  }
}
```

`flushSchedulerQueue` 函数我们之后会详细介绍,可以先大概了解一下,`updatedQueue` 是更新了的 `wathcer` 数组,那么在 `callUpdatedHooks` 函数中,它对这些数组做遍历,只有满足当前 `watcher` 为 `vm._watcher` 以及组件已经 `mounted` 这两个条件,才会执行 `updated` 钩子函数。

我们之前提过,在组件 mount 的过程中,会实例化一个渲染的 `Watcher` 去监听 `vm` 上的数据变化重新渲染,这段逻辑发生在 `mountComponent` 函数执行的时候:

```js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // ...
  // 这里是简写
  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  // ...
}
```
那么在实例化 `Watcher` 的过程中,在它的构造函数里会判断 `isRenderWatcher`,接着把当前 `watcher` 的实例赋值给 `vm._watcher`,定义在 `src/core/observer/watcher.js` 中:

```js
export default class Watcher {
  // ...
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // ...
  }
}
```

同时,还把当前 `wathcer` 实例 push 到 `vm._watchers` 中,`vm._watcher` 是专门用来监听 `vm` 上数据变化然后重新渲染的,所以它是一个渲染相关的 `watcher`,因此在 `callUpdatedHooks` 函数中,只有 `vm._watcher` 的回调执行完毕后,才会执行 `updated` 钩子函数。

## beforeDestroy & destroyed

顾名思义,`beforeDestroy` 和 `destroyed` 钩子函数的执行时机在组件销毁的阶段,组件的销毁过程之后会详细介绍,最终会调用 `$destroy` 方法,它的定义在 `src/core/instance/lifecycle.js` 中:
 
```js
Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
```
`beforeDestroy` 钩子函数的执行时机是在 `$destroy` 函数执行最开始的地方,接着执行了一系列的销毁动作,包括从 `parent` 的 `$children` 中删掉自身,删除 `watcher`,当前渲染的 VNode 执行销毁钩子函数等,执行完毕后再调用 `destroy` 钩子函数。

在 `$destroy` 的执行过程中,它又会执行 ` vm.__patch__(vm._vnode, null)` 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 `destroy` 钩子函数执行顺序是先子后父,和 `mounted` 过程一样。

## activated & deactivated

`activated` 和 `deactivated` 钩子函数是专门为 `keep-alive` 组件定制的钩子,我们会在介绍 `keep-alive` 组件的时候详细介绍,这里先留个悬念。

## 总结

这一节主要介绍了 Vue 生命周期中各个钩子函数的执行时机以及顺序,通过分析,我们知道了如在 `created` 钩子函数中可以访问到数据,在 `mounted` 钩子函数中可以访问到 DOM,在 `destroy` 钩子函数中可以做一些定时器销毁工作,了解它们有利于我们在合适的生命周期去做不同的事情。



================================================
FILE: docs/v2/components/merge-option.md
================================================
# 合并配置

通过之前章节的源码分析我们知道,`new Vue` 的过程通常有 2 种场景,一种是外部我们的代码主动调用 `new Vue(options)` 的方式实例化一个 Vue 对象;另一种是我们上一节分析的组件过程中内部通过 `new Vue(options)` 实例化子组件。

无论哪种场景,都会执行实例的 `_init(options)` 方法,它首先会执行一个 ` merge options` 的逻辑,相关的代码在 `src/core/instance/init.js` 中:

```js
Vue.prototype._init = function (options?: Object) {
  // 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
    )
  }
  // ...
}
```

可以看到不同场景对于 `options` 的合并逻辑是不一样的,并且传入的 `options` 值也有非常大的不同,接下来我会分开介绍 2 种场景的 options 合并过程。

为了更直观,我们可以举个简单的示例:

```js
import Vue from 'vue'

let childComp = {
  template: '<div>{{msg}}</div>',
  created() {
    console.log('child created')
  },
  mounted() {
    console.log('child mounted')
  },
  data() {
    return {
      msg: 'Hello Vue'
    }
  }
}

Vue.mixin({
  created() {
    console.log('parent created')
  }
})

let app = new Vue({
  el: '#app',
  render: h => h(childComp)
})
```

## 外部调用场景

当执行 `new Vue` 的时候,在执行 `this._init(options)` 的时候,就会执行如下逻辑去合并 `options`:

```js
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)
```

这里通过调用 `mergeOptions` 方法来合并,它实际上就是把 `resolveConstructorOptions(vm.constructor)` 的返回值和 `options` 做合并,`resolveConstructorOptions` 的实现先不考虑,在我们这个场景下,它还是简单返回 `vm.constructor.options`,相当于 `Vue.options`,那么这个值又是什么呢,其实在 `initGlobalAPI(Vue)` 的时候定义了这个值,代码在 `src/core/global-api/index.js` 中:

```js
export function initGlobalAPI (Vue: GlobalAPI) {
  // ...
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)
  // ...
}
```
首先通过 `Vue.options = Object.create(null)` 创建一个空对象,然后遍历 `ASSET_TYPES`,`ASSET_TYPES` 的定义在 `src/shared/constants.js` 中:

```js
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
```

所以上面遍历 `ASSET_TYPES` 后的代码相当于:

```js
Vue.options.components = {}
Vue.options.directives = {}
Vue.options.filters = {}
```
接着执行了 `Vue.options._base = Vue`,它的作用在我们上节实例化子组件的时候介绍了。

最后通过 `extend(Vue.options.components, builtInComponents)` 把一些内置组件扩展到 `Vue.options.components` 上,Vue 的内置组件目前有 `<keep-alive>`、`<transition>` 和 `<transition-group>` 组件,这也就是为什么我们在其它组件中使用 `<keep-alive>` 组件不需要注册的原因,这块儿后续我们介绍 `<keep-alive>` 组件的时候会详细讲。

那么回到 `mergeOptions` 这个函数,它的定义在 `src/core/util/options.js` 中:

```js
/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
```

`mergeOptions` 主要功能就是把 `parent` 和 `child` 这两个对象根据一些合并策略,合并成一个新对象并返回。比较核心的几步,先递归把 `extends` 和 `mixins` 合并到 `parent` 上,然后遍历 `parent`,调用 `mergeField`,然后再遍历 `child`,如果 `key` 不在 `parent` 的自身属性上,则调用 `mergeField`。

这里有意思的是 `mergeField` 函数,它对不同的 `key` 有着不同的合并策略。举例来说,对于生命周期函数,它的合并策略是这样的:

```js
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})
```
这其中的 `LIFECYCLE_HOOKS` 的定义在 `src/shared/constants.js` 中:

```js
export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]
```
这里定义了 Vue.js 所有的钩子函数名称,所以对于钩子函数,他们的合并策略都是 `mergeHook` 函数。这个函数的实现也非常有意思,用了一个多层 3 元运算符,逻辑就是如果不存在 `childVal` ,就返回 `parentVal`;否则再判断是否存在 `parentVal`,如果存在就把 `childVal` 添加到 `parentVal` 后返回新数组;否则返回 `childVal` 的数组。所以回到 `mergeOptions` 函数,一旦 `parent` 和 `child` 都定义了相同的钩子函数,那么它们会把 2 个钩子函数合并成一个数组。

关于其它属性的合并策略的定义都可以在 `src/core/util/options.js` 文件中看到,这里不一一介绍了,感兴趣的同学可以自己看。

通过执行 `mergeField` 函数,把合并后的结果保存到 `options` 对象中,最终返回它。

因此,在我们当前这个 case 下,执行完如下合并后:
```js
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)
```
`vm.$options` 的值差不多是如下这样:

```js
vm.$options = {
  components: { },
  created: [
    function created() {
      console.log('parent created')
    }
  ],
  directives: { },
  filters: { },
  _base: function Vue(options) {
    // ...
  },
  el: "#app",
  render: function (h) {
    //...
  }
}
```

## 组件场景

由于组件的构造函数是通过 `Vue.extend` 继承自 `Vue` 的,先回顾一下这个过程,代码定义在 `src/core/global-api/extend.js` 中。

```js
/**
 * Class inheritance
 */
Vue.extend = function (extendOptions: Object): Function {
  // ...
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )

  // ...
  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super's options have
  // been updated.
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

  // ...
  return Sub
}
```
我们只保留关键逻辑,这里的 `extendOptions` 对应的就是前面定义的组件对象,它会和 `Vue.options` 合并到 `Sub.opitons` 中。

接下来我们再回忆一下子组件的初始化过程,代码定义在 `src/core/vdom/create-component.js` 中:

```js
export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // ...
  return new vnode.componentOptions.Ctor(options)
}
```

这里的 `vnode.componentOptions.Ctor` 就是指向 `Vue.extend` 的返回值 `Sub`, 所以 执行 `new vnode.componentOptions.Ctor(options)` 接着执行 `this._init(options)`,因为 `options._isComponent` 为 true,那么合并 `options` 的过程走到了 ` initInternalComponent(vm, options)` 逻辑。先来看一下它的代码实现,在 `src/core/instance/init.js` 中:

```js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

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

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

`initInternalComponent` 方法首先执行 `const opts = vm.$options = Object.create(vm.constructor.options)`,这里的 `vm.constructor` 就是子组件的构造函数 `Sub`,相当于 `vm.$options = Object.create(Sub.options)`。

接着又把实例化子组件传入的子组件父 VNode 实例 `parentVnode`、子组件的父 Vue 实例 `parent` 保存到 `vm.$options` 中,另外还保留了 `parentVnode` 配置中的如 `propsData` 等其它的属性。

这么看来,`initInternalComponent` 只是做了简单一层对象赋值,并不涉及到递归、合并策略等复杂逻辑。

因此,在我们当前这个 case 下,执行完如下合并后:

```js
initInternalComponent(vm, options)
```

`vm.$options` 的值差不多是如下这样:

```js
vm.$options = {
  parent: Vue /*父Vue实例*/,
  propsData: undefined,
  _componentTag: undefined,
  _parentVnode: VNode /*父VNode实例*/,
  _renderChildren:undefined,
  __proto__: {
    components: { },
    directives: { },
    filters: { },
    _base: function Vue(options) {
        //...
    },
    _Ctor: {},
    created: [
      function created() {
        console.log('parent created')
      }, function created() {
        console.log('child created')
      }
    ],
    mounted: [
      function mounted() {
        console.log('child mounted')
      }
    ],
    data() {
       return {
         msg: 'Hello Vue'
       }
    },
    template: '<div>{{msg}}</div>'
  }
}
```

## 总结

那么至此,Vue 初始化阶段对于 `options` 的合并过程就介绍完了,我们需要知道对于 `options` 的合并有 2 种方式,子组件初始化过程通过 `initInternalComponent` 方式要比外部初始化 Vue 通过 `mergeOptions` 的过程要快,合并完的结果保留在 `vm.$options` 中。

纵观一些库、框架的设计几乎都是类似的,自身定义了一些默认配置,同时又可以在初始化阶段传入一些定义配置,然后去 merge 默认配置,来达到定制化不同需求的目的。只不过在 Vue 的场景下,会对 merge 的过程做一些精细化控制,虽然我们在开发自己的 JSSDK 的时候并没有 Vue 这么复杂,但这个设计思想是值得我们借鉴的。




================================================
FILE: docs/v2/components/patch.md
================================================
# patch

通过前一章的分析我们知道,当我们通过 `createComponent` 创建了组件 VNode,接下来会走到 `vm._update`,执行 `vm.__patch__` 去把 VNode 转换成真正的 DOM 节点。这个过程我们在前一章已经分析过了,但是针对一个普通的 VNode 节点,接下来我们来看看组件的 VNode 会有哪些不一样的地方。

patch 的过程会调用 `createElm` 创建元素节点,回顾一下 `createElm` 的实现,它的定义在 `src/core/vdom/patch.js` 中:

```js
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
  // ...
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }
  // ...
}
```

## createComponent

我们删掉多余的代码,只保留关键的逻辑,这里会判断 `createComponent(vnode, insertedVnodeQueue, parentElm, refElm)` 的返回值,如果为 `true` 则直接结束,那么接下来看一下 `createComponent` 方法的实现:

```js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
```

`createComponent` 函数中,首先对 `vnode.data` 做了一些判断:

```js
let i = vnode.data
if (isDef(i)) {
  // ...
  if (isDef(i = i.hook) && isDef(i = i.init)) {
    i(vnode, false /* hydrating */)
    // ...
  }
  // ..
}
```

如果 `vnode` 是一个组件 VNode,那么条件会满足,并且得到 `i` 就是 `init` 钩子函数,回顾上节我们在创建组件 VNode 的时候合并钩子函数中就包含 `init` 钩子函数,定义在 `src/core/vdom/create-component.js` 中:

```js
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
  if (
    vnode.componentInstance &&
    !vnode.componentInstance._isDestroyed &&
    vnode.data.keepAlive
  ) {
    // kept-alive components, treat as a patch
    const mountedNode: any = vnode // work around flow
    componentVNodeHooks.prepatch(mountedNode, mountedNode)
  } else {
    const child = vnode.componentInstance = createComponentInstanceForVnode(
      vnode,
      activeInstance
    )
    child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  }
},
```

`init` 钩子函数执行也很简单,我们先不考虑 `keepAlive` 的情况,它是通过 `createComponentInstanceForVnode` 创建一个 Vue 的实例,然后调用 `$mount` 方法挂载子组件,
先来看一下 `createComponentInstanceForVnode` 的实现:

```js
export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnode.componentOptions.Ctor(options)
}
```

`createComponentInstanceForVnode` 函数构造的一个内部组件的参数,然后执行 `new vnode.componentOptions.Ctor(options)`。这里的 `vnode.componentOptions.Ctor` 对应的就是子组件的构造函数,我们上一节分析了它实际上是继承于 Vue 的一个构造器 `Sub`,相当于 `new Sub(options)` 这里有几个关键参数要注意几个点,`_isComponent` 为 `true` 表示它是一个组件,`parent` 表示当前激活的组件实例(注意,这里比较有意思的是如何拿到组件实例,后面会介绍。

所以子组件的实例化实际上就是在这个时机执行的,并且它会执行实例的 `_init` 方法,这个过程有一些和之前不同的地方需要挑出来说,代码在 `src/core/instance/init.js` 中:

```js
Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  // 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
    )
  }
  // ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  } 
}
```

这里首先是合并 `options` 的过程有变化,`_isComponent` 为 true,所以走到了 `initInternalComponent` 过程,这个函数的实现也简单看一下:

```js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

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

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}
```
这个过程我们重点记住以下几个点即可:`opts.parent = options.parent`、`opts._parentVnode = parentVnode`,它们是把之前我们通过 `createComponentInstanceForVnode` 函数传入的几个参数合并到内部的选项 `$options` 里了。

再来看一下 `_init` 函数最后执行的代码:

```js
if (vm.$options.el) {
   vm.$mount(vm.$options.el)
}
```
由于组件初始化的时候是不传 el 的,因此组件是自己接管了 `$mount` 的过程,这个过程的主要流程在上一章介绍过了,回到组件 `init` 的过程,`componentVNodeHooks` 的 `init` 钩子函数,在完成实例化的 `_init` 后,接着会执行 `child.$mount(hydrating ? vnode.elm : undefined, hydrating)` 。这里 `hydrating` 为 true 一般是服务端渲染的情况,我们只考虑客户端渲染,所以这里 `$mount` 相当于执行 `child.$mount(undefined, false)`,它最终会调用 `mountComponent` 方法,进而执行 `vm._render()` 方法:

```js
Vue.prototype._render = function (): VNode {
  const vm: Component = this
  const { render, _parentVnode } = vm.$options

  
  // set parent vnode. this allows render functions to have access
  // to the data on the placeholder node.
  vm.$vnode = _parentVnode
  // render self
  let vnode
  try {
    vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e) {
    // ...
  }
  // set parent
  vnode.parent = _parentVnode
  return vnode
}
```

我们只保留关键部分的代码,这里的 `_parentVnode` 就是当前组件的父 VNode,而 `render` 函数生成的 `vnode` 当前组件的渲染 `vnode`,`vnode` 的 `parent` 指向了 `_parentVnode`,也就是 `vm.$vnode`,它们是一种父子的关系。

我们知道在执行完 `vm._render` 生成 VNode 后,接下来就要执行 `vm._update` 去渲染 VNode 了。来看一下组件渲染的过程中有哪些需要注意的,`vm._update` 的定义在 `src/core/instance/lifecycle.js` 中:

```js
export let activeInstance: any = null
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  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.
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  activeInstance = prevActiveInstance
  // update __vue__ reference
  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` 过程中有几个关键的代码,首先 `vm._vnode = vnode` 的逻辑,这个 `vnode` 是通过 `vm._render()` 返回的组件渲染 VNode,`vm._vnode` 和 `vm.$vnode` 的关系就是一种父子关系,用代码表达就是 `vm._vnode.parent === vm.$vnode`。还有一段比较有意思的代码:

```js
export let activeInstance: any = null
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    // ...
    const prevActiveInstance = activeInstance
    activeInstance = vm
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
    // ...
}

```
这个 `activeInstance` 作用就是保持当前上下文的 Vue 实例,它是在 `lifecycle` 模块的全局变量,定义是 `export let activeInstance: any = null`,并且在之前我们调用 `createComponentInstanceForVnode` 方法的时候从 `lifecycle` 模块获取,并且作为参数传入的。因为实际上 JavaScript 是一个单线程,Vue 整个初始化是一个深度遍历的过程,在实例化子组件的过程中,它需要知道当前上下文的 Vue 实例是什么,并把它作为子组件的父 Vue 实例。之前我们提到过对子组件的实例化过程先会调用 `initInternalComponent(vm, options)` 合并 `options`,把 `parent` 存储在 `vm.$options` 中,在 `$mount` 之前会调用 `initLifecycle(vm)` 方法:

```js
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  // ...
}
```
可以看到 `vm.$parent` 就是用来保留当前 `vm` 的父实例,并且通过 `parent.$children.push(vm)` 来把当前的 `vm` 存储到父实例的 `$children` 中。

在 `vm._update` 的过程中,把当前的 `vm` 赋值给 `activeInstance`,同时通过 `const prevActiveInstance = activeInstance` 用 `prevActiveInstance` 保留上一次的 `activeInstance`。实际上,`prevActiveInstance` 和当前的 `vm` 是一个父子关系,当一个 `vm` 实例完成它的所有子树的 patch 或者 update 过程后,`activeInstance` 会回到它的父实例,这样就完美地保证了 `createComponentInstanceForVnode` 整个深度遍历过程中,我们在实例化子组件的时候能传入当前子组件的父 Vue 实例,并在 `_init` 的过程中,通过 `vm.$parent` 把这个父子关系保留。

那么回到 `_update`,最后就是调用 `__patch__` 渲染 VNode 了。

```js
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
 
function patch (oldVnode, vnode, hydrating, removeOnly) {
  // ...
  let isInitialPatch = false
  const insertedVnodeQueue = []

  if (isUndef(oldVnode)) {
    // empty mount (likely as component), create new root element
    isInitialPatch = true
    createElm(vnode, insertedVnodeQueue)
  } else {
    // ...
  }
  // ...
}

```

这里又回到了本节开始的过程,之前分析过负责渲染成 DOM 的函数是 `createElm`,注意这里我们只传了 2 个参数,所以对应的 `parentElm` 是 `undefined`。我们再来看看它的定义:

```js
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
  // ...
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }

  const data = vnode.data
  const children = vnode.children
  const tag = vnode.tag
  if (isDef(tag)) {
    // ...

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

    /* istanbul ignore if */
    if (__WEEX__) {
      // ...
    } else {
      createChildren(vnode, children, insertedVnodeQueue)
      if (isDef(data)) {
        invokeCreateHooks(vnode, insertedVnodeQueue)
      }
      insert(parentElm, vnode.elm, refElm)
    }
    
    // ...
  } else if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  } else {
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  }
}
```
注意,这里我们传入的 `vnode` 是组件渲染的 `vnode`,也就是我们之前说的 `vm._vnode`,如果组件的根节点是个普通元素,那么 `vm._vnode` 也是普通的 `vnode`,这里 `createComponent(vnode, insertedVnodeQueue, parentElm, refElm)` 的返回值是 false。接下来的过程就和我们上一章一样了,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 `createElm`,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复本节开始的过程,这样通过一个递归的方式就可以完整地构建了整个组件树。

由于我们这个时候传入的 `parentElm` 是空,所以对组件的插入,在 `createComponent` 有这么一段逻辑:

```js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    // ....
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)
    }
    // ...
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
```

在完成组件的整个 `patch` 过程后,最后执行 `insert(parentElm, vnode.elm, refElm)` 完成组件的 DOM 插入,如果组件 `patch` 过程中又创建了子组件,那么DOM 的插入顺序是先子后父。

## 总结

那么到此,一个组件的 VNode 是如何创建、初始化、渲染的过程也就介绍完毕了。在对组件化的实现有一个大概了解后,接下来我们来介绍一下这其中的一些细节。我们知道编写一个组件实际上是编写一个 JavaScript 对象,对象的描述就是各种配置,之前我们提到在 `_init` 的最初阶段执行的就是 `merge options` 的逻辑,那么下一节我们从源码角度来分析合并配置的过程。







 
 


================================================
FILE: docs/v2/data-driven/create-element.md
================================================
# createElement

Vue.js 利用 createElement 方法创建 VNode,它定义在 `src/core/vdom/create-element.js` 中:

```js
// 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 | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}
```

`createElement` 方法实际上是对 `_createElement` 方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 `_createElement`:

```js
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  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()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // 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
    ns = (context.$vnode && context.$vnode.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
      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
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}
```

`_createElement` 方法有 5 个参数,`context` 表示 VNode 的上下文环境,它是 `Component` 类型;`tag` 表示标签,它可以是一个字符串,也可以是一个 `Component`;`data` 表示 VNode 的数据,它是一个 `VNodeData` 类型,可以在 `flow/vnode.js` 中找到它的定义,这里先不展开说;`children` 表示当前 VNode 的子节点,它是任意类型的,它接下来需要被规范为标准的 VNode 数组;`normalizationType` 表示子节点规范的类型,类型不同规范的方法也就不一样,它主要是参考 `render` 函数是编译生成的还是用户手写的。

`createElement` 函数的流程略微有点多,我们接下来主要分析 2 个重点的流程 —— `children` 的规范化以及 VNode 的创建。

## children 的规范化

由于 Virtual DOM 实际上是一个树状结构,每一个 VNode 可能会有若干个子节点,这些子节点应该也是 VNode 的类型。`_createElement` 接收的第 4 个参数 children 是任意类型的,因此我们需要把它们规范成 VNode 类型。

这里根据 `normalizationType` 的不同,调用了 `normalizeChildren(children)` 和 `simpleNormalizeChildren(children)` 方法,它们的定义都在 `src/core/vdom/helpers/normalzie-children.js` 中:

```js
// The template compiler attempts to minimize the need for normalization by
// statically analyzing the template at compile time.
//
// For plain HTML markup, normalization can be completely skipped because the
// generated render function is guaranteed to return Array<VNode>. There are
// two cases where extra normalization is needed:

// 1. When the children contains components - because a functional component
// may return an Array instead of a single root. In this case, just a simple
// normalization is needed - if any child is an Array, we flatten the whole
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
// because functional components already normalize their own children.
export function simpleNormalizeChildren (children: any) {
  for (let i = 0; i < children.length; i++) {
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children)
    }
  }
  return children
}

// 2. When the children contains constructs that always generated nested Arrays,
// e.g. <template>, <slot>, v-for, or when the children is provided by user
// with hand-written render functions / JSX. In such cases a full normalization
// is needed to cater to all possible types of children values.
export function normalizeChildren (children: any): ?Array<VNode> {
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : Array.isArray(children)
      ? normalizeArrayChildren(children)
      : undefined
}
```

`simpleNormalizeChildren` 方法调用场景是 `render` 函数是编译生成的。理论上编译生成的 `children` 都已经是 VNode 类型的,但这里有一个例外,就是 `functional component` 函数式组件返回的是一个数组而不是一个根节点,所以会通过 `Array.prototype.concat` 方法把整个 `children` 数组打平,让它的深度只有一层。

`normalizeChildren` 方法的调用场景有 2 种,一个场景是 `render` 函数是用户手写的,当 `children` 只有一个节点的时候,Vue.js 从接口层面允许用户把 `children` 写成基础类型用来创建单个简单的文本节点,这种情况会调用 `createTextVNode` 创建一个文本节点的 VNode;另一个场景是当编译 `slot`、`v-for` 的时候会产生嵌套数组的情况,会调用 `normalizeArrayChildren` 方法,接下来看一下它的实现:

```js
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
  const res = []
  let i, c, lastIndex, last
  for (i = 0; i < children.length; i++) {
    c = children[i]
    if (isUndef(c) || typeof c === 'boolean') continue
    lastIndex = res.length - 1
    last = res[lastIndex]
    //  nested
    if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
        // merge adjacent text nodes
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
          c.shift()
        }
        res.push.apply(res, c)
      }
    } else if (isPrimitive(c)) {
      if (isTextNode(last)) {
        // merge adjacent text nodes
        // this is necessary for SSR hydration because text nodes are
        // essentially merged when rendered to HTML strings
        res[lastIndex] = createTextVNode(last.text + c)
      } else if (c !== '') {
        // convert primitive to vnode
        res.push(createTextVNode(c))
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        // merge adjacent text nodes
        res[lastIndex] = createTextVNode(last.text + c.text)
      } else {
        // default key for nested array children (likely generated by v-for)
        if (isTrue(children._isVList) &&
          isDef(c.tag) &&
          isUndef(c.key) &&
          isDef(nestedIndex)) {
          c.key = `__vlist${nestedIndex}_${i}__`
        }
        res.push(c)
      }
    }
  }
  return res
}

```

`normalizeArrayChildren` 接收 2 个参数,`children` 表示要规范的子节点,`nestedIndex` 表示嵌套的索引,因为单个 `child` 可能是一个数组类型。 `normalizeArrayChildren` 主要的逻辑就是遍历 `children`,获得单个节点 `c`,然后对 `c` 的类型判断,如果是一个数组类型,则递归调用 `normalizeArrayChildren`; 如果是基础类型,则通过 `createTextVNode` 方法转换成 VNode 类型;否则就已经是 VNode 类型了,如果 `children` 是一个列表并且列表还存在嵌套的情况,则根据 `nestedIndex` 去更新它的 key。这里需要注意一点,在遍历的过程中,对这 3 种情况都做了如下处理:如果存在两个连续的 `text` 节点,会把它们合并成一个 `text` 节点。

经过对 `children` 的规范化,`children` 变成了一个类型为 VNode 的 Array。

## VNode 的创建

回到 `createElement` 函数,规范化 `children` 后,接下来会去创建一个 VNode 的实例:

```js
let vnode, ns
if (typeof tag === 'string') {
  let Ctor
  ns = (context.$vnode && context.$vnode.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
    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
  vnode = createComponent(tag, data, context, children)
}
```

这里先对 `tag` 做判断,如果是 `string` 类型,则接着判断如果是内置的一些节点,则直接创建一个普通 VNode,如果是为已注册的组件名,则通过 `createComponent` 创建一个组件类型的 VNode,否则创建一个未知的标签的 VNode。 如果 `tag` 是一个 `Component` 类型,则直接调用 `createComponent` 创建一个组件类型的 VNode 节点。对于 `createComponent` 创建组件类型的 VNode 的过程,我们之后会去介绍,本质上它还是返回了一个 VNode。

## 总结

那么至此,我们大致了解了 `createElement` 创建 VNode 的过程,每个 VNode 有 `children`,`children` 每个元素也是一个 VNode,这样就形成了一个 VNode Tree,它很好的描述了我们的 DOM Tree。

回到 `mountComponent` 函数的过程,我们已经知道 `vm._render` 是如何创建了一个 VNode,接下来就是要把这个 VNode 渲染成一个真实的 DOM 并渲染出来,这个过程是通过 `vm._update` 完成的,接下来分析一下这个过程。


================================================
FILE: docs/v2/data-driven/index.md
================================================
# 数据驱动

Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。

在 Vue.js 中我们可以采用简洁的模板语法来声明式的将数据渲染为 DOM:

```html
<div id="app">
  {{ message }}
</div>
```

```js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
```

最终它会在页面上渲染出 `Hello Vue`。接下来,我们会从源码角度来分析 Vue 是如何实现的,分析过程会以主线代码为主,重要的分支逻辑会放在之后单独分析。数据驱动还有一部分是数据更新驱动视图变化,这一块内容我们也会在之后的章节分析,这一章我们的目标是弄清楚模板和数据如何渲染成最终的 DOM。





================================================
FILE: docs/v2/data-driven/mounted.md
================================================
# Vue 实例挂载的实现

Vue 中我们是通过 `$mount` 实例方法去挂载 `vm` 的,`$mount` 方法在多个文件中都有定义,如 `src/platform/web/entry-runtime-with-compiler.js`、`src/platform/web/runtime/index.js`、`src/platform/weex/runtime/index.js`。因为 `$mount` 这个方法的实现是和平台、构建方式都相关的。接下来我们重点分析带 `compiler` 版本的 `$mount` 实现,因为抛开 webpack 的 vue-loader,我们在纯前端浏览器环境分析 Vue 的工作原理,有助于我们对原理理解的深入。

`compiler` 版本的 `$mount` 实现非常有意思,先来看一下 `src/platform/web/entry-runtime-with-compiler.js` 文件中定义:

```js
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
  if (!options.render) {
    let template = options.template
    if (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 = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}
```
这段代码首先缓存了原型上的 `$mount` 方法,再重新定义该方法,我们先来分析这段代码。首先,它对 `el` 做了限制,Vue 不能挂载在 `body`、`html` 这样的根节点上。接下来的是很关键的逻辑 —— 如果没有定义 `render` 方法,则会把 `el` 或者 `template` 字符串转换成 `render` 方法。这里我们要牢记,在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 `render` 方法,无论我们是用单文件 .vue 方式开发组件,还是写了 `el` 或者 `template` 属性,最终都会转换成 `render` 方法,那么这个过程是 Vue 的一个“在线编译”的过程,它是调用 `compileToFunctions` 方法实现的,编译过程我们之后会介绍。最后,调用原先原型上的 `$mount` 方法挂载。

原先原型上的 `$mount` 方法在 `src/platform/web/runtime/index.js` 中定义,之所以这么设计完全是为了复用,因为它是可以被 `runtime only` 版本的 Vue 直接使用的。

```js
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
```

`$mount` 方法支持传入 2 个参数,第一个是 `el`,它表示挂载的元素,可以是字符串,也可以是 DOM 对象,如果是字符串在浏览器环境下会调用 `query` 方法转换成 DOM 对象的。第二个参数是和服务端渲染相关,在浏览器环境下我们不需要传第二个参数。

`$mount` 方法实际上会去调用 `mountComponent` 方法,这个方法定义在 `src/core/instance/lifecycle.js` 文件中:

```js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
```
从上面的代码可以看到,`mountComponent` 核心就是先实例化一个渲染`Watcher`,在它的回调函数中会调用 `updateComponent` 方法,在此方法中调用 `vm._render` 方法先生成虚拟 Node,最终调用 `vm._update` 更新 DOM。

`Watcher` 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数,这块儿我们会在之后的章节中介绍。

函数最后判断为根节点的时候设置 `vm._isMounted` 为 `true`, 表示这个实例已经挂载了,同时执行 `mounted` 钩子函数。 这里注意 `vm.$vnode` 表示 Vue 实例的父虚拟 Node,所以它为 `Null` 则表示当前是根 Vue 的实例。


## 总结

`mountComponent` 方法的逻辑也是非常清晰的,它会完成整个渲染工作,接下来我们要重点分析其中的细节,也就是最核心的 2 个方法:`vm._render` 和 `vm._update`。


================================================
FILE: docs/v2/data-driven/new-vue.md
================================================
# new Vue 发生了什么
 
 从入口代码开始分析,我们先来分析 `new Vue` 背后发生了哪些事情。我们都知道,`new` 关键字在 Javascript 语言中代表实例化是一个对象,而 `Vue` 实际上是一个类,类在 Javascript 中是用 Function 来实现的,来看一下源码,在`src/core/instance/index.js` 中。
 
```js
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` 只能通过 new 关键字初始化,然后会调用 `this._init` 方法, 该方法在 `src/core/instance/init.js` 中定义。

```js
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-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
  }

  // a flag to avoid this being observed
  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)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')

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

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}
```

Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。

## 总结

Vue 的初始化逻辑写的非常清楚,把不同的功能逻辑拆成一些单独的函数执行,让主线逻辑一目了然,这样的编程思想是非常值得借鉴和学习的。

由于我们这一章的目标是弄清楚模板和数据如何渲染成最终的 DOM,所以各种初始化逻辑我们先不看。在初始化的最后,检测到如果有 `el` 属性,则调用 `vm.$mount` 方法挂载 `vm`,挂载的目标就是把模板渲染成最终的 DOM,那么接下来我们来分析 Vue 的挂载过程。

================================================
FILE: docs/v2/data-driven/render.md
================================================
# render

Vue 的 `_render` 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。它的定义在 `src/core/instance/render.js` 文件中:

```js
Vue.prototype._render = function (): VNode {
  const vm: Component = this
  const { render, _parentVnode } = vm.$options

  // reset _rendered flag on slots for duplicate slot check
  if (process.env.NODE_ENV !== 'production') {
    for (const key in vm.$slots) {
      // $flow-disable-line
      vm.$slots[key]._rendered = false
    }
  }

  if (_parentVnode) {
    vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
  }

  // set parent vnode. this allows render functions to have access
  // to the data on the placeholder node.
  vm.$vnode = _parentVnode
  // render self
  let vnode
  try {
    vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e) {
    handleError(e, vm, `render`)
    // return error render result,
    // or previous vnode to prevent render error causing blank component
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      if (vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } else {
      vnode = vm._vnode
    }
  }
  // return empty vnode in case the render function errored out
  if (!(vnode instanceof VNode)) {
    if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
      warn(
        'Multiple root nodes returned from render function. Render function ' +
        'should return a single root node.',
        vm
      )
    }
    vnode = createEmptyVNode()
  }
  // set parent
  vnode.parent = _parentVnode
  return vnode
}
```

这段代码最关键的是 `render` 方法的调用,我们在平时的开发工作中手写 `render` 方法的场景比较少,而写的比较多的是 `template` 模板,在之前的 `mounted` 方法的实现中,会把 `template` 编译成 `render` 方法,但这个编译过程是非常复杂的,我们不打算在这里展开讲,之后会专门花一个章节来分析 Vue 的编译过程。 

在 Vue 的官方文档中介绍了 `render` 函数的第一个参数是 `createElement`,那么结合之前的例子:

```html
<div id="app">
  {{ message }}
</div>
```

相当于我们编写如下 `render` 函数:

```js
render: function (createElement) {
  return createElement('div', {
     attrs: {
        id: 'app'
      },
  }, this.message)
}
```

再回到 `_render` 函数中的 `render` 方法的调用:

```js
vnode = render.call(vm._renderProxy, vm.$createElement)
```

可以看到,`render` 函数中的 `createElement` 方法就是 `vm.$createElement` 方法:

```js
export function initRender (vm: Component) {
  // ...
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}

```

实际上,`vm.$createElement` 方法定义是在执行 `initRender` 方法的时候,可以看到除了 `vm.$createElement` 方法,还有一个 `vm._c` 方法,它是被模板编译成的 `render` 函数使用,而 `vm.$createElement` 是用户手写 `render` 方法使用的, 这俩个方法支持的参数相同,并且内部都调用了 `createElement` 方法。


## 总结

`vm._render` 最终是通过执行 `createElement` 方法并返回的是 `vnode`,它是一个虚拟 Node。Vue 2.0 相比 Vue 1.0 最大的升级就是利用了 Virtual DOM。因此在分析 `createElement` 的实现前,我们先了解一下 Virtual DOM 的概念。

================================================
FILE: docs/v2/data-driven/update.md
================================================
# update

Vue 的 `_update` 是实例的一个私有方法,它被调用的时机有 2 个,一个是首次渲染,一个是数据更新的时候;由于我们这一章节只分析首次渲染部分,数据更新部分会在之后分析响应式原理的时候涉及。`_update` 方法的作用是把 VNode 渲染成真实的 DOM,它的定义在 `src/core/instance/lifecycle.js` 中:

```js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  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.
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  activeInstance = prevActiveInstance
  // update __vue__ reference
  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` 的核心就是调用 `vm.__patch__` 方法,这个方法实际上在不同的平台,比如 web 和 weex 上的定义是不一样的,因此在 web 平台中它的定义在 `src/platforms/web/runtime/index.js` 中:

```js
Vue.prototype.__patch__ = inBrowser ? patch : noop
```

可以看到,甚至在 web 平台上,是否是服务端渲染也会对这个方法产生影响。因为在服务端渲染中,没有真实的浏览器 DOM 环境,所以不需要把 VNode 最终转换成 DOM,因此是一个空函数,而在浏览器端渲染中,它指向了 `patch` 方法,它的定义在 `src/platforms/web/runtime/patch.js`中:

```js
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'

// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)

export const patch: Function = createPatchFunction({ nodeOps, modules })
```

该方法的定义是调用 `createPatchFunction` 方法的返回值,这里传入了一个对象,包含 `nodeOps` 参数和 `modules` 参数。其中,`nodeOps` 封装了一系列 DOM 操作的方法,`modules` 定义了一些模块的钩子函数的实现,我们这里先不详细介绍,来看一下 `createPatchFunction` 的实现,它定义在 `src/core/vdom/patch.js` 中:

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

export function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend

  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]])
      }
    }
  }

  // ...

  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    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
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      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)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              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
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        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)
        )

        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes(parentElm, [oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}
```

`createPatchFunction` 内部定义了一系列的辅助方法,最终返回了一个 `patch` 方法,这个方法就赋值给了 `vm._update` 函数里调用的 `vm.__patch__`。

在介绍 `patch` 的方法实现之前,我们可以思考一下为何 Vue.js 源码绕了这么一大圈,把相关代码分散到各个目录。因为前面介绍过,`patch` 是平台相关的,在 Web 和 Weex 环境,它们把虚拟 DOM 映射到 “平台 DOM” 的方法是不同的,并且对 “DOM” 包括的属性模块创建和更新也不尽相同。因此每个平台都有各自的 `nodeOps` 和 `modules`,它们的代码需要托管在 `src/platforms` 这个大目录下。

而不同平台的 `patch` 的主要逻辑部分是相同的,所以这部分公共的部分托管在 `core` 这个大目录下。差异化部分只需要通过参数来区别,这里用到了一个函数柯里化的技巧,通过 `createPatchFunction` 把差异化参数提前固化,这样不用每次调用 `patch` 的时候都传递 `nodeOps` 和 `modules` 了,这种编程技巧也非常值得学习。

在这里,`nodeOps` 表示对 “平台 DOM” 的一些操作方法,`modules` 表示平台的一些模块,它们会在整个 `patch` 过程的不同阶段执行相应的钩子函数。这些代码的具体实现会在之后的章节介绍。

回到 `patch` 方法本身,它接收 4个参数,`oldVnode` 表示旧的 VNode 节点,它也可以不存在或者是一个 DOM 对象;`vnode` 表示执行 `_render` 后返回的 VNode 的节点;`hydrating` 表示是否是服务端渲染;`removeOnly` 是给 `transition-group` 用的,之后会介绍。

`patch` 的逻辑看上去相对复杂,因为它有着非常多的分支逻辑,为了方便理解,我们并不会在这里介绍所有的逻辑,仅会针对我们之前的例子分析它的执行逻辑。之后我们对其它场景做源码分析的时候会再次回顾 `patch` 方法。

先来回顾我们的例子:

```js
var app = new Vue({
  el: '#app',
  render: function (createElement) {
    return createElement('div', {
      attrs: {
        id: 'app'
      },
    }, this.message)
  },
  data: {
    message: 'Hello Vue!'
  }
})
```

然后我们在 `vm._update` 的方法里是这么调用 `patch` 方法的:

```js
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
```

结合我们的例子,我们的场景是首次渲染,所以在执行 `patch` 函数的时候,传入的 `vm.$el` 对应的是例子中 id 为 `app` 的 DOM 对象,这个也就是我们在 index.html 模板中写的 `<div id="app">`, `vm.$el` 的赋值是在之前 `mountComponent` 函数做的,`vnode` 对应的是调用 `render` 函数的返回值,`hydrating` 在非服务端渲染情况下为 false,`removeOnly` 为 false。

确定了这些入参后,我们回到 `patch` 函数的执行过程,看几个关键步骤。

```js
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)) {
      oldVnode.removeAttribute(SSR_ATTR)
      hydrating = true
    }
    if (isTrue(hydrating)) {
      if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
        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
    oldVnode = emptyNodeAt(oldVnode)
  }

  // replacing existing element
  const oldElm = oldVnode.elm
  const parentElm = nodeOps.parentNode(oldElm)

  // create new node
  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)
  )
}
```

由于我们传入的 `oldVnode` 实际上是一个 DOM container,所以 `isRealElement` 为 true,接下来又通过 `emptyNodeAt` 方法把 `oldVnode` 转换成 `VNode` 对象,然后再调用 `createElm` 方法,这个方法在这里非常重要,来看一下它的实现:

```js
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // This vnode was used in a previous render!
    // now it's used as a new node, overwriting its elm would cause
    // potential patch errors down the road when it's used as an insertion
    // reference node. Instead, we clone the node on-demand before creating
    // associated DOM element for it.
    vnode = ownerArray[index] = cloneVNode(vnode)
  }

  vnode.isRootInsert = !nested // for transition enter check
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }

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

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

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

    if (process.env.NODE_ENV !== 'production' && data && data.pre) {
      creatingElmInVPre--
    }
  } else if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  } else {
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  }
}
```

`createElm` 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中。 我们来看一下它的一些关键逻辑,`createComponent` 方法目的是尝试创建子组件,这个逻辑在之后组件的章节会详细介绍,在当前这个 case 下它的返回值为 false;接下来判断 `vnode` 是否包含 tag,如果包含,先简单对 tag 的合法性在非生产环境下做校验,看是否是一个合法标签;然后再去调用平台 DOM 的操作去创建一个占位符元素。

```js
vnode.elm = vnode.ns
  ? nodeOps.createElementNS(vnode.ns, tag)
  : nodeOps.createElement(tag, vnode)
```

接下来调用 `createChildren` 方法去创建子元素:

```js
createChildren(vnode, children, insertedVnodeQueue)

function createChildren (vnode, children, insertedVnodeQueue) {
  if (Array.isArray(children)) {
    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(children)
    }
    for (let i = 0; i < children.length; ++i) {
      createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
    }
  } else if (isPrimitive(vnode.text)) {
    nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
  }
}
```

`createChildren` 的逻辑很简单,实际上是遍历子虚拟节点,递归调用 `createElm`,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 `vnode.elm` 作为父容器的 DOM 节点占位符传入。

接着再调用 `invokeCreateHooks` 方法执行所有的 create 的钩子并把 `vnode` push 到 `insertedVnodeQueue` 中。

```js
 if (isDef(data)) {
  invokeCreateHooks(vnode, insertedVnodeQueue)
}

function invokeCreateHooks (vnode, insertedVnodeQueue) {
  for (let i = 0; i < cbs.create.length; ++i) {
    cbs.create[i](emptyNode, vnode)
  }
  i = vnode.data.hook // Reuse variable
  if (isDef(i)) {
    if (isDef(i.create)) i.create(emptyNode, vnode)
    if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
  }
}
```

最后调用 `insert` 方法把 `DOM` 插入到父节点中,因为是递归调用,子元素会优先调用 `insert`,所以整个 `vnode` 树节点的插入顺序是先子后父。来看一下 `insert` 方法,它的定义在 `src/core/vdom/patch.js` 上。

```js
insert(parentElm, vnode.elm, refElm)

function insert (parent, elm, ref) {
  if (isDef(parent)) {
    if (isDef(ref)) {
      if (ref.parentNode === parent) {
        nodeOps.insertBefore(parent, elm, ref)
      }
    } else {
      nodeOps.appendChild(parent, elm)
    }
  }
}
```
`insert` 逻辑很简单,调用一些 `nodeOps` 把子节点插入到父节点中,这些辅助方法定义在 `src/platforms/web/runtime/node-ops.js` 中:

```js
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
  parentNode.insertBefore(newNode, referenceNode)
}

export function appendChild (node: Node, child: Node) {
  node.appendChild(child)
}
```

其实就是调用原生 DOM 的 API 进行 DOM 操作,看到这里,很多同学恍然大悟,原来 Vue 是这样动态创建的 DOM。

在 `createElm` 过程中,如果 `vnode` 节点不包含 `tag`,则它有可能是一个注释或者纯文本节点,可以直接插入到父元素中。在我们这个例子中,最内层就是一个文本 `vnode`,它的 `text` 值取的就是之前的 `this.message` 的值 `Hello Vue!`。

再回到 `patch` 方法,首次渲染我们调用了 `createElm` 方法,这里传入的 `parentElm` 是 `oldVnode.elm` 的父元素,在我们的例子是 id 为 `#app` div 的父元素,也就是 Body;实际上整个过程就是递归创建了一个完整的 DOM 树并插入到 Body 上。

最后,我们根据之前递归 `createElm` 生成的 `vnode` 插入顺序队列,执行相关的 `insert` 钩子函数,这部分内容我们之后会详细介绍。

## 总结

那么至此我们从主线上把模板和数据如何渲染成最终的 DOM 的过程分析完毕了,我们可以通过下图更直观地看到从初始化 Vue 到最终渲染的整个过程。 

<img :src="$withBase('/assets/new-vue.png')"/>

我们这里只是分析了最简单和最基础的场景,在实际项目中,我们是把页面拆成很多组件的,Vue 另一个核心思想就是组件化。那么下一章我们就来分析 Vue 的组件化过程。



================================================
FILE: docs/v2/data-driven/virtual-dom.md
================================================
# Virtual DOM

Virtual DOM 这个概念相信大部分人都不会陌生,它产生的前提是浏览器中的 DOM 是很“昂贵"的,为了更直观的感受,我们可以简单的把一个简单的 div 元素的属性都打印出来,如图所示:

<img :src="$withBase('/assets/dom.png')">

可以看到,真正的 DOM 元素是非常庞大的,因为浏览器的标准就把 DOM 设计的非常复杂。当我们频繁的去做 DOM 更新,会产生一定的性能问题。

而 Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 `VNode` 这么一个 Class 去描述,它是定义在 `src/core/vdom/vnode.js` 中的。

```js
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
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  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?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  fnScopeId: ?string; // functional scope id support

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

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

可以看到 Vue.js 中的 Virtual DOM 的定义还是略微复杂一些的,因为它这里包含了很多 Vue.js 的特性。这里千万不要被这些茫茫多的属性吓到,实际上 Vue.js 中 Virtual DOM 是借鉴了一个开源库 [snabbdom](https://github.com/snabbdom/snabbdom) 的实现,然后加入了一些 Vue.js 特色的东西。我建议大家如果想深入了解 Vue.js 的 Virtual DOM 前不妨先阅读这个库的源码,因为它更加简单和纯粹。

## 总结

其实 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。

Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。那么在 Vue.js 中,VNode 的 create 是通过之前提到的 `createElement` 方法创建的,我们接下来分析这部分的实现。


================================================
FILE: docs/v2/extend/event.md
================================================
# event

我们平时开发工作中,处理组件间的通讯,原生的交互,都离不开事件。对于一个组件元素,我们不仅仅可以绑定原生的 DOM 事件,还可以绑定自定义事件,非常灵活和方便。那么接下来我们从源码角度来看看它的实现原理。

为了更加直观,我们通过一个例子来分析它的实现:

```js
let Child = {
  template: '<button @click="clickHandler($event)">' +
  'click me' +
  '</button>',
  methods: {
    clickHandler(e) {
      console.log('Button clicked!', e)
      this.$emit('select')
    }
  }
}

let vm = new Vue({
  el: '#app',
  template: '<div>' +
  '<child @select="selectHandler" @click.native.prevent="clickHandler"></child>' +
  '</div>',
  methods: {
    clickHandler() {
      console.log('Child clicked!')
    },
    selectHandler() {
      console.log('Child select!')
    }
  },
  components: {
    Child
  }
})
```

## 编译

先从编译阶段开始看起,在 `parse` 阶段,会执行 `processAttrs` 方法,它的定义在 `src/compiler/parser/index.js` 中:

```js
export const onRE = /^@|^v-on:/
export const dirRE = /^v-|^@|^:/
export const bindRE = /^:|^v-bind:/
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, isProp
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {   
      el.hasBindings = true
      modifiers = parseModifiers(name)
      if (modifiers) {
        name = name.replace(modifierRE, '')
      }
      if (bindRE.test(name)) {
        // ..
      } else if (onRE.test(name)) {
        name = name.replace(onRE, '')
        addHandler(el, name, value, modifiers, false, warn)
      } else {
        // ...
      }
    } else {
      // ...
    }
  }
}

function parseModifiers (name: string): Object | void {
  const match = name.match(modifierRE)
  if (match) {
    const ret = {}
    match.forEach(m => { ret[m.slice(1)] = true })
    return ret
  }
}
```

在对标签属性的处理过程中,判断如果是指令,首先通过 `parseModifiers` 解析出修饰符,然后判断如果事件的指令,则执行 `addHandler(el, name, value, modifiers, false, warn)` 方法,它的定义在 `src/compiler/helpers.js` 中:

```js
export function addHandler (
  el: ASTElement,
  name: string,
  value: string,
  modifiers: ?ASTModifiers,
  important?: boolean,
  warn?: Function
) {
  modifiers = modifiers || emptyObject
  // warn prevent and passive modifier
  /* istanbul ignore if */
  if (
    process.env.NODE_ENV !== 'production' && warn &&
    modifiers.prevent && modifiers.passive
  ) {
    warn(
      'passive and prevent can\'t be used together. ' +
      'Passive handler can\'t prevent default event.'
    )
  }

  // check capture modifier
  if (modifiers.capture) {
    delete modifiers.capture
    name = '!' + name // mark the event as captured
  }
  if (modifiers.once) {
    delete modifiers.once
    name = '~' + name // mark the event as once
  }
  /* istanbul ignore if */
  if (modifiers.passive) {
    delete modifiers.passive
    name = '&' + name // mark the event as passive
  }

  // normalize click.right and click.middle since they don't actually fire
  // this is technically browser-specific, but at least for now browsers are
  // the only target envs that have right/middle clicks.
  if (name === 'click') {
    if (modifiers.right) {
      name = 'contextmenu'
      delete modifiers.right
    } else if (modifiers.middle) {
      name = 'mouseup'
    }
  }

  let events
  if (modifiers.native) {
    delete modifiers.native
    events = el.nativeEvents || (el.nativeEvents = {})
  } else {
    events = el.events || (el.events = {})
  }

  const newHandler: any = {
    value: value.trim()
  }
  if (modifiers !== emptyObject) {
    newHandler.modifiers = modifiers
  }

  const handlers = events[name]
  /* istanbul ignore if */
  if (Array.isArray(handlers)) {
    important ? handlers.unshift(newHandler) : handlers.push(newHandler)
  } else if (handlers) {
    events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
  } else {
    events[name] = newHandler
  }

  el.plain = false
}
```

`addHandler` 函数看起来长,实际上就做了 3 件事情,首先根据 `modifier` 修饰符对事件名 `name` 做处理,接着根据 `modifier.native` 判断是一个纯原生事件还是普通事件,分别对应 `el.nativeEvents` 和 `el.events`,最后按照 `name` 对事件做归类,并把回调函数的字符串保留到对应的事件中。

在我们的例子中,父组件的 `child` 节点生成的 `el.events` 和 `el.nativeEvents` 如下:

```js
el.events = {
  select: {
    value: 'selectHandler'
  }
}

el.nativeEvents = {
  click: {
    value: 'clickHandler',
    modifiers: {
      prevent: true
    }
  }
}
```

子组件的 `button` 节点生成的 `el.events` 如下:

```js
el.events = {
  click: {
    value: 'clickHandler($event)'
  }
}
```
 
然后在 `codegen` 的阶段,会在 `genData` 函数中根据 AST 元素节点上的 `events` 和 `nativeEvents` 生成 `data` 数据,它的定义在 `src/compiler/codegen/index.js` 中:

```js
export function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'
  // ...
  if (el.events) {
    data += `${genHandlers(el.events, false, state.warn)},`
  }
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true, state.warn)},`
  }
  // ...
  return data
}
```
对于这两个属性,会调用 `genHandlers` 函数,定义在 `src/compiler/codegen/events.js` 中:

```js
export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean,
  warn: Function
): string {
  let res = isNative ? 'nativeOn:{' : 'on:{'
  for (const name in events) {
    res += `"${name}":${genHandler(name, events[name])},`
  }
  return res.slice(0, -1) + '}'
}

const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/
const simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/
function genHandler (
  name: string,
  handler: ASTElementHandler | Array<ASTElementHandler>
): string {
  if (!handler) {
    return 'function(){}'
  }

  if (Array.isArray(handler)) {
    return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`
  }

  const isMethodPath = simplePathRE.test(handler.value)
  const isFunctionExpression = fnExpRE.test(handler.value)

  if (!handler.modifiers) {
    if (isMethodPath || isFunctionExpression) {
      return handler.value
    }
    /* istanbul ignore if */
    if (__WEEX__ && handler.params) {
      return genWeexHandler(handler.params, handler.value)
    }
    return `function($event){${handler.value}}` // inline statement
  } else {
    let code = ''
    let genModifierCode = ''
    const keys = []
    for (const key in handler.modifiers) {
      if (modifierCode[key]) {
        genModifierCode += modifierCode[key]
        // left/right
        if (keyCodes[key]) {
          keys.push(key)
        }
      } else if (key === 'exact') {
        const modifiers: ASTModifiers = (handler.modifiers: any)
        genModifierCode += genGuard(
          ['ctrl', 'shift', 'alt', 'meta']
            .filter(keyModifier => !modifiers[keyModifier])
            .map(keyModifier => `$event.${keyModifier}Key`)
            .join('||')
        )
      } else {
        keys.push(key)
      }
    }
    if (keys.length) {
      code += genKeyFilter(keys)
    }
    // Make sure modifiers like prevent and stop get executed after key filtering
    if (genModifierCode) {
      code += genModifierCode
    }
    const handlerCode = isMethodPath
      ? `return ${handler.value}($event)`
      : isFunctionExpression
        ? `return (${handler.value})($event)`
        : handler.value
    /* istanbul ignore if */
    if (__WEEX__ && handler.params) {
      return genWeexHandler(handler.params, code + handlerCode)
    }
    return `function($event){${code}${handlerCode}}`
  }
}

```

`genHandlers` 方法遍历事件对象 `events`,对同一个事件名称的事件调用 `genHandler(name, events[name])` 方法,它的内容看起来多,但实际上逻辑很简单,首先先判断如果 `handler` 是一个数组,就遍历它然后递归调用 `genHandler` 方法并拼接结果,然后判断 `hanlder.value` 是一个函数的调用路径还是一个函数表达式, 接着对 `modifiers` 做判断,对于没有 `modifiers` 的情况,就根据 `handler.value` 不同情况处理,要么直接返回,要么返回一个函数包裹的表达式;对于有 `modifiers` 的情况,则对各种不同的 `modifer` 情况做不同处理,添加相应的代码串。

那么对于我们的例子而言,父组件生成的 `data` 串为:

```js
{
  on: {"select": selectHandler},
  nativeOn: {"click": function($event) {
      $event.preventDefault();
      return clickHandler($event)
    }
  }
}
```
子组件生成的 `data` 串为:

```js
{
  on: {"click": function($event) {
      clickHandler($event)
    }
  }
}
```

那么到这里,编译部分完了,接下来我们来看一下运行时部分是如何实现的。其实 Vue 的事件有 2 种,一种是原生 DOM 事件,一种是用户自定义事件,我们分别来看。

## DOM 事件

还记得我们之前在 `patch` 的时候执行各种 `module` 的钩子函数吗,当时这部分是略过的,我们之前只分析了 DOM 是如何渲染的,而 DOM 元素相关的属性、样式、事件等都是通过这些 `module` 的钩子函数完成设置的。

所有和 web 相关的 `module` 都定义在 `src/platforms/web/runtime/modules` 目录下,我们这次只关注目录下的 `events.js` 即可。

在 `patch` 过程中的创建阶段和更新阶段都会执行 `updateDOMListeners`:

```js
let target: any
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
    return
  }
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  target = vnode.elm
  normalizeEvents(on)
  updateListeners(on, oldOn, add, remove, vnode.context)
  target = undefined
}
```

首先获取 `vnode.data.on`,这就是我们之前的生成的 `data` 中对应的事件对象,`target` 是当前 `vnode` 对于的 DOM 对象,`normalizeEvents` 主要是对 `v-model` 相关的处理,我们之后分析 `v-model` 的时候会介绍,接着调用 `updateListeners(on, oldOn, add, remove, vnode.context)` 方法,它的定义在 `src/core/vdom/helpers/update-listeners.js` 中:

```js
export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur)
      }
      add(event.name, cur, event.once, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}
```

`updateListeners` 的逻辑很简单,遍历 `on` 去添加事件监听,遍历 `oldOn` 去移除事件监听,关于监听和移除事件的方法都是外部传入的,因为它既处理原生 DOM 事件的添加删除,也处理自定义事件的添加删除。

对于 `on` 的遍历,首先获得每一个事件名,然后做 `normalizeEvent` 的处理:

```js
const normalizeEvent = cached((name: string): {
  name: string,
  once: boolean,
  capture: boolean,
  passive: boolean,
  handler?: Function,
  params?: Array<any>
} => {
  const passive = name.charAt(0) === '&'
  name = passive ? name.slice(1) : name
  const once = name.charAt(0) === '~' // Prefixed last, checked first
  name = once ? name.slice(1) : name
  const capture = name.charAt(0) === '!'
  name = capture ? name.slice(1) : name
  return {
    name,
    once,
    capture,
    passive
  }
})
```

根据我们的的事件名的一些特殊标识(之前在 `addHandler` 的时候添加上的)区分出这个事件是否有 `once`、`capture`、`passive` 等修饰符。

处理完事件名后,又对事件回调函数做处理,对于第一次,满足 `isUndef(old)` 并且 `isUndef(cur.fns)`,会执行 `cur = on[name] = createFnInvoker(cur)` 方法去创建一个回调函数,然后在执行 `add(event.name, cur, event.once, event.capture, event.passive, event.params)` 完成一次事件绑定。我们先看一下 `createFnInvoker` 的实现:

```js
export function createFnInvoker (fns: Function | Array<Function>): Function {
  function invoker () {
    const fns = invoker.fns
    if (Array.isArray(fns)) {
      const cloned = fns.slice()
      for (let i = 0; i < cloned.length; i++) {
        cloned[i].apply(null, arguments)
      }
    } else {
      return fns.apply(null, arguments)
    }
  }
  invoker.fns = fns
  return invoker
}
```

这里定义了 `invoker` 方法并返回,由于一个事件可能会对应多个回调函数,所以这里做了数组的判断,多个回调函数就依次调用。注意最后的赋值逻辑, `invoker.fns = fns`,每一次执行 `invoker` 函数都是从 `invoker.fns` 里取执行的回调函数,回到 `updateListeners`,当我们第二次执行该函数的时候,判断如果 `cur !== old`,那么只需要更改 `old.fns = cur` 把之前绑定的 `involer.fns`  赋值为新的回调函数即可,并且 通过 `on[name] = old` 保留引用关系,这样就保证了事件回调只添加一次,之后仅仅去修改它的回调函数的引用。

`updateListeners` 函数的最后遍历 `oldOn` 拿到事件名称,判断如果满足 `isUndef(on[name])`,则执行 `remove(event.name, oldOn[name], event.capture)` 去移除事件回调。

了解了 `updateListeners` 的实现后,我们来看一下在原生 DOM 事件中真正添加回调和移除回调函数的实现,它们的定义都在 `src/platforms/web/runtime/modules/event.js` 中:

```js
function add (
  event: string,
  handler: Function,
  once: boolean,
  capture: boolean,
  passive: boolean
) {
  handler = withMacroTask(handler)
  if (once) handler = createOnceHandler(handler, event, capture)
  target.addEventListener(
    event,
    handler,
    supportsPassive
      ? { capture, passive }
      : capture
  )
}

function remove (
  event: string,
  handler: Function,
  capture: boolean,
  _target?: HTMLElement
) {
  (_target || target).removeEventListener(
    event,
    handler._withTask || handler,
    capture
  )
}
```

`add` 和 `remove` 的逻辑很简单,就是实际上调用原生 `addEventListener` 和 `removeEventListener`,并根据参数传递一些配置,注意这里的 `hanlder` 会用 `withMacroTask(hanlder)` 包裹一下,它的定义在 `src/core/util/next-tick.js` 中:

```js
export function withMacroTask (fn: Function): Function {
  return fn._withTask || (fn._withTask = function () {
    useMacroTask = true
    const res = fn.apply(null, arguments)
    useMacroTask = false
    return res
  })
}
```

实际上就是强制在 DOM 事件的回调函数执行期间如果修改了数据,那么这些数据更改推入的队列会被当做 `macroTask` 在 `nextTick` 后执行。

## 自定义事件

除了原生 DOM 事件,Vue 还支持了自定义事件,并且自定义事件只能作用在组件上,如果在组件上使用原生事件,需要加 `.native` 修饰符,普通元素上使用 `.native` 修饰符无效,接下来我们就来分析它的实现。

在 `render` 阶段,如果是一个组件节点,则通过 `createComponent` 创建一个组件 `vnode`,我们再来回顾这个方法,定义在 `src/core/vdom/create-component.js` 中:

```js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // ...
  const listeners = data.on
  
  data.on = data.nativeOn
  
  // ...
  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 },
    asyncFactory
  )

  return vnode
}
```
我们只关注事件相关的逻辑,可以看到,它把 `data.on` 赋值给了 `listeners`,把 `data.nativeOn` 赋值给了 `data.on`,这样所有的原生 DOM 事件处理跟我们刚才介绍的一样,它是在当前组件环境中处理的。而对于自定义事件,我们把 `listeners` 作为 `vnode` 的 `componentOptions` 传入,它是在子组件初始化阶段中处理的,所以它的处理环境是子组件。

然后在子组件的初始化的时候,会执行 `initInternalComponent` 方法,它的定义在 `src/core/instance/init.js` 中:

```js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // ....
  const vnodeComponentOptions = parentVnode.componentOptions
 
  opts._parentListeners = vnodeComponentOptions.listeners
  // ...
}
```
这里拿到了父组件传入的 `listeners`,然后在执行 `initEvents` 的过程中,会处理这个 `listeners`,定义在 `src/core/instance/events.js` 中:

```js
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
```

拿到 `listeners` 后,执行 `updateComponentListeners(vm, listeners)` 方法:

```js
let target: any
export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, vm)
  target = undefined
}
```

`updateListeners` 我们之前介绍过,所以对于自定义事件和原生 DOM 事件处理的差异就在事件添加和删除的实现上,来看一下自定义事件 `add` 和 `remove` 的实现:

```js
function add (event, fn, once) {
  if (once) {
    target.$once(event, fn)
  } else {
    target.$on(event, fn)
  }
}

function remove (event, fn) {
  target.$off(event, fn)
}
```

实际上是利用 Vue 定义的事件中心,简单分析一下它的实现:

```js
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    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
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  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
  }

  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
    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]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    if (fn) {
      // 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
  }

  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++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
  }
}
```
非常经典的事件中心的实现,把所有的事件用 `vm._events` 存储起来,当执行 `vm.$on(event,fn)` 的时候,按事件的名称 `event` 把回调函数 `fn` 存储起来 `vm._events[event].push(fn)`。当执行 `vm.$emit(event)` 的时候,根据事件名 `event` 找到所有的回调函数 `let cbs = vm._events[event]`,然后遍历执行所有的回调函数。当执行 `vm.$off(event,fn)` 的时候会移除指定事件名 `event` 和指定的 `fn` 当执行 `vm.$once(event,fn)` 的时候,内部就是执行 `vm.$on`,并且当回调函数执行一次后再通过 `vm.$off` 移除事件的回调,这样就确保了回调函数只执行一次。

所以对于用户自定义的事件添加和删除就是利用了这几个事件中心的 API。需要注意的事一点,`vm.$emit` 是给当前的 `vm` 上派发的实例,之所以我们常用它做父子组件通讯,是因为它的回调函数的定义是在父组件中,对于我们这个例子而言,当子组件的 `button` 被点击了,它通过 `this.$emit('select')` 派发事件,那么子组件的实例就监听到了这个 `select` 事件,并执行它的回调函数——定义在父组件中的 `selectHandler` 方法,这样就相当于完成了一次父子组件的通讯。

## 总结

那么至此我们对 Vue 的事件实现有了进一步的了解,Vue 支持 2 种事件类型,原生 DOM 事件和自定义事件,它们主要的区别在于添加和删除事件的方式不一样,并且自定义事件的派发是往当前实例上派发,但是可以利用在父组件环境定义回调函数来实现父子组件的通讯。另外要注意一点,只有组件节点才可以添加自定义事件,并且添加原生 DOM 事件需要使用 `native` 修饰符;而普通元素使用 `.native` 修饰符是没有作用的,也只能添加原生 DOM 事件。
  
  





================================================
FILE: docs/v2/extend/index.md
================================================
# 扩展

前面几章我们分析了 Vue 的核心以及编译过程,除此之外,Vue 还提供了很多好用的 feature 如 `event`、`v-model`、`slot`、`keep-alive`、`transition` 等等。对他们的理解有助于我们在平时开发中更好地应用这些 feature,即使出现 bug 我们也可以很从容地应对。

这一章是一个可扩展的章节,除了已分析的这些 feature 外,未来我们可能会扩展更多的内容。

================================================
FILE: docs/v2/extend/keep-alive.md
================================================
# keep-alive

在我们的平时开发工作中,经常为了组件的缓存优化而使用 `<keep-alive>` 组件,乐此不疲,但很少有人关注它的实现原理,下面就让我们来一探究竟。

## 内置组件

`<keep-alive>` 是 Vue 源码中实现的一个组件,也就是说 Vue 源码不仅实现了一套组件化的机制,也实现了一些内置组件,它的定义在 `src/core/components/keep-alive.js` 中:

```js
export default {
  name: 'keep-alive',
  abstract: true,

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },

  created () {
    this.cache = Object.create(null)
    this.keys = []
  },

  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      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
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      } else {
        cache[key] = vnode
        keys.push(key)
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}
```

可以看到 `<keep-alive>` 组件的实现也是一个对象,注意它有一个属性 `abstract` 为 true,是一个抽象组件,Vue 的文档没有提这个概念,实际上它在组件实例建立父子关系的时候会被忽略,发生在 `initLifecycle` 的过程中:

```js
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
  while (parent.$options.abstract && parent.$parent) {
    parent = parent.$parent
  }
  parent.$children.push(vm)
}
vm.$parent = parent
```

`<keep-alive>` 在 `created` 钩子里定义了 `this.cache` 和 `this.keys`,本质上它就是去缓存已经创建过的 `vnode`。它的 `props` 定义了 `include`,`exclude`,它们可以字符串或者表达式,`include` 表示只有匹配的组件会被缓存,而 `exclude` 表示任何匹配的组件都不会被缓存,`props` 还定义了 `max`,它表示缓存的大小,因为我们是缓存的 `vnode` 对象,它也会持有 DOM,当我们缓存很多的时候,会比较占用内存,所以该配置允许我们指定缓存大小。

`<keep-alive>` 直接实现了 `render` 函数,而不是我们常规模板的方式,执行 `<keep-alive>` 组件渲染的时候,就会执行到这个 `render` 函数,接下来我们分析一下它的实现。

首先获取第一个子元素的 `vnode`:

```js
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
```

由于我们也是在 `<keep-alive>` 标签内部写 DOM,所以可以先获取到它的默认插槽,然后再获取到它的第一个子节点。`<keep-alive>` 只处理第一个子元素,所以一般和它搭配使用的有 `component` 动态组件或者是 `router-view`,这点要牢记。

然后又判断了当前组件的名称和 `include`、`exclude` 的关系:

```js
// check pattern
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
  // not included
  (include && (!name || !matches(include, name))) ||
  // excluded
  (exclude && name && matches(exclude, name))
) {
  return vnode
}

function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    return pattern.test(name)
  }
  return false
}
```

`matches` 的逻辑很简单,就是做匹配,分别处理了数组、字符串、正则表达式的情况,也就是说我们平时传的 `include` 和 `exclude` 可以是这三种类型的任意一种。并且我们的组件名如果满足了配置 `include` 且不匹配或者是配置了 `exclude` 且匹配,那么就直接返回这个组件的 `vnode`,否则的话走下一步缓存:

```js
const { cache, keys } = this
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
if (cache[key]) {
  vnode.componentInstance = cache[key].componentInstance
  // make current key freshest
  remove(keys, key)
  keys.push(key)
} else {
  cache[key] = vnode
  keys.push(key)
  // prune oldest entry
  if (this.max && keys.length > parseInt(this.max)) {
    pruneCacheEntry(cache, keys[0], keys, this._vnode)
  }
}
```

这部分逻辑很简单,如果命中缓存,则直接从缓存中拿 `vnode` 的组件实例,并且重新调整了 key 的顺序放在了最后一个;否则把 `vnode` 设置进缓存,最后还有一个逻辑,如果配置了 `max` 并且缓存的长度超过了 `this.max`,还要从缓存中删除第一个:

```js
function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null 
  remove(keys, key)
}
```
除了从缓存中删除外,还要判断如果要删除的缓存并的组件 `tag` 不是当前渲染组件 `tag`,也执行删除缓存的组件实例的 `$destroy` 方法。

最后设置 `vnode.data.keepAlive = true` ,这个作用稍后我们介绍。

注意,`<keep-alive>` 组件也是为观测 `include` 和 `exclude` 的变化,对缓存做处理:

```js
watch: {
  include (val: string | RegExp | Array<string>) {
    pruneCache(this, name => matches(val, name))
  },
  exclude (val: string | RegExp | Array<string>) {
    pruneCache(this, name => !matches(val, name))
  }
}

function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}
```
逻辑很简单,观测他们的变化执行 `pruneCache` 函数,其实就是对 `cache` 做遍历,发现缓存的节点名称和新的规则没有匹配上的时候,就把这个缓存节点从缓存中摘除。

## 组件渲染

到此为止,我们只了解了 `<keep-alive>` 的组件实现,但并不知道它包裹的子组件渲染和普通组件有什么不一样的地方。我们关注 2 个方面,首次渲染和缓存渲染。

同样为了更好地理解,我们也结合一个示例来分析:

```js
let A = {
  template: '<div class="a">' +
  '<p>A Comp</p>' +
  '</div>',
  name: 'A'
}

let B = {
  template: '<div class="b">' +
  '<p>B Comp</p>' +
  '</div>',
  name: 'B'
}

let vm = new Vue({
  el: '#app',
  template: '<div>' +
  '<keep-alive>' +
  '<component :is="currentComp">' +
  '</component>' +
  '</keep-alive>' +
  '<button @click="change">switch</button>' +
  '</div>',
  data: {
    currentComp: 'A'
  },
  methods: {
    change() {
      this.currentComp = this.currentComp === 'A' ? 'B' : 'A'
    }
  },
  components: {
    A,
    B
  }
})
```

### 首次渲染

我们知道 Vue 的渲染最后都会到 `patch` 过程,而组件的 `patch` 过程会执行 `createComponent` 方法,它的定义在 `src/core/vdom/patch.js` 中:

```js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
```

`createComponent` 定义了 `isReactivated` 的变量,它是根据 `vnode.componentInstance` 以及 `vnode.data.keepAlive` 的判断,第一次渲染的时候,`vnode.componentInstance` 为 `undefined`,`vnode.data.keepAlive` 为 true,因为它的父组件 `<keep-alive>` 的 `render` 函数会先执行,那么该 `vnode` 缓存到内存中,并且设置 `vnode.data.keepAlive` 为 true,因此 `isReactivated` 为 `false`,那么走正常的 `init` 的钩子函数执行组件的 `mount`。当 `vnode` 已经执行完 `patch` 后,执行 `initComponent` 函数:

```js
function initComponent (vnode, insertedVnodeQueue) {
  if (isDef(vnode.data.pendingInsert)) {
    insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
    vnode.data.pendingInsert = null
  }
  vnode.elm = vnode.componentInstance.$el
  if (isPatchable(vnode)) {
    invokeCreateHooks(vnode, insertedVnodeQueue)
    setScope(vnode)
  } else {
    // empty component root.
    // skip all element-related modules except for ref (#3455)
    registerRef(vnode)
    // make sure to invoke the insert hook
    insertedVnodeQueue.push(vnode)
  }
}
```
这里会有 `vnode.elm` 缓存了 `vnode` 创建生成的 DOM 节点。所以对于首次渲染而言,除了在 `<keep-alive>` 中建立缓存,和普通组件渲染没什么区别。

所以对我们的例子,初始化渲染 `A` 组件以及第一次点击 `switch` 渲染 `B` 组件,都是首次渲染。

### 缓存渲染

当我们从 `B` 组件再次点击 `switch` 切换到 `A` 组件,就会命中缓存渲染。

我们之前分析过,当数据发送变化,在 `patch` 的过程中会执行 `patchVnode` 的逻辑,它会对比新旧 `vnode` 节点,甚至对比它们的子节点去做更新逻辑,但是对于组件 `vnode` 而言,是没有 `children` 的,那么对于 `<keep-alive>` 组件而言,如何更新它包裹的内容呢?

原来 `patchVnode` 在做各种 diff 之前,会先执行 `prepatch` 的钩子函数,它的定义在 `src/core/vdom/create-component` 中:

```js
const componentVNodeHooks = {
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },
  // ...
}
```

`prepatch` 核心逻辑就是执行 `updateChildComponent` 方法,它的定义在 `src/core/instance/lifecycle.js` 中:

```js
export function updateChildComponent (
  vm: Component,
  propsData: ?Object,
  listeners: ?Object,
  parentVnode: MountedComponentVNode,
  renderChildren: ?Array<VNode>
) {
  const hasChildren = !!(
    renderChildren ||          
    vm.$options._renderChildren ||
    parentVnode.data.scopedSlots || 
    vm.$scopedSlots !== emptyObject 
  )

  // ...
  if (hasChildren) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context)
    vm.$forceUpdate()
  }
}
```

`updateChildComponent` 方法主要是去更新组件实例的一些属性,这里我们重点关注一下 `slot` 部分,由于 `<keep-alive>` 组件本质上支持了 `slot`,所以它执行 `prepatch` 的时候,需要对自己的 `children`,也就是这些 `slots` 做重新解析,并触发 `<keep-alive>` 组件实例 `$forceUpdate` 逻辑,也就是重新执行 `<keep-alive>` 的 `render` 方法,这个时候如果它包裹的第一个组件 `vnode` 命中缓存,则直接返回缓存中的 `vnode.componentInstance`,在我们的例子中就是缓存的 `A` 组件,接着又会执行 `patch` 过程,再次执行到 `createComponent` 方法,我们再回顾一下:

```js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
```
这个时候 `isReactivated` 为 true,并且在执行 `init` 钩子函数的时候不会再执行组件的 `mount` 过程了,相关逻辑在 `src/core/vdom/create-component.js` 中:

```js
const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  // ...
}
```

这也就是被 `<keep-alive>` 包裹的组件在有缓存的时候就不会在执行组件的 `created`、`mounted` 等钩子函数的原因了。回到 `createComponent` 方法,在 `isReactivated` 为 true 的情况下会执行 `reactivateComponent` 方法:

```js
function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i
  // hack for #4339: a reactivated component with inner transition
  // does not trigger because the inner node's created hooks are not called
  // again. It's not ideal to involve module-specific logic in here but
  // there doesn't seem to be a better way to do it.
  let innerNode = vnode
  while (innerNode.componentInstance) {
    innerNode = innerNode.componentInstance._vnode
    if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
      for (i = 0; i < cbs.activate.length; ++i) {
        cbs.activate[i](emptyNode, innerNode)
      }
      insertedVnodeQueue.push(innerNode)
      break
    }
  }
  // unlike a newly created component,
  // a reactivated keep-alive component doesn't insert itself
  insert(parentElm, vnode.elm, refElm)
}
```
前面部分的逻辑是解决对 `reactived` 组件 `transition` 动画不触发的问题,可以先不关注,最后通过执行 `insert(parentElm, vnode.elm, refElm)` 就把缓存的 DOM 对象直接插入到目标元素中,这样就完成了在数据更新的情况下的渲染过程。

## 生命周期

之前我们提到,组件一旦被 `<keep-alive>` 缓存,那么再次渲染的时候就不会执行 `created`、`mounted` 等钩子函数,但是我们很多业务场景都是希望在我们被缓存的组件再次被渲染的时候做一些事情,好在 Vue 提供了 `activated` 钩子函数,它的执行时机是 `<keep-alive>` 包裹的组件渲染的时候,接下来我们从源码角度来分析一下它的实现原理。

在渲染的最后一步,会执行 `invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)` 函数执行 `vnode` 的 `insert` 钩子函数,它的定义在 `src/core/vdom/create-component.js` 中:

```js
const componentVNodeHooks = {
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },
  // ...
}
```

这里判断如果是被 `<keep-alive>` 包裹的组件已经 `mounted`,那么则执行 `queueActivatedComponent(componentInstance)` ,否则执行 `activateChildComponent(componentInstance, true)`。我们先分析非 `mounted` 的情况,`activateChildComponent` 的定义在 `src/core/instance/lifecycle.js` 中:

```js
export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {
      return
    }
  } else if (vm._directInactive) {
    return
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}
```

可以看到这里就是执行组件的 `acitvated` 钩子函数,并且递归去执行它的所有子组件的 `activated` 钩子函数。

那么再看 `queueActivatedComponent` 的逻辑,它定义在 `src/core/observer/scheduler.js` 中:

```js
export function queueActivatedComponent (vm: Component) {
  vm._inactive = false
  activatedChildren.push(vm)
}
```
这个逻辑很简单,把当前 `vm` 实例添加到 `activatedChildren` 数组中,等所有的渲染完毕,在 `nextTick`后会执行 `flushSchedulerQueue`,这个时候就会执行:

```js
function flushSchedulerQueue () {
  // ...
  const activatedQueue = activatedChildren.slice()
  callActivatedHooks(activatedQueue)
  // ...
} 

function callActivatedHooks (queue
Download .txt
gitextract_7kf9tf8l/

├── .flowconfig
├── .gitignore
├── LICENSE
├── README.md
├── build.sh
├── docs/
│   ├── .vuepress/
│   │   ├── config.js
│   │   └── public/
│   │       └── manifest.json
│   ├── README.md
│   ├── v2/
│   │   ├── compile/
│   │   │   ├── codegen.md
│   │   │   ├── entrance.md
│   │   │   ├── index.md
│   │   │   ├── optimize.md
│   │   │   └── parse.md
│   │   ├── components/
│   │   │   ├── async-component.md
│   │   │   ├── component-register.md
│   │   │   ├── create-component.md
│   │   │   ├── index.md
│   │   │   ├── lifecycle.md
│   │   │   ├── merge-option.md
│   │   │   └── patch.md
│   │   ├── data-driven/
│   │   │   ├── create-element.md
│   │   │   ├── index.md
│   │   │   ├── mounted.md
│   │   │   ├── new-vue.md
│   │   │   ├── render.md
│   │   │   ├── update.md
│   │   │   └── virtual-dom.md
│   │   ├── extend/
│   │   │   ├── event.md
│   │   │   ├── index.md
│   │   │   ├── keep-alive.md
│   │   │   ├── slot.md
│   │   │   ├── tansition-group.md
│   │   │   ├── tansition.md
│   │   │   └── v-model.md
│   │   ├── prepare/
│   │   │   ├── build.md
│   │   │   ├── directory.md
│   │   │   ├── entrance.md
│   │   │   ├── flow.md
│   │   │   └── index.md
│   │   ├── reactive/
│   │   │   ├── component-update.md
│   │   │   ├── computed-watcher.md
│   │   │   ├── getters.md
│   │   │   ├── index.md
│   │   │   ├── next-tick.md
│   │   │   ├── props.md
│   │   │   ├── questions.md
│   │   │   ├── reactive-object.md
│   │   │   ├── setters.md
│   │   │   └── summary.md
│   │   ├── vue-router/
│   │   │   ├── index.md
│   │   │   ├── install.md
│   │   │   ├── matcher.md
│   │   │   ├── router.md
│   │   │   └── transition-to.md
│   │   └── vuex/
│   │       ├── api.md
│   │       ├── index.md
│   │       ├── init.md
│   │       └── plugin.md
│   └── v3/
│       ├── guide/
│       │   └── index.md
│       └── new/
│           └── index.md
├── package.json
├── vue/
│   ├── flow/
│   │   ├── compiler.js
│   │   ├── component.js
│   │   ├── global-api.js
│   │   ├── modules.js
│   │   ├── options.js
│   │   ├── ssr.js
│   │   ├── vnode.js
│   │   └── weex.js
│   ├── package.json
│   ├── scripts/
│   │   ├── alias.js
│   │   ├── build.js
│   │   ├── config.js
│   │   ├── gen-release-note.js
│   │   ├── get-weex-version.js
│   │   ├── git-hooks/
│   │   │   ├── commit-msg
│   │   │   └── pre-commit
│   │   ├── release-weex.sh
│   │   ├── release.sh
│   │   └── verify-commit-msg.js
│   ├── src/
│   │   ├── compiler/
│   │   │   ├── codegen/
│   │   │   │   ├── events.js
│   │   │   │   └── index.js
│   │   │   ├── create-compiler.js
│   │   │   ├── directives/
│   │   │   │   ├── bind.js
│   │   │   │   ├── index.js
│   │   │   │   ├── model.js
│   │   │   │   └── on.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
│   │   │   └── to-function.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-listeners.js
│   │   │   │   │   ├── bind-object-props.js
│   │   │   │   │   ├── check-keycodes.js
│   │   │   │   │   ├── index.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
│   │   │   │   ├── traverse.js
│   │   │   │   └── watcher.js
│   │   │   ├── util/
│   │   │   │   ├── debug.js
│   │   │   │   ├── env.js
│   │   │   │   ├── error.js
│   │   │   │   ├── index.js
│   │   │   │   ├── lang.js
│   │   │   │   ├── next-tick.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
│   │   │       │   ├── is-async-placeholder.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
│   │   │   │   │   │   ├── model.js
│   │   │   │   │   │   └── style.js
│   │   │   │   │   ├── options.js
│   │   │   │   │   └── util.js
│   │   │   │   ├── entry-compiler.js
│   │   │   │   ├── entry-runtime-with-compiler.js
│   │   │   │   ├── entry-runtime.js
│   │   │   │   ├── entry-server-basic-renderer.js
│   │   │   │   ├── entry-server-renderer.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
│   │   │   │   ├── server/
│   │   │   │   │   ├── compiler.js
│   │   │   │   │   ├── directives/
│   │   │   │   │   │   ├── index.js
│   │   │   │   │   │   ├── model.js
│   │   │   │   │   │   └── show.js
│   │   │   │   │   ├── modules/
│   │   │   │   │   │   ├── attrs.js
│   │   │   │   │   │   ├── class.js
│   │   │   │   │   │   ├── dom-props.js
│   │   │   │   │   │   ├── index.js
│   │   │   │   │   │   └── style.js
│   │   │   │   │   └── util.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
│   │   │       │       ├── recycle-list/
│   │   │       │       │   ├── component-root.js
│   │   │       │       │   ├── component.js
│   │   │       │       │   ├── index.js
│   │   │       │       │   ├── recycle-list.js
│   │   │       │       │   ├── text.js
│   │   │       │       │   ├── v-bind.js
│   │   │       │       │   ├── v-for.js
│   │   │       │       │   ├── v-if.js
│   │   │       │       │   ├── v-on.js
│   │   │       │       │   └── v-once.js
│   │   │       │       └── style.js
│   │   │       ├── entry-compiler.js
│   │   │       ├── entry-framework.js
│   │   │       ├── entry-runtime-factory.js
│   │   │       ├── runtime/
│   │   │       │   ├── components/
│   │   │       │   │   ├── index.js
│   │   │       │   │   ├── richtext.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
│   │   │       │   ├── recycle-list/
│   │   │       │   │   ├── render-component-template.js
│   │   │       │   │   └── virtual-component.js
│   │   │       │   └── text-node.js
│   │   │       └── util/
│   │   │           ├── element.js
│   │   │           ├── index.js
│   │   │           └── parser.js
│   │   ├── server/
│   │   │   ├── bundle-renderer/
│   │   │   │   ├── create-bundle-renderer.js
│   │   │   │   ├── create-bundle-runner.js
│   │   │   │   └── source-map-support.js
│   │   │   ├── create-basic-renderer.js
│   │   │   ├── create-renderer.js
│   │   │   ├── optimizing-compiler/
│   │   │   │   ├── codegen.js
│   │   │   │   ├── index.js
│   │   │   │   ├── modules.js
│   │   │   │   ├── optimizer.js
│   │   │   │   └── runtime-helpers.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
│   └── test/
│       ├── e2e/
│       │   ├── .eslintrc
│       │   ├── nightwatch.config.js
│       │   ├── runner.js
│       │   └── specs/
│       │       ├── async-edge-cases.html
│       │       ├── async-edge-cases.js
│       │       ├── basic-ssr.html
│       │       ├── basic-ssr.js
│       │       ├── commits.js
│       │       ├── grid.js
│       │       ├── markdown.js
│       │       ├── modal.js
│       │       ├── select2.js
│       │       ├── svg.js
│       │       ├── todomvc.js
│       │       └── tree.js
│       ├── helpers/
│       │   ├── .eslintrc
│       │   ├── classlist.js
│       │   ├── test-object-option.js
│       │   ├── to-equal.js
│       │   ├── to-have-been-warned.js
│       │   ├── trigger-event.js
│       │   ├── vdom.js
│       │   └── wait-for-update.js
│       ├── ssr/
│       │   ├── .eslintrc
│       │   ├── async-loader.js
│       │   ├── compile-with-webpack.js
│       │   ├── fixtures/
│       │   │   ├── app.js
│       │   │   ├── async-bar.js
│       │   │   ├── async-foo.js
│       │   │   ├── cache.js
│       │   │   ├── error.js
│       │   │   ├── nested-cache.js
│       │   │   ├── promise-rejection.js
│       │   │   ├── split.js
│       │   │   └── test.css
│       │   ├── jasmine.json
│       │   ├── ssr-basic-renderer.spec.js
│       │   ├── ssr-bundle-render.spec.js
│       │   ├── ssr-stream.spec.js
│       │   ├── ssr-string.spec.js
│       │   └── ssr-template.spec.js
│       ├── unit/
│       │   ├── .eslintrc
│       │   ├── features/
│       │   │   ├── component/
│       │   │   │   ├── component-async.spec.js
│       │   │   │   ├── component-keep-alive.spec.js
│       │   │   │   ├── component-scoped-slot.spec.js
│       │   │   │   ├── component-slot.spec.js
│       │   │   │   └── component.spec.js
│       │   │   ├── debug.spec.js
│       │   │   ├── directives/
│       │   │   │   ├── bind.spec.js
│       │   │   │   ├── class.spec.js
│       │   │   │   ├── cloak.spec.js
│       │   │   │   ├── for.spec.js
│       │   │   │   ├── html.spec.js
│       │   │   │   ├── if.spec.js
│       │   │   │   ├── model-checkbox.spec.js
│       │   │   │   ├── model-component.spec.js
│       │   │   │   ├── model-dynamic.spec.js
│       │   │   │   ├── model-file.spec.js
│       │   │   │   ├── model-parse.spec.js
│       │   │   │   ├── model-radio.spec.js
│       │   │   │   ├── model-select.spec.js
│       │   │   │   ├── model-text.spec.js
│       │   │   │   ├── on.spec.js
│       │   │   │   ├── once.spec.js
│       │   │   │   ├── pre.spec.js
│       │   │   │   ├── show.spec.js
│       │   │   │   ├── static-style-parser.spec.js
│       │   │   │   ├── style.spec.js
│       │   │   │   └── text.spec.js
│       │   │   ├── error-handling.spec.js
│       │   │   ├── filter/
│       │   │   │   └── filter.spec.js
│       │   │   ├── global-api/
│       │   │   │   ├── assets.spec.js
│       │   │   │   ├── compile.spec.js
│       │   │   │   ├── config.spec.js
│       │   │   │   ├── extend.spec.js
│       │   │   │   ├── mixin.spec.js
│       │   │   │   ├── set-delete.spec.js
│       │   │   │   └── use.spec.js
│       │   │   ├── instance/
│       │   │   │   ├── init.spec.js
│       │   │   │   ├── methods-data.spec.js
│       │   │   │   ├── methods-events.spec.js
│       │   │   │   ├── methods-lifecycle.spec.js
│       │   │   │   ├── properties.spec.js
│       │   │   │   └── render-proxy.spec.js
│       │   │   ├── options/
│       │   │   │   ├── _scopeId.spec.js
│       │   │   │   ├── comments.spec.js
│       │   │   │   ├── components.spec.js
│       │   │   │   ├── computed.spec.js
│       │   │   │   ├── data.spec.js
│       │   │   │   ├── delimiters.spec.js
│       │   │   │   ├── directives.spec.js
│       │   │   │   ├── el.spec.js
│       │   │   │   ├── errorCaptured.spec.js
│       │   │   │   ├── extends.spec.js
│       │   │   │   ├── functional.spec.js
│       │   │   │   ├── inheritAttrs.spec.js
│       │   │   │   ├── inject.spec.js
│       │   │   │   ├── lifecycle.spec.js
│       │   │   │   ├── methods.spec.js
│       │   │   │   ├── mixins.spec.js
│       │   │   │   ├── name.spec.js
│       │   │   │   ├── parent.spec.js
│       │   │   │   ├── props.spec.js
│       │   │   │   ├── propsData.spec.js
│       │   │   │   ├── render.spec.js
│       │   │   │   ├── renderError.spec.js
│       │   │   │   ├── template.spec.js
│       │   │   │   └── watch.spec.js
│       │   │   ├── ref.spec.js
│       │   │   └── transition/
│       │   │       ├── inject-styles.js
│       │   │       ├── transition-group.spec.js
│       │   │       ├── transition-mode.spec.js
│       │   │       └── transition.spec.js
│       │   ├── index.js
│       │   ├── karma.base.config.js
│       │   ├── karma.cover.config.js
│       │   ├── karma.dev.config.js
│       │   ├── karma.sauce.config.js
│       │   ├── karma.unit.config.js
│       │   └── modules/
│       │       ├── compiler/
│       │       │   ├── codegen.spec.js
│       │       │   ├── compiler-options.spec.js
│       │       │   ├── optimizer.spec.js
│       │       │   └── parser.spec.js
│       │       ├── observer/
│       │       │   ├── dep.spec.js
│       │       │   ├── observer.spec.js
│       │       │   ├── scheduler.spec.js
│       │       │   └── watcher.spec.js
│       │       ├── server-compiler/
│       │       │   └── optimizer.spec.js
│       │       ├── sfc/
│       │       │   └── sfc-parser.spec.js
│       │       ├── util/
│       │       │   └── next-tick.spec.js
│       │       └── vdom/
│       │           ├── create-component.spec.js
│       │           ├── create-element.spec.js
│       │           ├── modules/
│       │           │   ├── attrs.spec.js
│       │           │   ├── class.spec.js
│       │           │   ├── directive.spec.js
│       │           │   ├── dom-props.spec.js
│       │           │   ├── events.spec.js
│       │           │   └── style.spec.js
│       │           └── patch/
│       │               ├── children.spec.js
│       │               ├── edge-cases.spec.js
│       │               ├── element.spec.js
│       │               ├── hooks.spec.js
│       │               └── hydration.spec.js
│       └── weex/
│           ├── .eslintrc
│           ├── cases/
│           │   ├── cases.spec.js
│           │   ├── event/
│           │   │   ├── click.after.vdom.js
│           │   │   ├── click.before.vdom.js
│           │   │   └── click.vue
│           │   ├── recycle-list/
│           │   │   ├── attrs.vdom.js
│           │   │   ├── attrs.vue
│           │   │   ├── classname.vdom.js
│           │   │   ├── classname.vue
│           │   │   ├── components/
│           │   │   │   ├── banner.vue
│           │   │   │   ├── counter.vue
│           │   │   │   ├── editor.vue
│           │   │   │   ├── footer.vue
│           │   │   │   ├── lifecycle.vue
│           │   │   │   ├── poster.vue
│           │   │   │   ├── stateful-lifecycle.vdom.js
│           │   │   │   ├── stateful-lifecycle.vue
│           │   │   │   ├── stateful-v-model.vdom.js
│           │   │   │   ├── stateful-v-model.vue
│           │   │   │   ├── stateful.vdom.js
│           │   │   │   ├── stateful.vue
│           │   │   │   ├── stateless-multi-components.vdom.js
│           │   │   │   ├── stateless-multi-components.vue
│           │   │   │   ├── stateless-with-props.vdom.js
│           │   │   │   ├── stateless-with-props.vue
│           │   │   │   ├── stateless.vdom.js
│           │   │   │   └── stateless.vue
│           │   │   ├── inline-style.vdom.js
│           │   │   ├── inline-style.vue
│           │   │   ├── text-node.vdom.js
│           │   │   ├── text-node.vue
│           │   │   ├── v-else-if.vdom.js
│           │   │   ├── v-else-if.vue
│           │   │   ├── v-else.vdom.js
│           │   │   ├── v-else.vue
│           │   │   ├── v-for-iterator.vdom.js
│           │   │   ├── v-for-iterator.vue
│           │   │   ├── v-for.vdom.js
│           │   │   ├── v-for.vue
│           │   │   ├── v-if.vdom.js
│           │   │   ├── v-if.vue
│           │   │   ├── v-on-inline.vdom.js
│           │   │   ├── v-on-inline.vue
│           │   │   ├── v-on.vdom.js
│           │   │   ├── v-on.vue
│           │   │   ├── v-once.vdom.js
│           │   │   └── v-once.vue
│           │   └── render/
│           │       ├── sample.vdom.js
│           │       └── sample.vue
│           ├── compiler/
│           │   ├── append.spec.js
│           │   ├── class.spec.js
│           │   ├── compile.spec.js
│           │   ├── parser.spec.js
│           │   ├── props.spec.js
│           │   ├── style.spec.js
│           │   └── v-model.spec.js
│           ├── helpers/
│           │   └── index.js
│           ├── jasmine.json
│           └── runtime/
│               ├── attrs.spec.js
│               ├── class.spec.js
│               ├── components/
│               │   └── richtext.spec.js
│               ├── events.spec.js
│               ├── framework.spec.js
│               ├── node.spec.js
│               └── style.spec.js
├── vue-router/
│   ├── build/
│   │   ├── build.js
│   │   ├── configs.js
│   │   ├── release.sh
│   │   ├── rollup.dev.config.js
│   │   └── update-docs.sh
│   ├── flow/
│   │   └── declarations.js
│   ├── 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
│   └── test/
│       ├── e2e/
│       │   ├── nightwatch.config.js
│       │   ├── runner.js
│       │   └── specs/
│       │       ├── active-links.js
│       │       ├── auth-flow.js
│       │       ├── basic.js
│       │       ├── data-fetching.js
│       │       ├── hash-mode.js
│       │       ├── hash-scroll-behavior.js
│       │       ├── lazy-loading.js
│       │       ├── named-routes.js
│       │       ├── named-views.js
│       │       ├── navigation-guards.js
│       │       ├── nested-router.js
│       │       ├── nested-routes.js
│       │       ├── redirect.js
│       │       ├── route-alias.js
│       │       ├── route-matching.js
│       │       ├── route-props.js
│       │       ├── scroll-behavior.js
│       │       └── transitions.js
│       └── unit/
│           ├── jasmine.json
│           └── specs/
│               ├── api.spec.js
│               ├── async.spec.js
│               ├── create-map.spec.js
│               ├── create-matcher.spec.js
│               ├── custom-query.spec.js
│               ├── discrete-components.spec.js
│               ├── error-handling.spec.js
│               ├── location.spec.js
│               ├── node.spec.js
│               ├── path.spec.js
│               ├── query.spec.js
│               └── route.spec.js
└── vuex/
    ├── build/
    │   ├── build.main.js
    │   ├── configs.js
    │   ├── release.sh
    │   ├── rollup.dev.config.js
    │   └── rollup.logger.config.js
    ├── 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
    └── test/
        ├── e2e/
        │   ├── nightwatch.config.js
        │   ├── runner.js
        │   └── specs/
        │       ├── cart.js
        │       ├── chat.js
        │       ├── counter.js
        │       └── todomvc.js
        └── unit/
            ├── .eslintrc
            ├── helpers.spec.js
            ├── hot-reload.spec.js
            ├── jasmine.json
            ├── module/
            │   ├── module-collection.spec.js
            │   └── module.spec.js
            ├── modules.spec.js
            ├── setup.js
            ├── store.spec.js
            └── util.spec.js
Download .txt
SYMBOL INDEX (1141 symbols across 254 files)

FILE: vue-router/build/build.js
  function build (line 14) | function build (builds) {
  function buildEntry (line 29) | function buildEntry ({ input, output }) {
  function write (line 50) | function write (dest, code, zip) {
  function getSize (line 71) | function getSize (code) {
  function logError (line 75) | function logError (e) {
  function blue (line 79) | function blue (str) {

FILE: vue-router/build/configs.js
  function genConfig (line 39) | function genConfig (opts) {

FILE: vue-router/src/components/link.js
  method let (line 127) | let child

FILE: vue-router/src/components/view.js
  method render (line 12) | render (_, { props, children, parent, data }) {
  function resolveProps (line 89) | function resolveProps (route, config) {
  function extend (line 110) | 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 35) | constructor (options: RouterOptions = {}) {
  method apply (line 170) | apply([], route.matched.map(m => {

FILE: vue-router/src/install.js
  function install (line 6) | 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 52) | const START = createRoute(null, {
  function formatMatch (line 56) | function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
  function getFullPath (line 65) | function getFullPath (
  function isObjectEqual (line 96) | function isObjectEqual (a = {}, b = {}): boolean {
  function isIncludedRoute (line 115) | function isIncludedRoute (current: Route, target: Route): boolean {
  method for (line 126) | 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-router/test/e2e/specs/active-links.js
  function assertActiveLinks (line 36) | function assertActiveLinks (n, activeA, activeLI, exactActiveA, exactAct...

FILE: vue-router/test/unit/specs/api.spec.js
  method beforeRouteEnter (line 85) | beforeRouteEnter (to, from, next) {

FILE: vue/flow/modules.js
  class SourceMapGenerator (line 7) | class SourceMapGenerator {

FILE: vue/scripts/build.js
  function build (line 28) | function build (builds) {
  function buildEntry (line 43) | function buildEntry (config) {
  function write (line 66) | function write (dest, code, zip) {
  function getSize (line 87) | function getSize (code) {
  function logError (line 91) | function logError (e) {
  function blue (line 95) | function blue (str) {

FILE: vue/scripts/config.js
  method intro (line 19) | intro () {
  method outro (line 22) | outro () {
  function genConfig (line 171) | function genConfig (name) {

FILE: vue/scripts/gen-release-note.js
  method transform (line 9) | transform (pkg) {

FILE: vue/src/compiler/codegen/events.js
  function genHandlers (line 52) | function genHandlers (
  function genWeexHandler (line 66) | function genWeexHandler (params: Array<any>, handlerCode: string) {
  method if (line 86) | if (!handler) {
  method if (line 98) | if (isMethodPath || isFunctionExpression) {
  method if (line 111) | if (modifierCode[key]) {

FILE: vue/src/compiler/codegen/index.js
  function generate (line 40) | function generate (
  function genElement (line 52) | function genElement (el: ASTElement, state: CodegenState): string {
  function genStatic (line 89) | function genStatic (el: ASTElement, state: CodegenState): string {
  function genOnce (line 100) | function genOnce (el: ASTElement, state: CodegenState): string {
  method if (line 142) | if (!conditions.length) {

FILE: vue/src/compiler/create-compiler.js
  function createCompilerCreator (line 7) | function createCompilerCreator (baseCompile: Function): Function {

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 70) | function parseModel (val: string): ModelParseResult {
  function next (line 110) | function next (): number {
  function eof (line 114) | function eof (): boolean {
  function isStringStart (line 118) | function isStringStart (chr: number): boolean {
  function parseBracket (line 122) | function parseBracket (chr: number): void {
  function parseString (line 140) | function parseString (chr: number): void {

FILE: vue/src/compiler/directives/on.js
  function on (line 5) | function on (el: ASTElement, dir: ASTDirective) {

FILE: vue/src/compiler/error-detector.js
  function checkNode (line 30) | function checkNode (node: ASTNode, errors: Array<string>) {
  function checkEvent (line 56) | function checkEvent (exp: string, text: string, errors: Array<string>) {
  function checkFor (line 68) | function checkFor (node: ASTElement, text: string, errors: Array<string>) {
  function checkIdentifier (line 75) | function checkIdentifier (
  function checkExpression (line 90) | 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 19) | function addProp (el: ASTElement, name: string, value: string) {
  function addAttr (line 24) | function addAttr (el: ASTElement, name: string, value: any) {
  function addRawAttr (line 30) | function addRawAttr (el: ASTElement, name: string, value: any) {
  method parseFilters (line 132) | parseFilters(dynamicValue)
  method let (line 150) | let val
  method if (line 160) | if (removeFromMap) {

FILE: vue/src/compiler/optimizer.js
  function genStaticKeys (line 31) | function genStaticKeys (keys: string): Function {
  function markStatic (line 38) | function markStatic (node: ASTNode) {
  function markStaticRoots (line 70) | function markStaticRoots (node: ASTNode, isInFor: boolean) {
  function isStatic (line 100) | function isStatic (node: ASTNode): boolean {
  function isDirectChildOfTemplateFor (line 117) | function isDirectChildOfTemplateFor (node: ASTElement): boolean {

FILE: vue/src/compiler/parser/filter-parser.js
  function parseFilters (line 5) | function parseFilters (exp: string): string {
  function wrapFilter (line 87) | function wrapFilter (exp: string, filter: string): string {

FILE: vue/src/compiler/parser/html-parser.js
  constant IS_REGEX_CAPTURING_BROKEN (line 29) | let IS_REGEX_CAPTURING_BROKEN = false
  function decodeAttr (line 53) | function decodeAttr (value, shouldDecodeNewlines) {
  function parseHTML (line 58) | function parseHTML (html, options) {

FILE: vue/src/compiler/parser/index.js
  method start (line 117) | start (tag, attrs, unary) {
  method end (line 224) | end () {
  method chars (line 237) | chars (text: string) {
  method comment (line 282) | comment (text: string) {
  function processIf (line 389) | function processIf (el) {
  function processIfConditions (line 408) | function processIfConditions (el, parent) {
  method if (line 426) | if (children[i].type === 1) {
  method warn (line 430) | warn(

FILE: vue/src/compiler/to-function.js
  method if (line 38) | if (e.toString().match(/unsafe-eval|CSP/)) {

FILE: vue/src/core/components/keep-alive.js
  function matches (line 12) | function matches (pattern: string | RegExp | Array<string>, name: string...
  function pruneCache (line 24) | function pruneCache (keepAliveInstance: any, filter: Function) {
  function pruneCacheEntry (line 37) | function pruneCacheEntry (

FILE: vue/src/core/global-api/assets.js
  method if (line 15) | if (!definition) {

FILE: vue/src/core/global-api/extend.js
  function initExtend (line 7) | function initExtend (Vue: GlobalAPI) {
  function initProps (line 83) | function initProps (Comp) {
  function initComputed (line 90) | 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 5) | function initMixin (Vue: GlobalAPI) {

FILE: vue/src/core/global-api/use.js
  function initUse (line 5) | function initUse (Vue: GlobalAPI) {

FILE: vue/src/core/index.js
  method get (line 13) | get () {

FILE: vue/src/core/instance/events.js
  function initEvents (line 12) | function initEvents (vm: Component) {
  function add (line 24) | function add (event, fn, once) {
  function remove (line 32) | function remove (event, fn) {
  function eventsMixin (line 46) | 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 15) | function initMixin (Vue: Class<Component>) {
  function initInternalComponent (line 74) | function initInternalComponent (vm: Component, options: InternalComponen...
  function resolveConstructorOptions (line 93) | function resolveConstructorOptions (Ctor: Class<Component>) {
  method if (line 123) | 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 24) | function initLifecycle (vm: Component) {
  function lifecycleMixin (line 50) | function lifecycleMixin (Vue: Class<Component>) {
  method before (line 191) | before () {
  method if (line 215) | if (process.env.NODE_ENV !== 'production') {
  function isInInactiveTree (line 274) | function isInInactiveTree (vm) {
  function activateChildComponent (line 281) | function activateChildComponent (vm: Component, direct?: boolean) {
  function deactivateChildComponent (line 299) | function deactivateChildComponent (vm: Component, direct?: boolean) {
  function callHook (line 315) | 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-listeners.js
  function bindObjectListeners (line 5) | function bindObjectListeners (data: any, value: any): VNodeData {

FILE: vue/src/core/instance/render-helpers/bind-object-props.js
  method if (line 22) | if (value) {

FILE: vue/src/core/instance/render-helpers/check-keycodes.js
  method isKeyNotMatch (line 28) | isKeyNotMatch(builtInKeyName, eventKeyName)

FILE: vue/src/core/instance/render-helpers/index.js
  function installRenderHelpers (line 14) | function installRenderHelpers (target: any) {

FILE: vue/src/core/instance/render-helpers/render-list.js
  method key (line 16) | key
  method isObject (line 27) | isObject(val)) {
  method if (line 35) | if (isDef(ret)) {

FILE: vue/src/core/instance/render-helpers/render-static.js
  function markOnce (line 31) | function markOnce (
  function markStatic (line 40) | function markStatic (
  function markStaticNode (line 56) | function markStaticNode (node, key, isOnce) {

FILE: vue/src/core/instance/render-helpers/resolve-filter.js
  function resolveFilter (line 8) | function resolveFilter (id: string): Function {

FILE: vue/src/core/instance/render-helpers/resolve-slots.js
  function isWhitespace (line 48) | function isWhitespace (node: VNode): boolean {
  method if (line 58) | if (Array.isArray(fns[i])) {

FILE: vue/src/core/instance/render.js
  function initRender (line 18) | function initRender (vm: Component) {
  function renderMixin (line 53) | function renderMixin (Vue: Class<Component>) {

FILE: vue/src/core/instance/state.js
  function proxy (line 38) | function proxy (target: Object, sourceKey: string, key: string) {
  function initState (line 48) | function initState (vm: Component) {
  function initProps (line 64) | function initProps (vm: Component, propsOptions: Object) {
  function initData (line 112) | function initData (vm: Component) {
  function getData (line 154) | function getData (data: Function, vm: Component): any {
  function initComputed (line 169) | function initComputed (vm: Component, computed: Object) {
  function defineComputed (line 210) | function defineComputed (
  function createComputedGetter (line 243) | function createComputedGetter (key) {
  function initMethods (line 253) | function initMethods (vm: Component, methods: Object) {
  function initWatch (line 281) | function initWatch (vm: Component, watch: Object) {
  function createWatcher (line 294) | function createWatcher (
  function stateMixin (line 310) | function stateMixin (Vue: Class<Component>) {

FILE: vue/src/core/observer/dep.js
  function popTarget (line 56) | function popTarget () {

FILE: vue/src/core/observer/index.js
  function toggleObserving (line 27) | function toggleObserving (value: boolean) {
  method for (line 98) | for (let i = 0, l = keys.length; i < l; i++) {
  method if (line 110) | if (!isObject(value) || value instanceof VNode) {

FILE: vue/src/core/observer/scheduler.js
  constant MAX_UPDATE_COUNT (line 13) | const MAX_UPDATE_COUNT = 100

FILE: vue/src/core/observer/traverse.js
  function traverse (line 14) | function traverse (val: any) {
  function _traverse (line 19) | function _traverse (val: any, seen: SimpleSet) {

FILE: vue/src/core/util/env.js
  method get (line 26) | get () {
  function isNative (line 56) | function isNative (Ctor: any): boolean {

FILE: vue/src/core/util/error.js
  function handleError (line 7) | function handleError (err: Error, vm: any, info: string) {
  function globalHandleError (line 27) | function globalHandleError (err, vm, info) {
  function logError (line 38) | function logError (err, vm, info) {

FILE: vue/src/core/util/lang.js
  function isReserved (line 6) | function isReserved (str: string): boolean {

FILE: vue/src/core/util/next-tick.js
  function flushCallbacks (line 11) | function flushCallbacks () {
  function withMacroTask (line 81) | function withMacroTask (fn: Function): Function {

FILE: vue/src/core/util/options.js
  function mergeData (line 48) | function mergeData (to: Object, from: ?Object): Object {
  method if (line 73) | if (!vm) {
  method if (line 115) | if (!vm) {
  method if (line 226) | if (childVal && process.env.NODE_ENV !== 'production') {
  method for (line 292) | for (const key in props) {
  method warn (line 300) | warn(
  method mergeField (line 393) | mergeField(key)
  method mergeField (line 400) | mergeField (key) {

FILE: vue/src/core/util/props.js
  method if (line 33) | if (absent && !hasOwn(prop, 'default')) {
  method if (line 69) | if (!hasOwn(prop, 'default')) {
  method if (line 107) | if (prop.required && absent) {
  method if (line 121) | if (!Array.isArray(type)) {
  method if (line 141) | if (!validator(value)) {

FILE: vue/src/core/vdom/create-component.js
  method if (line 38) | if (
  method if (line 108) | if (isUndef(Ctor)) {
  method if (line 122) | if (process.env.NODE_ENV !== 'production') {
  function createComponentInstanceForVnode (line 208) | function createComponentInstanceForVnode (
  function installComponentHooks (line 226) | function installComponentHooks (data: VNodeData) {
  function mergeHook (line 238) | function mergeHook (f1: any, f2: any): Function {
  function transformModel (line 250) | function transformModel (options, data: any) {

FILE: vue/src/core/vdom/create-element.js
  constant SIMPLE_NORMALIZE (line 23) | const SIMPLE_NORMALIZE = 1
  constant ALWAYS_NORMALIZE (line 24) | const ALWAYS_NORMALIZE = 2
  method if (line 36) | if (Array.isArray(data) || isPrimitive(data)) {
  method if (line 41) | if (isTrue(alwaysNormalize)) {
  method if (line 54) | if (isDef(data) && isDef((data: any).__ob__)) {
  method if (line 63) | if (isDef(data) && isDef(data.is)) {
  method if (line 66) | if (!tag) {
  method if (line 71) | if (process.env.NODE_ENV !== 'production' &&
  method if (line 83) | if (Array.isArray(children) &&
  method if (line 90) | if (normalizationType === ALWAYS_NORMALIZE) {

FILE: vue/src/core/vdom/create-functional-component.js
  method for (line 89) | for (const key in propOptions) {
  function cloneAndMarkFunctionalResult (line 119) | function cloneAndMarkFunctionalResult (vnode, data, contextVm, options) {
  function mergeProps (line 132) | 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/is-async-placeholder.js
  function isAsyncPlaceholder (line 3) | function isAsyncPlaceholder (node: VNode): boolean {

FILE: vue/src/core/vdom/helpers/merge-hook.js
  function mergeVNodeHook (line 7) | 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) {
  function isTextNode (line 39) | function isTextNode (node): boolean {
  method if (line 53) | if (c.length > 0) {
  method if (line 78) | if (isTrue(children._isVList) &&

FILE: vue/src/core/vdom/helpers/resolve-async-component.js
  function ensureCtor (line 15) | function ensureCtor (comp: any, base) {
  function resolveAsyncComponent (line 40) | function resolveAsyncComponent (

FILE: vue/src/core/vdom/helpers/update-listeners.js
  method if (line 68) | if (isUndef(cur.fns)) {
  method if (line 78) | if (isUndef(on[name])) {

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 106) | function getRawDirName (dir: VNodeDirective): string {
  function callHook (line 110) | function callHook (dir, hook, vnode, oldVnode, isDestroy) {

FILE: vue/src/core/vdom/patch.js
  function sameVnode (line 35) | function sameVnode (a, b) {
  function sameInputType (line 52) | function sameInputType (a, b) {
  function createKeyToOldIdx (line 60) | function createKeyToOldIdx (children, beginIdx, endIdx) {
  function createPatchFunction (line 70) | function createPatchFunction (backend) {

FILE: vue/src/core/vdom/vnode.js
  function createTextVNode (line 80) | function createTextVNode (val: string | number) {
  function cloneVNode (line 88) | 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 65) | function genCheckboxModel (
  function genRadioModel (line 96) | function genRadioModel (
  function genSelect (line 108) | function genSelect (
  function type (line 125) | function genDefaultModel (

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/model.js
  function preTransformNode (line 26) | function preTransformNode (el: ASTElement, options: CompilerOptions) {
  function cloneASTElement (line 88) | function cloneASTElement (el) {

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/entry-runtime-with-compiler.js
  method if (line 37) | if (typeof template === 'string') {
  method if (line 51) | if (process.env.NODE_ENV !== 'production') {
  method if (line 61) | if (process.env.NODE_ENV !== 'production' && config.performance && mark) {

FILE: vue/src/platforms/web/runtime/components/transition-group.js
  method beforeMount (line 36) | beforeMount () {
  method if (line 62) | if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
  method if (line 132) | if (!hasTransition) {
  method if (line 136) | if (this._hasMove) {

FILE: vue/src/platforms/web/runtime/components/transition.js
  function componentOptions (line 34) | function getRealChild (vnode: ?VNode): ?VNode {
  function isSameChild (line 75) | function isSameChild (child: VNode, oldChild: VNode): boolean {

FILE: vue/src/platforms/web/runtime/directives/model.js
  method inserted (line 23) | inserted (el, binding, vnode, oldVnode) {
  method componentUpdated (line 52) | componentUpdated (el, binding, vnode) {
  function setSelected (line 75) | function setSelected (el, binding, vm) {
  function actuallySetSelected (line 85) | function actuallySetSelected (el, binding, vm) {
  function hasNoMatchingOption (line 120) | function hasNoMatchingOption (value, options) {
  function getValue (line 124) | function getValue (option) {
  function onCompositionStart (line 130) | function onCompositionStart (e) {
  function onCompositionEnd (line 134) | function onCompositionEnd (e) {
  function trigger (line 141) | function trigger (el, type) {

FILE: vue/src/platforms/web/runtime/directives/show.js
  function locateNode (line 6) | function locateNode (vnode: VNode): VNodeWithData {
  method if (line 56) | if (!isDestroy) {

FILE: vue/src/platforms/web/runtime/modules/attrs.js
  function updateAttrs (line 20) | function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function setAttr (line 61) | function setAttr (el: Element, key: string, value: any) {
  function baseSetAttr (line 90) | function baseSetAttr (el, key, value) {

FILE: vue/src/platforms/web/runtime/modules/class.js
  function updateClass (line 14) | 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 56) | function shouldUpdateValue (elm: acceptValueElm, checkVal: string): bool...
  function isNotInFocusAndDirty (line 64) | function isNotInFocusAndDirty (elm: acceptValueElm, checkVal: string): b...
  function isDirtyWithModifiers (line 74) | function isDirtyWithModifiers (elm: any, newVal: string): boolean {

FILE: vue/src/platforms/web/runtime/modules/events.js
  function normalizeEvents (line 12) | function normalizeEvents (on) {
  function createOnceHandler (line 31) | function createOnceHandler (handler, event, capture) {
  function add (line 41) | 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 176) | function leave (vnode: VNodeWithData, rm: Function) {
  function checkDuration (line 283) | function checkDuration (val, name, vnode) {
  function isValidDuration (line 299) | function isValidDuration (val) {
  function getHookArgumentsLength (line 309) | function getHookArgumentsLength (fn: Function): boolean {
  function _enter (line 326) | function _enter (_: any, vnode: VNodeWithData) {
  method if (line 337) | if (vnode.data.show !== true) {

FILE: vue/src/platforms/web/runtime/node-ops.js
  function createElement (line 5) | function createElement (tagName: string, vnode: VNode): Element {
  function createElementNS (line 17) | function createElementNS (namespace: string, tagName: string): Element {
  function createTextNode (line 21) | function createTextNode (text: string): Text {
  function createComment (line 25) | function createComment (text: string): Comment {
  function insertBefore (line 29) | function insertBefore (parentNode: Node, newNode: Node, referenceNode: N...
  function removeChild (line 33) | function removeChild (node: Node, child: Node) {
  function appendChild (line 37) | function appendChild (node: Node, child: Node) {
  function tagName (line 49) | function tagName (node: Element): string {
  function setTextContent (line 53) | function setTextContent (node: Node, text: string) {
  function setStyleScope (line 57) | function setStyleScope (node: Element, scopeId: 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 137) | if (transitionTimeout > 0) {
  method if (line 143) | if (animationTimeout > 0) {
  method while (line 174) | while (delays.length < durations.length) {

FILE: vue/src/platforms/web/server/directives/model.js
  function model (line 7) | function model (node: VNodeWithData, dir: VNodeDirective) {
  function getValue (line 31) | function getValue (option) {
  function setSelected (line 40) | function setSelected (option) {

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 17) | function renderAttrs (node: VNodeWithData): string {
  function renderAttr (line 46) | 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 46) | function setText (node, text, raw) {

FILE: vue/src/platforms/web/server/modules/style.js
  function genStyle (line 7) | function genStyle (style: Object): string {

FILE: vue/src/platforms/web/server/util.js
  constant ESC (line 38) | const ESC = {
  function escape (line 45) | function escape (s: string) {
  function escapeChar (line 49) | function escapeChar (a) {

FILE: vue/src/platforms/web/util/class.js
  function genClassForVnode (line 5) | function genClassForVnode (vnode: VNodeWithData): string {
  function mergeClassData (line 23) | function mergeClassData (child: VNodeData, parent: VNodeData): {
  function stringifyClass (line 50) | function stringifyClass (value: any): string {
  method if (line 68) | if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {

FILE: vue/src/platforms/web/util/compat.js
  function getShouldDecode (line 7) | function getShouldDecode (href: boolean): 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 13) | if (typeof el === 'string') {

FILE: vue/src/platforms/web/util/style.js
  function getStyle (line 43) | function getStyle (vnode: VNodeWithData, checkChild: boolean): Object {

FILE: vue/src/platforms/weex/compiler/modules/append.js
  function preTransformNode (line 9) | function preTransformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 19) | 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/recycle-list/component-root.js
  function postTransformComponentRoot (line 6) | function postTransformComponentRoot (

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/component.js
  function postTransformComponent (line 8) | function postTransformComponent (

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/index.js
  function shouldCompile (line 15) | function shouldCompile (el: ASTElement, options: WeexCompilerOptions) {
  function preTransformNode (line 20) | function preTransformNode (el: ASTElement, options: WeexCompilerOptions) {
  function transformNode (line 33) | function transformNode (el: ASTElement, options: WeexCompilerOptions) {
  function postTransformNode (line 39) | function postTransformNode (el: ASTElement, options: WeexCompilerOptions) {

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js
  function preTransformRecycleList (line 15) | function preTransformRecycleList (

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/text.js
  function genText (line 5) | function genText (node: ASTNode) {
  function postTransformText (line 16) | function postTransformText (el: ASTElement, options: WeexCompilerOptions) {

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js
  function parseAttrName (line 8) | function parseAttrName (name: string): string {
  function preTransformVBind (line 12) | function preTransformVBind (el: ASTElement, options: WeexCompilerOptions) {

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js
  function preTransformVFor (line 6) | function preTransformVFor (el: ASTElement, options: WeexCompilerOptions) {

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js
  function hasConditionDirective (line 6) | function hasConditionDirective (el: ASTElement): boolean {
  function getPreviousConditions (line 15) | function getPreviousConditions (el: ASTElement): Array<string> {
  function preTransformVIf (line 34) | function preTransformVIf (el: ASTElement, options: WeexCompilerOptions) {

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js
  function parseHandlerParams (line 5) | function parseHandlerParams (handler: ASTElementHandler) {

FILE: vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js
  function containVOnce (line 5) | function containVOnce (el: ASTElement): boolean {
  function preTransformVOnce (line 14) | function preTransformVOnce (el: ASTElement, options: WeexCompilerOptions) {

FILE: vue/src/platforms/weex/compiler/modules/style.js
  function transformNode (line 18) | function transformNode (el: ASTElement, options: CompilerOptions) {
  function genData (line 42) | function genData (el: ASTElement): string {
  function parseStaticStyle (line 53) | function parseStaticStyle (staticStyle: ?string, options: CompilerOption...

FILE: vue/src/platforms/weex/entry-framework.js
  function createInstanceContext (line 12) | function createInstanceContext (
  function destroyInstance (line 40) | function destroyInstance (instanceId: string): void {
  function refreshInstance (line 58) | function refreshInstance (
  function createVueModuleInstance (line 78) | function createVueModuleInstance (
  function getInstanceTimer (line 156) | function getInstanceTimer (

FILE: vue/src/platforms/weex/runtime/components/richtext.js
  function getVNodeType (line 3) | function getVNodeType (vnode: VNode): string {
  function isSimpleSpan (line 10) | function isSimpleSpan (vnode: VNode): boolean {
  function parseStyle (line 16) | function parseStyle (vnode: VNode): Object | void {
  method if (line 35) | if (!children.length) {

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 52) | function getStyle (oldClassList: Array<string>, classList: Array<string>...

FILE: vue/src/platforms/weex/runtime/modules/events.js
  method if (line 15) | if (capture) {
  method if (line 19) | if (once) {

FILE: vue/src/platforms/weex/runtime/modules/style.js
  function createStyle (line 7) | function createStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function updateStyle (line 29) | function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  function toObject (line 71) | function toObject (arr) {

FILE: vue/src/platforms/weex/runtime/modules/transition.js
  function enter (line 12) | function enter (_, vnode) {
  function leave (line 136) | function leave (vnode, rm) {
  function getEnterTargetState (line 232) | function getEnterTargetState (el, stylesheet, startClass, endClass, acti...

FILE: vue/src/platforms/weex/runtime/node-ops.js
  function createElement (line 8) | function createElement (tagName: string): WeexElement {
  function createElementNS (line 12) | function createElementNS (namespace: string, tagName: string): WeexEleme...
  function createTextNode (line 16) | function createTextNode (text: string) {
  function createComment (line 20) | function createComment (text: string) {
  function insertBefore (line 24) | function insertBefore (
  function removeChild (line 43) | function removeChild (node: WeexElement, child: WeexElement) {
  function appendChild (line 51) | function appendChild (node: WeexElement, child: WeexElement) {
  function tagName (line 75) | function tagName (node: WeexElement): string {
  function setTextContent (line 79) | function setTextContent (node: WeexElement, text: string) {
  function setAttribute (line 85) | function setAttribute (node: WeexElement, key: string, val: any) {
  function setStyleScope (line 89) | function setStyleScope (node: WeexElement, scopeId: string) {

FILE: vue/src/platforms/weex/runtime/recycle-list/render-component-template.js
  function isRecyclableComponent (line 9) | function isRecyclableComponent (vnode: VNodeWithData): boolean {
  function renderRecyclableComponentTemplate (line 15) | function renderRecyclableComponentTemplate (vnode: MountedComponentVNode...

FILE: vue/src/platforms/weex/runtime/recycle-list/virtual-component.js
  function initVirtualComponent (line 19) | function initVirtualComponent (options: Object = {}) {
  function updateVirtualComponent (line 86) | function updateVirtualComponent (vnode?: VNode) {
  function resolveVirtualComponent (line 103) | function resolveVirtualComponent (vnode: MountedComponentVNode): VNode {

FILE: vue/src/platforms/weex/runtime/text-node.js
  function TextNode (line 3) | function TextNode (text) {

FILE: vue/src/platforms/weex/util/index.js
  constant RECYCLE_LIST_MARKER (line 6) | const RECYCLE_LIST_MARKER = '@inRecycleList'
  function registerComponentHook (line 10) | function registerComponentHook (
  function updateComponentData (line 27) | function updateComponentData (

FILE: vue/src/platforms/weex/util/parser.js
  function nodeToBinding (line 11) | function nodeToBinding (node: Object): any {
  method Expression (line 54) | Expression (node) {

FILE: vue/src/server/bundle-renderer/create-bundle-renderer.js
  constant INVALID_MSG (line 12) | const INVALID_MSG =
  function createBundleRendererCreator (line 31) | function createBundleRendererCreator (

FILE: vue/src/server/bundle-renderer/create-bundle-runner.js
  function createSandbox (line 8) | function createSandbox (context) {
  function compileModule (line 25) | function compileModule (files, basedir, runInNewContext) {
  function deepClone (line 77) | function deepClone (val) {
  function createBundleRunner (line 91) | 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/create-basic-renderer.js
  function createBasicRenderer (line 7) | function createBasicRenderer ({

FILE: vue/src/server/create-renderer.js
  method if (line 61) | if (typeof context === 'function') {
  method if (line 65) | if (context) {
  method if (line 71) | if (!cb) {
  method if (line 102) | if (context) {

FILE: vue/src/server/optimizing-compiler/codegen.js
  constant RAW (line 35) | const RAW = 0
  constant INTERPOLATION (line 36) | const INTERPOLATION = 1
  constant EXPRESSION (line 37) | const EXPRESSION = 2
  function generate (line 39) | function generate (
  function genSSRElement (line 51) | function genSSRElement (el: ASTElement, state: CodegenState): string {
  function genNormalElement (line 81) | function genNormalElement (el, state, stringifyChildren) {
  function genSSRChildren (line 93) | function genSSRChildren (el, state, checkSkip) {
  function genSSRNode (line 97) | function genSSRNode (el, state) {
  function genChildrenAsStringNode (line 103) | function genChildrenAsStringNode (el, state) {
  function genStringElement (line 109) | function genStringElement (el, state) {
  function genStringElementWithChildren (line 113) | function genStringElementWithChildren (el, state) {
  function elementToString (line 122) | function elementToString (el, state) {
  function elementToSegments (line 126) | function elementToSegments (el, state): Array<StringSegment> {
  function elementToOpenTagSegments (line 153) | function elementToOpenTagSegments (el, state): Array<StringSegment> {
  function childrenToSegments (line 200) | function childrenToSegments (el, state): Array<StringSegment> {
  function nodesToSegments (line 216) | function nodesToSegments (

FILE: vue/src/server/optimizing-compiler/modules.js
  function applyModelTransform (line 28) | function applyModelTransform (el: ASTElement, state: CodegenState) {
  method if (line 67) | if (plainStringRE.test(value)) {
  method if (line 94) | if (staticClass && !classBinding) {
  method if (line 110) | if (staticStyle && !styleBinding && !vShowExpression) {

FILE: vue/src/server/optimizing-compiler/optimizer.js
  function walk (line 32) | function walk (node: ASTNode, isRoot?: boolean) {
  function optimizeSiblings (line 74) | function optimizeSiblings (el) {
  function isUnOptimizableTree (line 109) | function isUnOptimizableTree (node: ASTNode): boolean {
  function isSelectWithModel (line 133) | function isSelectWithModel (node: ASTNode): boolean {

FILE: vue/src/server/optimizing-compiler/runtime-helpers.js
  function installSSRHelpers (line 31) | function installSSRHelpers (vm: Component) {
  method for (line 92) | for (i = 0, l = val.length; i < l; i++) {
  method for (line 96) | for (i = 0; i < val; i++) {

FILE: vue/src/server/render-context.js
  function normalizeAsync (line 121) | 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 51) | function renderNode (node, isRoot, context) {
  function registerComponentForCache (line 73) | function registerComponentForCache (options, write) {
  function renderComponent (line 83) | function renderComponent (node, isRoot, context) {
  function renderComponentWithCache (line 142) | function renderComponentWithCache (node, isRoot, key, context) {
  function renderComponentInner (line 159) | function renderComponentInner (node, isRoot, context) {
  function renderAsyncComponent (line 177) | function renderAsyncComponent (node, isRoot, context) {
  function renderStringNode (line 242) | function renderStringNode (el, context) {
  function renderElement (line 259) | function renderElement (el, isRoot, context) {
  function hasAncestorData (line 291) | function hasAncestorData (node: VNode) {
  function tmp (line 296) | function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
  function renderStartingTag (line 312) | 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 51) | constructor (options: TemplateRendererOptions) {
  method bindRenderFns (line 72) | bindRenderFns (context: Object) {
  method if (line 217) | if (!context._mappedFiles && context._registeredComponents && this.mapFi...
  method if (line 226) | if (!this.parsedTemplate) {
  function getPreloadType (line 244) | function getPreloadType (ext: string): string {

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/util.js
  function createPromiseCallback (line 7) | function createPromiseCallback () {

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 22) | 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 1) | const SSR_ATTR = 'data-server-rendered'
  constant ASSET_TYPES (line 3) | const ASSET_TYPES = [
  constant LIFECYCLE_HOOKS (line 9) | const LIFECYCLE_HOOKS = [

FILE: vue/src/shared/util.js
  function toRawType (line 50) | function toRawType (value: any): string {
  function isPlainObject (line 58) | function isPlainObject (obj: any): boolean {
  function isRegExp (line 62) | function isRegExp (v: any): boolean {
  function isValidArrayIndex (line 69) | function isValidArrayIndex (val: any): boolean {
  function toString (line 77) | function toString (val: any): string {
  method if (line 126) | if (arr.length) {
  method for (line 224) | for (const key in _from) {
  method if (line 236) | if (arr[i]) {
  method String (line 300) | String(b)

FILE: vue/test/e2e/specs/grid.js
  function assertTable (line 93) | function assertTable (data) {

FILE: vue/test/e2e/specs/todomvc.js
  function createNewItem (line 156) | function createNewItem (text) {
  function removeItemAt (line 160) | function removeItemAt (n) {

FILE: vue/test/helpers/test-object-option.js
  function testObjectOption (line 3) | function testObjectOption (name) {

FILE: vue/test/helpers/to-have-been-warned.js
  function noop (line 1) | function noop () {}
  function createCompareFn (line 15) | function createCompareFn (spy) {

FILE: vue/test/helpers/wait-for-update.js
  function shift (line 19) | function shift () {
  function timeout (line 71) | function timeout (n) {

FILE: vue/test/ssr/compile-with-webpack.js
  function compileWithWebpack (line 5) | function compileWithWebpack (file, extraConfig, cb) {

FILE: vue/test/ssr/fixtures/app.js
  method render (line 7) | render (h) {

FILE: vue/test/ssr/fixtures/async-bar.js
  method beforeCreate (line 2) | beforeCreate () {
  method render (line 5) | render (h) {

FILE: vue/test/ssr/fixtures/async-foo.js
  method beforeCreate (line 7) | beforeCreate () {
  method render (line 10) | render (h) {

FILE: vue/test/ssr/fixtures/cache.js
  method render (line 7) | render (h) {

FILE: vue/test/ssr/fixtures/nested-cache.js
  function createRegisterFn (line 3) | function createRegisterFn (id) {
  function addHooks (line 10) | function addHooks (comp) {
  method render (line 22) | render (h) {
  method render (line 31) | render (h) {
  method render (line 40) | render (h) {

FILE: vue/test/ssr/fixtures/split.js
  method render (line 11) | render (h) {

FILE: vue/test/ssr/ssr-basic-renderer.spec.js
  method render (line 25) | render () {
  method testAsync (line 29) | testAsync (resolve) {
  method created (line 61) | created () {

FILE: vue/test/ssr/ssr-bundle-render.spec.js
  function createRenderer (line 6) | function createRenderer (file, options, cb) {
  function createAssertions (line 40) | function createAssertions (runInNewContext) {

FILE: vue/test/ssr/ssr-stream.spec.js
  method bComp (line 24) | bComp (resolve) {
  method render (line 42) | render (h) {
  method render (line 70) | render () {

FILE: vue/test/ssr/ssr-string.spec.js
  method data (line 433) | data () {
  method render (line 436) | render () {
  method beforeCreate (line 455) | beforeCreate () {
  method created (line 458) | created () {
  method beforeCreate (line 465) | beforeCreate () {
  method created (line 468) | created () {
  method render (line 471) | render () {
  method b (line 497) | b () {
  method created (line 501) | created () {
  method testAsync (line 519) | testAsync (resolve) {
  method render (line 578) | render () {
  method testAsync (line 597) | testAsync (resolve) {
  method testAsync (line 620) | testAsync (resolve) {
  method render (line 649) | render () {
  method render (line 682) | render () {
  method testAsync (line 686) | testAsync (resolve) {
  method render (line 811) | render () {
  method render (line 836) | render () {
  method render (line 861) | render () {
  method render (line 889) | render () {
  method foo (line 1033) | foo () { return this.bar + 1 }
  method created (line 1035) | created () {
  method render (line 1058) | render () {
  method render (line 1109) | render (h) {
  method render (line 1221) | render (h, ctx) {
  function renderVmWithOptions (line 1234) | function renderVmWithOptions (options, cb) {

FILE: vue/test/ssr/ssr-template.spec.js
  function generateClientManifest (line 11) | function generateClientManifest (file, cb) {
  function createRendererWithManifest (line 29) | function createRendererWithManifest (file, options, cb) {
  function createClientManifestAssertions (line 250) | function createClientManifestAssertions (runInNewContext) {

FILE: vue/test/unit/features/component/component-async.spec.js
  function next (line 22) | function next () {
  function next (line 49) | function next () {
  function next (line 73) | function next () {
  function step1 (line 107) | function step1 () {
  function step2 (line 115) | function step2 () {
  function next (line 156) | function next () {
  function next (line 183) | function next () {
  function next (line 221) | function next () {
  function next (line 250) | function next () {
  function next (line 279) | function next () {
  function next (line 317) | function next () {
  method mounted (line 355) | mounted () {

FILE: vue/test/unit/features/component/component-keep-alive.spec.js
  function assertHookCalls (line 34) | function assertHookCalls (component, callCounts) {
  function sharedAssertions (line 212) | function sharedAssertions (vm, done) {
  function assertCount (line 517) | function assertCount (calls) {
  method render (line 597) | render (h, ctx) {
  function assert (line 614) | function assert (foo, bar) {
  method render (line 649) | render (h, ctx) {
  function assert (line 666) | function assert (foo, bar) {
  method afterLeave (line 732) | afterLeave () {
  method afterLeave (line 817) | afterLeave () {
  method afterEnter (line 902) | afterEnter () {
  method afterEnter (line 995) | afterEnter () {
  method afterEnter (line 1134) | afterEnter () {
  method afterLeave (line 1137) | afterLeave () {

FILE: vue/test/unit/features/component/component-scoped-slot.spec.js
  method data (line 15) | data () {
  method data (line 43) | data () {
  method data (line 73) | data () {
  method data (line 106) | data () {
  method data (line 132) | data () {
  method data (line 161) | data () {
  method data (line 189) | data () {
  method data (line 213) | data () {
  method data (line 240) | data () {
  function assertOutput (line 254) | function assertOutput () {
  method data (line 278) | data () {
  function assertOutput (line 294) | function assertOutput () {
  method data (line 316) | data () {
  method data (line 341) | data () {
  method data (line 366) | data () {
  method render (line 384) | render (h) {
  method data (line 394) | data () {
  method render (line 397) | render (h) {
  method render (line 417) | render (h) {
  method data (line 424) | data () {
  method render (line 427) | render (h) {
  method render (line 475) | render (h) {
  method data (line 482) | data () {
  method render (line 485) | render (h) {
  method data (line 576) | data () {

FILE: vue/test/unit/features/component/component-slot.spec.js
  function mount (line 5) | function mount (options) {
  method render (line 303) | render () {
  method render (line 333) | render () {
  method render (line 407) | render (h, ctx) {
  method data (line 428) | data () {
  method data (line 455) | data () {
  method created (line 462) | created () {
  method destroyed (line 465) | destroyed () {
  method render (line 581) | render (h, { slots }) {
  method render (line 616) | render (h) {
  method render (line 623) | render (h) {
  method render (line 630) | render (h) {
  method data (line 697) | data () {
  method render (line 789) | render (h) {
  method render (line 796) | render (h) {
  method render (line 831) | render (h, { slots }) {
  method render (line 838) | render (h, { children }) {
  method data (line 867) | data () {
  method render (line 870) | render (h) {

FILE: vue/test/unit/features/component/component.spec.js
  method data (line 9) | data () {
  method data (line 25) | data () {
  method data (line 40) | data () {
  method data (line 58) | data () {
  method data (line 72) | data () {
  method data (line 91) | data () {
  method data (line 97) | data () {
  method data (line 218) | data () {
  method data (line 246) | data () {
  method created (line 303) | created () {
  method data (line 330) | data () {
  method render (line 333) | render (h) {
  method render (line 371) | render (h) {
  method render (line 395) | render (h) {
  method mounted (line 398) | mounted () {
  method beforeDestroy (line 401) | beforeDestroy () {
  method data (line 409) | data () {

FILE: vue/test/unit/features/directives/bind.spec.js
  method handleUpdate (line 203) | handleUpdate () {
  function makeInstance (line 360) | function makeInstance (options) {

FILE: vue/test/unit/features/directives/class.spec.js
  function assertClass (line 3) | function assertClass (assertions, done) {
  method render (line 114) | render () {
  method render (line 119) | render () {

FILE: vue/test/unit/features/directives/for.spec.js
  function assertMarkup (line 332) | function assertMarkup () {
  function assertMarkup (line 372) | function assertMarkup () {
  method render (line 415) | render () {}

FILE: vue/test/unit/features/directives/if.spec.js
  method data (line 225) | data () {

FILE: vue/test/unit/features/directives/model-dynamic.spec.js
  function assertInputWorks (line 182) | function assertInputWorks (vm, type, chain) {

FILE: vue/test/unit/features/directives/model-select.spec.js
  function hasMultiSelectBug (line 6) | function hasMultiSelectBug () {
  function updateSelect (line 20) | function updateSelect (el, value) {
  function getValue (line 31) | function getValue (option) {
  method get (line 530) | get () {
  method set (line 533) | set () {
  function makeInstance (line 552) | function makeInstance (foo) {

FILE: vue/test/unit/features/directives/model-text.spec.js
  method onInput (line 240) | onInput (e) {
  method onInput (line 327) | onInput (e) {
  method onInput (line 348) | onInput (e) {
  method onInput (line 398) | onInput (e) {

FILE: vue/test/unit/features/directives/on.spec.js
  method foo (line 95) | foo ($event) {
  method foo (line 115) | foo () { callOrder.push(1) }
  method bar (line 116) | bar () { callOrder.push(2) }
  method foo (line 171) | foo () { callOrder.push(1) }
  method bar (line 172) | bar () { callOrder.push(2) }
  method render (line 488) | render (h) {
  method stopped (line 510) | stopped (ev) { ev.stopPropagation() }
  method render (line 514) | render (h) {
  method render (line 540) | render (h) {
  method stopped (line 564) | stopped (ev) { ev.stopPropagation() }
  method render (line 568) | render (h) {
  method render (line 601) | render (h) {
  method foo (line 685) | foo (e) {
  method foo (line 713) | foo ($event) {
  method created (line 754) | created () {
  method created (line 778) | created () {
  method get (line 853) | get () {
  method set (line 856) | set (val) {
  method change (line 875) | change () {

FILE: vue/test/unit/features/directives/once.spec.js
  function expectTextContent (line 362) | function expectTextContent (vm, text) {

FILE: vue/test/unit/features/directives/style.spec.js
  function checkPrefixedProp (line 3) | function checkPrefixedProp (prop) {
  method data (line 24) | data () {

FILE: vue/test/unit/features/error-handling.spec.js
  function getErrorMsg (line 70) | function getErrorMsg () {
  method render (line 133) | render (h) {
  method renderError (line 136) | renderError (h, err) {
  function createErrorTestComponents (line 147) | function createErrorTestComponents () {
  function createTestInstance (line 253) | function createTestInstance (Comp) {
  function assertRootInstanceActive (line 270) | function assertRootInstanceActive (vm, chain) {
  function assertBothInstancesActive (line 278) | function assertBothInstancesActive (vm) {

FILE: vue/test/unit/features/global-api/extend.spec.js
  method created (line 57) | created () {
  method created (line 62) | created () {
  method created (line 67) | created () {
  method a (line 77) | a () { return this.n }
  method b (line 82) | b () { return this.n + 1 }
  method c (line 88) | c () { return this.n + 2 }

FILE: vue/test/unit/features/global-api/mixin.spec.js
  method created (line 11) | created () {
  method beforeCreate (line 25) | beforeCreate () {
  method beforeCreate (line 30) | beforeCreate () {
  method created (line 132) | created () {

FILE: vue/test/unit/features/global-api/set-delete.spec.js
  class Model (line 86) | class Model {
    method constructor (line 87) | constructor () {
    method bar (line 90) | get bar () {
    method bar (line 93) | set bar (newvalue) {

FILE: vue/test/unit/features/instance/properties.spec.js
  method a (line 22) | a () {}
  method b (line 27) | b () {}
  method created (line 62) | created () {
  method render (line 116) | render () {}
  method mounted (line 117) | mounted () {

FILE: vue/test/unit/features/instance/render-proxy.spec.js
  method render (line 25) | render (h) {
  method created (line 36) | created () {
  method render (line 39) | render (h) {

FILE: vue/test/unit/features/options/_scopeId.spec.js
  method render (line 76) | render (h) {

FILE: vue/test/unit/features/options/comments.spec.js
  method data (line 7) | data () {

FILE: vue/test/unit/features/options/computed.spec.js
  method b (line 14) | b () {
  method get (line 36) | get () { return this.a + 1 }
  method set (line 37) | set (v) { this.a = v - 1 }
  method data (line 63) | data () {
  method set (line 70) | set (v) { this.a = v }
  method b (line 84) | b () {
  method b (line 100) | b () { return this.a + 1 }
  method b (line 117) | b () {
  method get (line 139) | get () {
  method data (line 156) | data () {
  method b (line 161) | b () {
  method c (line 170) | c () {
  method a (line 229) | a () {

FILE: vue/test/unit/features/options/data.spec.js
  method data (line 20) | data () {
  method data (line 38) | data () {
  method data (line 66) | data () {}
  method render (line 82) | render () {}
  method data (line 83) | data () {
  method beforeUpdate (line 103) | beforeUpdate () { calls++ }
  method data (line 108) | data () {
  method computedMsg (line 112) | computedMsg () {
  method get (line 131) | get () {
  method data (line 135) | data () {
  method data (line 150) | data ({ foo }) {

FILE: vue/test/unit/features/options/directives.spec.js
  method bind (line 26) | bind (el, binding, vnode) {
  method inserted (line 34) | inserted (el, binding, vnode) {
  method update (line 42) | update (el, binding, vnode, oldVnode) {
  method componentUpdated (line 53) | componentUpdated (el, binding, vnode) {
  method unbind (line 57) | unbind (el, binding, vnode) {
  method test (line 91) | test (el, binding, vnode) {

FILE: vue/test/unit/features/options/el.spec.js
  method render (line 32) | render (h) {

FILE: vue/test/unit/features/options/errorCaptured.spec.js
  method created (line 20) | created () {
  method render (line 25) | render () {}
  method created (line 41) | created () {
  method render (line 45) | render () {}
  method errorCaptured (line 52) | errorCaptured (e, vm, info) {
  method render (line 56) | render (h) {
  method created (line 76) | created () {
  method render (line 81) | render () {}
  method errorCaptured (line 85) | errorCaptured (err, vm, info) {
  method created (line 101) | created () {
  method render (line 106) | render () {}
  method errorCaptured (line 111) | errorCaptured () {
  method created (line 126) | created () {
  method render (line 129) | render () {}
  method errorCaptured (line 133) | errorCaptured () {
  method errorCaptured (line 139) | errorCaptured () {
  method errorCaptured (line 147) | errorCaptured () {
  method errorCaptured (line 158) | errorCaptured () {
  method created (line 171) | created () {
  method render (line 174) | render () {}
  method errorCaptured (line 178) | errorCaptured () {
  method errorCaptured (line 184) | errorCaptured () {
  method errorCaptured (line 192) | errorCaptured () {
  method errorCaptured (line 204) | errorCaptured () {

FILE: vue/test/unit/features/options/extends.spec.js
  method data (line 7) | data () {
  method data (line 13) | data () {
  method data (line 30) | data () {
  method data (line 36) | data () {

FILE: vue/test/unit/features/options/functional.spec.js
  method render (line 13) | render (h, { props, children }) {
  method render (line 29) | render (h, { props }) {
  method render (line 36) | render (h) {
  method render (line 62) | render (h, { listeners }) {
  method render (line 86) | render (h) {
  method render (line 103) | render (h, { slots }) {
  method render (line 126) | render (h, { props, children, data: { on }}) {
  method render (line 133) | render () {
  method mounted (line 136) | mounted () {
  method destroyed (line 139) | destroyed () {
  method onInput (line 143) | onInput (e) {
  method validate (line 150) | validate (val) {
  method render (line 175) | render () {
  method render (line 193) | render (h) {
  method fn (line 295) | fn () {
  function assertMarkup (line 301) | function assertMarkup () {

FILE: vue/test/unit/features/options/inject.spec.js
  method render (line 12) | render () {}
  method created (line 13) | created () {
  method provide (line 72) | provide () {
  method render (line 97) | render () {}
  method created (line 98) | created () {
  method data (line 132) | data () {
  method default (line 139) | default () {
  method render (line 163) | render (h, context) {
  method data (line 197) | data () {
  method provide (line 202) | provide () {
  method created (line 236) | created () {
  method created (line 262) | created () {
  method render (line 268) | render (h) {
  method created (line 283) | created () {
  method render (line 289) | render (h) {
  method created (line 304) | created () {
  method render (line 310) | render (h) {
  method created (line 344) | created () {}
  method created (line 361) | created () {}
  method created (line 386) | created () {
  method created (line 406) | created () {
  method created (line 428) | created () {
  method created (line 442) | created () {
  method render (line 448) | render (h) {
  method created (line 462) | created () {
  method render (line 468) | render (h) {
  method created (line 484) | created () {
  method render (line 490) | render (h) {
  method created (line 504) | created () {
  method render (line 510) | render (h) {
  method created (line 523) | created () {
  method render (line 530) | render (h) {
  function isObserver (line 542) | function isObserver (obj) {
  method data (line 551) | data () {
  method provide (line 558) | provide () {
  method render (line 598) | render (h) {
  method render (line 605) | render (h) {
  method render (line 611) | render (h) {
  method created (line 623) | created () {
  method render (line 629) | render (h) {

FILE: vue/test/unit/features/options/lifecycle.spec.js
  method beforeCreate (line 15) | beforeCreate () {
  method created (line 36) | created () {
  method render (line 48) | render () {}
  method beforeMount (line 49) | beforeMount () {
  method mounted (line 67) | mounted () {
  method mounted (line 86) | mounted () {
  method mounted (line 97) | mounted () {
  method mounted (line 103) | mounted () {
  method mounted (line 110) | mounted () {
  method beforeUpdate (line 128) | beforeUpdate () {
  method beforeUpdate (line 145) | beforeUpdate () {
  method updated (line 162) | updated () {
  method updated (line 183) | updated () {
  method updated (line 189) | updated () {
  method render (line 207) | render () {}
  method beforeDestroy (line 208) | beforeDestroy () {
  method render (line 225) | render () {}
  method destroyed (line 226) | destroyed () {
  method render (line 245) | render () {}
  method beforeCreate (line 246) | beforeCreate () {

FILE: vue/test/unit/features/options/methods.spec.js
  method plus (line 13) | plus () {
  method foo (line 37) | foo () {}
  method _update (line 46) | _update () {}

FILE: vue/test/unit/features/options/parent.spec.js
  method render (line 6) | render () {}
  method render (line 11) | render () {}

FILE: vue/test/unit/features/options/props.spec.js
  function makeInstance (line 149) | function makeInstance (value, type, validator, required) {
  function Class (line 233) | function Class () {}
  method a (line 343) | a () {

FILE: vue/test/unit/features/options/render.spec.js
  method render (line 6) | render (h) {
  method render (line 27) | render (h) {

FILE: vue/test/unit/features/options/renderError.spec.js
  method render (line 10) | render (h) {
  method renderError (line 17) | renderError (h, err) {
  method render (line 33) | render () {
  method renderError (line 36) | renderError () {

FILE: vue/test/unit/features/ref.spec.js
  method render (line 58) | render (h) {
  function assertRefs (line 117) | function assertRefs () {
  function assertRefs (line 149) | function assertRefs () {
  method data (line 160) | data () {
  function assertRefs (line 195) | function assertRefs () {
  method data (line 212) | data () {

FILE: vue/test/unit/features/transition/inject-styles.js
  function insertCSS (line 1) | function insertCSS (text) {
  function injectStyles (line 11) | function injectStyles () {

FILE: vue/test/unit/features/transition/transition-group.spec.js
  function createBasicVM (line 16) | function createBasicVM (useIs, appear) {
  method beforeEnter (line 195) | beforeEnter (el) {
  method afterEnter (line 199) | afterEnter (el) {
  method afterLeave (line 204) | afterLeave (el) {

FILE: vue/test/unit/features/transition/transition-mode.spec.js
  method afterLeave (line 62) | afterLeave () {
  method afterLeave (line 107) | afterLeave () {
  method afterEnter (line 154) | afterEnter () {
  method afterEnter (line 204) | afterEnter () {
  method afterLeave (line 299) | afterLeave () {
  method afterEnter (line 342) | afterEnter () {
  function next1 (line 411) | function next1 () {
  function next2 (line 427) | function next2 () {
  function next1 (line 526) | function next1 () {
  function next2 (line 541) | function next2 () {

FILE: vue/test/unit/features/transition/transition.spec.js
  method render (line 143) | render (h) {
  method render (line 890) | render (h, { data, children }) {

FILE: vue/test/unit/modules/compiler/codegen.spec.js
  function assertCodegen (line 8) | function assertCodegen (template, generatedCode, ...args) {

FILE: vue/test/unit/modules/compiler/compiler-options.spec.js
  method validate (line 13) | validate (el, dir) {
  method transformNode (line 24) | transformNode (el) {
  method genData (line 34) | genData (el) {
  method transformCode (line 44) | transformCode (el, code) {
  method render (line 78) | render (h) {
  method valid (line 82) | valid () {
  method mounted (line 94) | mounted () {
  method required (line 104) | required (val) {
  method max (line 107) | max (val, rule) {

FILE: vue/test/unit/modules/compiler/parser.spec.js
  method preTransformNode (line 603) | preTransformNode (el) {
  method postTransformNode (line 606) | postTransformNode (el) {

FILE: vue/test/unit/modules/observer/observer.spec.js
  method get (line 67) | get () {
  method set (line 71) | set (v) { val = v }
  method get (line 101) | get () { return 123 }
  method set (line 132) | set (v) { val = v }
  method addDep (line 185) | addDep (dep) {
  method get (line 221) | get () { return this.val }
  method set (line 222) | set (v) {

FILE: vue/test/unit/modules/observer/scheduler.spec.js
  function queueWatcher (line 7) | function queueWatcher (watcher) {
  method run (line 49) | run () { queueWatcher(job) }
  method a (line 64) | a () { calls.push(1) }
  method beforeUpdate (line 66) | beforeUpdate () {
  method a (line 84) | a () {
  method beforeUpdate (line 88) | beforeUpdate () {
  method a (line 97) | a () {
  method beforeUpdate (line 101) | beforeUpdate () {
  method run (line 117) | run () {
  method run (line 134) | run () {
  method updated (line 165) | updated () {

FILE: vue/test/unit/modules/vdom/create-component.spec.js
  method data (line 9) | data () {
  method render (line 20) | render () {}
  function async (line 48) | function async (resolve, reject) {
  function go (line 57) | function go () {
  function loaded (line 62) | function loaded () {
  function async (line 84) | function async (resolve, reject) {
  function go (line 90) | function go () {
  function failed (line 94) | function failed () {

FILE: vue/test/unit/modules/vdom/create-element.spec.js
  method render (line 195) | render (h) {
  method render (line 204) | render (h) {
  method render (line 213) | render (h) {
  method render (line 222) | render (h) {
  method render (line 232) | render (h) {
  method render (line 262) | render (h) {

FILE: vue/test/unit/modules/vdom/modules/attrs.spec.js
  method render (line 86) | render (h) {

FILE: vue/test/unit/modules/vdom/modules/dom-props.spec.js
  method render (line 74) | render (h) {

FILE: vue/test/unit/modules/vdom/patch/children.spec.js
  function prop (line 4) | function prop (name) {
  function map (line 8) | function map (fn, list) {
  function spanNum (line 16) | function spanNum (n) {
  function shuffle (line 24) | function shuffle (array) {
  function spanNumWithOpacity (line 242) | function spanNumWithOpacity (n, o) {
  function makeNode (line 449) | function makeNode (text) {
  function makeNode (line 470) | function makeNode (text) {
  function makeNode (line 512) | function makeNode (key) {
  function makeNode (line 522) | function makeNode (key) {

FILE: vue/test/unit/modules/vdom/patch/edge-cases.spec.js
  method addFive (line 43) | addFive ($event, toAdd = 0) {
  method bind (line 49) | bind (el, binding, vnode) {
  method data (line 108) | data () {
  method render (line 111) | render (h) {
  method render (line 139) | render (h) {
  method render (line 152) | render (h) {
  method data (line 245) | data () {
  method get (line 283) | get () {
  method render (line 289) | render (h) {
  method render (line 355) | render () {}
  method render (line 381) | render (h) {
  method render (line 401) | render (h) {

FILE: vue/test/unit/modules/vdom/patch/hooks.spec.js
  function insert (line 19) | function insert (vnode) {
  function prepatch (line 38) | function prepatch (oldVnode, newVnode) {
  function prepatch (line 65) | function prepatch (oldVnode, newVnode) {
  function postpatch (line 68) | function postpatch (oldVnode, newVnode) {
  function cb (line 95) | function cb (result, oldVnode, newVnode) {
  function remove (line 123) | function remove (vnode, rm) {
  function init (line 148) | function init (vnode) { count++ }
  function prepatch (line 149) | function prepatch (oldVnode, newVnode) { count++ }
  method remove (line 163) | remove (_, rm) { rm1 = rm }
  method remove (line 164) | remove (_, rm) { rm2 = rm }
  method remove (line 168) | remove (_, rm) { rm3 = rm }
  function remove (line 188) | function remove (vnode, rm) {
  function destroy (line 207) | function destroy (vnode) { result.push(vnode) }
  method create (line 234) | create () { created++ }
  method destroy (line 235) | destroy () { destroyed++ }
  method create (line 259) | create () { created++ }
  method remove (line 260) | remove () { removed++ }
  method create (line 281) | create () { created++ }
  method destroy (line 282) | destroy () { destroyed++ }
  function create (line 305) | function create (empty, vnode) {

FILE: vue/test/unit/modules/vdom/patch/hydration.spec.js
  function createMockSSRDOM (line 6) | function createMockSSRDOM (innerHTML) {
  function init (line 22) | function init (vnode) { result.push(vnode) }
  function createServerRenderedDOM (line 23) | function createServerRenderedDOM () {
  function traverseAndAssert (line 49) | function traverseAndAssert (vnode, element) {
  function createServerRenderedDOM (line 74) | function createServerRenderedDOM () {
  method data (line 112) | data () {
  method data (line 171) | data () {
  method data (line 191) | data () {

FILE: vue/test/weex/cases/cases.spec.js
  function createRenderTestCase (line 15) | function createRenderTestCase (name) {
  function createEventTestCase (line 32) | function createEventTestCase (name) {

FILE: vue/test/weex/helpers/index.js
  function readFile (line 12) | function readFile (filename) {
  function readObject (line 16) | function readObject (filename) {
  function strToRegExp (line 24) | function strToRegExp (str) {
  function parseStatic (line 28) | function parseStatic (fns) {
  function compileAndStringify (line 32) | function compileAndStringify (template) {
  function compileVue (line 45) | function compileVue (source, componentName) {
  function compileWithDeps (line 93) | function compileWithDeps (entryPath, deps) {
  function isObject (line 107) | function isObject (object) {
  function isEmptyObject (line 111) | function isEmptyObject (object) {
  function omitUseless (line 115) | function omitUseless (object) {
  function getRoot (line 134) | function getRoot (instance) {
  function getEvents (line 139) | function getEvents (instance) {
  function fireEvent (line 156) | function fireEvent (instance, ref, type, event = {}) {
  function createInstance (line 163) | function createInstance (id, code, ...args) {
  function compileAndExecute (line 183) | function compileAndExecute (template, additional = '') {
  function syncPromise (line 199) | function syncPromise (arr) {
  function checkRefresh (line 207) | function checkRefresh (instance, data, checker) {
  function addTaskHook (line 217) | function addTaskHook (hook) {
  function resetTaskHook (line 231) | function resetTaskHook () {

FILE: vue/test/weex/runtime/components/richtext.spec.js
  function compileSnippet (line 8) | function compileSnippet (snippet, additional) {

FILE: vuex/build/build.main.js
  function build (line 14) | function build (builds) {
  function buildEntry (line 29) | function buildEntry ({ input, output }) {
  function write (line 49) | function write (dest, code, zip) {
  function getSize (line 70) | function getSize (code) {
  function logError (line 74) | function logError (e) {
  function blue (line 78) | function blue (str) {

FILE: vuex/build/configs.js
  function genConfig (line 39) | function genConfig (opts) {
  function mapValues (line 67) | function mapValues (obj, fn) {

FILE: vuex/src/helpers.js
  function normalizeMap (line 131) | function normalizeMap (map) {
  function normalizeNamespace (line 142) | function normalizeNamespace (fn) {
  function getModuleByNamespace (line 161) | function getModuleByNamespace (store, helper, namespace) {

FILE: vuex/src/mixin.js
  function vuexInit (line 22) | function vuexInit () {

FILE: vuex/src/module/module-collection.js
  class ModuleCollection (line 4) | class ModuleCollection {
    method constructor (line 5) | constructor (rawRootModule) {
    method get (line 10) | get (path) {
    method getNamespace (line 16) | getNamespace (path) {
    method update (line 24) | update (rawRootModule) {
    method register (line 28) | register (path, rawModule, runtime = true) {
    method unregister (line 49) | unregister (path) {
  function update (line 58) | function update (path, targetModule, newModule) {
  function assertRawModule (line 104) | function assertRawModule (path, rawModule) {
  function makeAssertionMessage (line 119) | function makeAssertionMessage (path, key, type, value, expected) {

FILE: vuex/src/module/module.js
  class Module (line 4) | class Module {
    method constructor (line 5) | constructor (rawModule, runtime) {
    method namespaced (line 17) | get namespaced () {
    method addChild (line 21) | addChild (key, module) {
    method removeChild (line 25) | removeChild (key) {
    method getChild (line 29) | getChild (key) {
    method update (line 33) | update (rawModule) {
    method forEachChild (line 46) | forEachChild (fn) {
    method forEachGetter (line 50) | forEachGetter (fn) {
    method forEachAction (line 56) | forEachAction (fn) {
    method forEachMutation (line 62) | forEachMutation (fn) {

FILE: vuex/src/plugins/devtool.js
  function devtoolPlugin (line 5) | function devtoolPlugin (store) {

FILE: vuex/src/plugins/logger.js
  function createLogger (line 5) | function createLogger ({
  function repeat (line 53) | function repeat (str, times) {
  function pad (line 57) | function pad (num, maxLength) {

FILE: vuex/src/store.js
  class Store (line 8) | class Store {
    method constructor (line 9) | constructor (options = {}) {
    method state (line 71) | get state () {
    method state (line 75) | set state (v) {
    method commit (line 81) | commit (_type, _payload, _options) {
    method dispatch (line 115) | dispatch (_type, _payload) {
    method subscribe (line 138) | subscribe (fn) {
    method subscribeAction (line 142) | subscribeAction (fn) {
    method watch (line 146) | watch (getter, cb, options) {
    method replaceState (line 153) | replaceState (state) {
    method registerModule (line 159) | registerModule (path, rawModule, options = {}) {
    method unregisterModule (line 173) | unregisterModule (path) {
    method hotUpdate (line 188) | hotUpdate (newOptions) {
    method _withCommit (line 193) | _withCommit (fn) {
  function genericSubscribe (line 201) | function genericSubscribe (fn, subs) {
  function resetStore (line 213) | function resetStore (store, hot) {
  function resetStoreVM (line 225) | function resetStoreVM (store, state, hot) {
  function installModule (line 271) | function installModule (store, rootState, path, module, hot) {
  function makeLocalContext (line 316) | function makeLocalContext (store, namespace, path) {
  function makeLocalGetters (line 369) | function makeLocalGetters (store, namespace) {
  function registerMutation (line 392) | function registerMutation (store, type, handler, local) {
  function registerAction (line 399) | function registerAction (store, type, handler, local) {
  function registerGetter (line 424) | function registerGetter (store, type, rawGetter, local) {
  function enableStrictMode (line 441) | function enableStrictMode (store) {
  function getNestedState (line 449) | function getNestedState (state, path) {
  function unifyObjectStyle (line 455) | function unifyObjectStyle (type, payload, options) {
  function install (line 469) | 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 60) | function isPromise (val) {
  function assert (line 64) | function assert (condition, msg) {

FILE: vuex/test/e2e/specs/todomvc.js
  function createNewItem (line 144) | function createNewItem (text) {
  function removeItemAt (line 148) | function removeItemAt (n) {

FILE: vuex/test/unit/helpers.spec.js
  method inc (line 140) | inc (state, amount) {
  method plus (line 148) | plus (commit, amount) {
  method inc (line 190) | inc (state, amount) {
  method plus (line 200) | plus (commit, amount) {
  method foo (line 405) | foo (dispatch, arg) {
  method foo (line 455) | foo (dispatch, arg) {

FILE: vuex/test/unit/hot-reload.spec.js
  constant TEST (line 4) | const TEST = 'TEST'
  method [TEST] (line 10) | [TEST] (state, n) {
  method [TEST] (line 59) | [TEST] (state, n) {
  method [TEST] (line 114) | [TEST] (state, n) {
  method [TEST] (line 122) | [TEST] (state, n) {
  method [TEST] (line 130) | [TEST] (state, n) {
  method [TEST] (line 140) | [TEST] (state, n) {
  method [TEST] (line 148) | [TEST] (state, n) {
  method [TEST] (line 160) | [TEST] (state, n) {
  method [TEST] (line 182) | [TEST] (state, n) {
  method [TEST] (line 187) | [TEST] ({ commit }) {
  method [TEST] (line 194) | [TEST] ({ commit }) {
  method [TEST] (line 207) | [TEST] ({ commit }) {
  method [TEST] (line 218) | [TEST] ({ commit }) {
  method [TEST] (line 225) | [TEST] ({ commit }) {
  method check (line 248) | check ({ getters }, value) {

FILE: vuex/test/unit/modules.spec.js
  constant TEST (line 4) | const TEST = 'TEST'
  method state (line 132) | state () {
  method [TEST] (line 136) | [TEST] (state, n) {
  method state (line 159) | state () {
  method [TEST] (line 163) | [TEST] (state, n) {
  method [TEST] (line 195) | [TEST] (state, n) {
  method [TEST] (line 246) | [TEST] ({ state, rootState }) {
  method test (line 482) | test ({ dispatch, commit, getters, rootGetters }) {
  method test (line 528) | test ({ dispatch, commit, getters }) {
  method [TEST] (line 555) | [TEST] () {
  method [TEST] (line 562) | [TEST] () {
  method handler (line 584) | handler () {
  method handler (line 595) | handler () {
  method handler (line 605) | handler () {
  method [TEST] (line 615) | [TEST] () {
  method [TEST] (line 641) | [TEST] (state, n) {

FILE: vuex/test/unit/store.spec.js
  constant TEST (line 4) | const TEST = 'TEST'
  method [TEST] (line 14) | [TEST] (state, n) {
  method [TEST] (line 29) | [TEST] (state, payload) {
  method undefined (line 49) | undefined (state, n) {
  method [TEST] (line 66) | [TEST] (state, n) {
  method [TEST] (line 71) | [TEST] ({ commit }, n) {
  method [TEST] (line 86) | [TEST] (state, n) {
  method [TEST] (line 91) | [TEST] ({ commit }, payload) {
  method [TEST] (line 109) | [TEST] (state, n) {
  method [TEST] (line 114) | [TEST] ({ commit }, n) {
  method [TEST] (line 137) | [TEST] (state, n) {
  method [TEST] (line 142) | [TEST] ({ commit }, n) {
  method [TEST] (line 167) | [TEST] () {
  method [TEST] (line 195) | [TEST] (state, n) {
  method undefined (line 202) | undefined ({ commit }, n) {
  method [TEST] (line 222) | [TEST] (state, n) {
  method check (line 227) | check ({ getters }, value) {
  method [TEST] (line 256) | [TEST] () {}
  method [TEST] (line 279) | [TEST] (state, n) {

FILE: vuex/test/unit/util.spec.js
  function plus (line 50) | function plus (value, key) {
Condensed preview — 569 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,202K chars).
[
  {
    "path": ".flowconfig",
    "chars": 783,
    "preview": "[ignore]\n.*/node_modules/.*\n.*/test/.*\n.*/build/.*\n.*/examples/.*\n.*/benchmarks/.*\n\n[include]\n\n[libs]\nflow\n\n[options]\nun"
  },
  {
    "path": ".gitignore",
    "chars": 44,
    "preview": ".idea/\nnode_modules\ndist\nexample/\n.DS_Store\n"
  },
  {
    "path": "LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2017 HuangYi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 1232,
    "preview": "# Vue.js 技术揭秘\n\n[电子书](https://ustbhuangyi.github.io/vue-analysis/)\n\n目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细"
  },
  {
    "path": "build.sh",
    "chars": 172,
    "preview": "#!/usr/bin/env sh\n\nset -e\nnpm run build\n\ncd dist\n\ngit init\ngit add -A\ngit commit -m 'deploy'\n\ngit push -f git@github.com"
  },
  {
    "path": "docs/.vuepress/config.js",
    "chars": 4494,
    "preview": "module.exports = {\n  base: '/vue-analysis/',\n  dest: 'dist',\n  title: 'Vue.js 技术揭秘',\n  description: 'Analysis vue.js dee"
  },
  {
    "path": "docs/.vuepress/public/manifest.json",
    "chars": 424,
    "preview": "{\n  \"name\": \"VueAnalysis\",\n  \"short_name\": \"VueAnalysis\",\n  \"icons\": [\n    {\n      \"src\": \"/icons/android-chrome-192x192"
  },
  {
    "path": "docs/README.md",
    "chars": 1145,
    "preview": "## 前言\n\n目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理,让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 V"
  },
  {
    "path": "docs/v2/compile/codegen.md",
    "chars": 13658,
    "preview": "# codegen\n\n编译的最后一步就是把优化后的 AST 树转换成可执行的代码,这部分内容也比较多,我并不打算把所有的细节都讲了,了解整体流程即可。部分细节我们会在之后的章节配合一个具体 case 去详细讲。\n\n为了方便理解,我们还是用之"
  },
  {
    "path": "docs/v2/compile/entrance.md",
    "chars": 10273,
    "preview": "# 编译入口\n\n当我们使用 Runtime + Compiler 的 Vue.js,它的入口是 `src/platforms/web/entry-runtime-with-compiler.js`,看一下它对 `$mount` 函数的定义:"
  },
  {
    "path": "docs/v2/compile/index.md",
    "chars": 427,
    "preview": "# 编译\n\n之前我们分析过模板到真实 DOM 渲染的过程,中间有一个环节是把模板编译成 `render` 函数,这个过程我们把它称作编译。\n\n虽然我们可以直接为组件编写 `render` 函数,但是编写 `template` 模板更加直观,"
  },
  {
    "path": "docs/v2/compile/optimize.md",
    "chars": 6004,
    "preview": "# optimize\n\n当我们的模板 `template` 经过 `parse` 过程后,会输出生成 AST 树,那么接下来我们需要对这颗树做优化,`optimize` 的逻辑是远简单于 `parse` 的逻辑,所以理解起来会轻松很多。\n\n"
  },
  {
    "path": "docs/v2/compile/parse.md",
    "chars": 21569,
    "preview": "# parse\n\n编译过程首先就是对模板做解析,生成 AST,它是一种抽象语法树,是对源代码的抽象语法结构的树状表现形式。在很多编译技术中,如 babel 编译 ES6 的代码都会先生成 AST。\n\n这个过程是比较复杂的,它会用到大量正则表"
  },
  {
    "path": "docs/v2/components/async-component.md",
    "chars": 12044,
    "preview": "# 异步组件\n\n在我们平时的开发工作中,为了减少首屏代码体积,往往会把一些非首屏的组件设计成异步组件,按需加载。Vue 也原生支持了异步组件的能力,如下:\n\n```js\nVue.component('async-example', func"
  },
  {
    "path": "docs/v2/components/component-register.md",
    "chars": 5744,
    "preview": "# 组件注册\n\n在 Vue.js 中,除了它内置的组件如 `keep-alive`、`component`、`transition`、`transition-group` 等,其它用户自定义组件在使用前必须注册。很多同学在开发过程中可能会遇"
  },
  {
    "path": "docs/v2/components/create-component.md",
    "chars": 11701,
    "preview": "# createComponent\n\n上一章我们在分析 `createElement` 的实现的时候,它最终会调用 `_createElement` 方法,其中有一段逻辑是对参数 `tag` 的判断,如果是一个普通的 html 标签,像上一"
  },
  {
    "path": "docs/v2/components/index.md",
    "chars": 603,
    "preview": "# 组件化\n\nVue.js 另一个核心思想是组件化。所谓组件化,就是把页面拆分成多个组件 (component),每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用,组件"
  },
  {
    "path": "docs/v2/components/lifecycle.md",
    "chars": 9152,
    "preview": "# 生命周期\n\n每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添"
  },
  {
    "path": "docs/v2/components/merge-option.md",
    "chars": 9206,
    "preview": "# 合并配置\n\n通过之前章节的源码分析我们知道,`new Vue` 的过程通常有 2 种场景,一种是外部我们的代码主动调用 `new Vue(options)` 的方式实例化一个 Vue 对象;另一种是我们上一节分析的组件过程中内部通过 `"
  },
  {
    "path": "docs/v2/components/patch.md",
    "chars": 12021,
    "preview": "# patch\n\n通过前一章的分析我们知道,当我们通过 `createComponent` 创建了组件 VNode,接下来会走到 `vm._update`,执行 `vm.__patch__` 去把 VNode 转换成真正的 DOM 节点。这"
  },
  {
    "path": "docs/v2/data-driven/create-element.md",
    "chars": 9571,
    "preview": "# createElement\n\nVue.js 利用 createElement 方法创建 VNode,它定义在 `src/core/vdom/create-element.js` 中:\n\n```js\n// wrapper function"
  },
  {
    "path": "docs/v2/data-driven/index.md",
    "chars": 547,
    "preview": "# 数据驱动\n\nVue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代"
  },
  {
    "path": "docs/v2/data-driven/mounted.md",
    "chars": 6008,
    "preview": "# Vue 实例挂载的实现\n\nVue 中我们是通过 `$mount` 实例方法去挂载 `vm` 的,`$mount` 方法在多个文件中都有定义,如 `src/platform/web/entry-runtime-with-compiler."
  },
  {
    "path": "docs/v2/data-driven/new-vue.md",
    "chars": 2315,
    "preview": "# new Vue 发生了什么\n \n 从入口代码开始分析,我们先来分析 `new Vue` 背后发生了哪些事情。我们都知道,`new` 关键字在 Javascript 语言中代表实例化是一个对象,而 `Vue` 实际上是一个类,类在 Jav"
  },
  {
    "path": "docs/v2/data-driven/render.md",
    "chars": 3350,
    "preview": "# render\n\nVue 的 `_render` 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。它的定义在 `src/core/instance/render.js` 文件中:\n\n```js\nVue.prototype."
  },
  {
    "path": "docs/v2/data-driven/update.md",
    "chars": 14658,
    "preview": "# update\n\nVue 的 `_update` 是实例的一个私有方法,它被调用的时机有 2 个,一个是首次渲染,一个是数据更新的时候;由于我们这一章节只分析首次渲染部分,数据更新部分会在之后分析响应式原理的时候涉及。`_update` "
  },
  {
    "path": "docs/v2/data-driven/virtual-dom.md",
    "chars": 3084,
    "preview": "# Virtual DOM\n\nVirtual DOM 这个概念相信大部分人都不会陌生,它产生的前提是浏览器中的 DOM 是很“昂贵\"的,为了更直观的感受,我们可以简单的把一个简单的 div 元素的属性都打印出来,如图所示:\n\n<img :s"
  },
  {
    "path": "docs/v2/extend/event.md",
    "chars": 18980,
    "preview": "# event\n\n我们平时开发工作中,处理组件间的通讯,原生的交互,都离不开事件。对于一个组件元素,我们不仅仅可以绑定原生的 DOM 事件,还可以绑定自定义事件,非常灵活和方便。那么接下来我们从源码角度来看看它的实现原理。\n\n为了更加直观,"
  },
  {
    "path": "docs/v2/extend/index.md",
    "chars": 216,
    "preview": "# 扩展\n\n前面几章我们分析了 Vue 的核心以及编译过程,除此之外,Vue 还提供了很多好用的 feature 如 `event`、`v-model`、`slot`、`keep-alive`、`transition` 等等。对他们的理解有"
  },
  {
    "path": "docs/v2/extend/keep-alive.md",
    "chars": 17166,
    "preview": "# keep-alive\n\n在我们的平时开发工作中,经常为了组件的缓存优化而使用 `<keep-alive>` 组件,乐此不疲,但很少有人关注它的实现原理,下面就让我们来一探究竟。\n\n## 内置组件\n\n`<keep-alive>` 是 Vu"
  },
  {
    "path": "docs/v2/extend/slot.md",
    "chars": 16269,
    "preview": "# slot\n\nVue 的组件提供了一个非常有用的特性 —— `slot` 插槽,它让组件的实现变的更加灵活。我们平时在开发组件库的时候,为了让组件更加灵活可定制,经常用插槽的方式让用户可以自定义内容。插槽分为普通插槽和作用域插槽,它们可以"
  },
  {
    "path": "docs/v2/extend/tansition-group.md",
    "chars": 12641,
    "preview": "# transition-group\n\n前一节我们介绍了 `<transiiton>` 组件的实现原理,它只能针对单一元素实现过渡效果。我们做前端开发经常会遇到列表的需求,我们对列表元素进行添加和删除,有时候也希望有过渡效果,Vue.js "
  },
  {
    "path": "docs/v2/extend/tansition.md",
    "chars": 26193,
    "preview": "# transition\n\n在我们平时的前端项目开发中,经常会遇到如下需求,一个 DOM 节点的插入和删除或者是显示和隐藏,我们不想让它特别生硬,通常会考虑加一些过渡效果。\n\nVue.js 除了实现了强大的数据驱动,组件化的能力,也给我们提"
  },
  {
    "path": "docs/v2/extend/v-model.md",
    "chars": 13849,
    "preview": "# v-model\n\n很多同学在理解 Vue 的时候都把 Vue 的数据响应原理理解为双向绑定,但实际上这是不准确的,我们之前提到的数据响应,都是通过数据的改变去驱动 DOM 视图的变化,而双向绑定除了数据驱动 DOM 外, DOM 的变化"
  },
  {
    "path": "docs/v2/prepare/build.md",
    "chars": 5804,
    "preview": "# Vue.js 源码构建\n\nVue.js 源码是基于 [Rollup](https://github.com/rollup/rollup) 构建的,它的构建相关配置都在 scripts 目录下。\n\n## 构建脚本\n\n通常一个基于 NPM "
  },
  {
    "path": "docs/v2/prepare/directory.md",
    "chars": 1205,
    "preview": "# Vue.js 源码目录设计\n\nVue.js 的源码都在 src 目录下,其目录结构如下。\n\n```\nsrc\n├── compiler        # 编译相关 \n├── core            # 核心代码 \n├── plat"
  },
  {
    "path": "docs/v2/prepare/entrance.md",
    "chars": 7782,
    "preview": "# 从入口开始\n\n我们之前提到过 Vue.js 构建过程,在 web 应用下,我们来分析 Runtime + Compiler 构建出来的 Vue.js,它的入口是 `src/platforms/web/entry-runtime-with"
  },
  {
    "path": "docs/v2/prepare/flow.md",
    "chars": 3230,
    "preview": "# 认识 Flow\n\n[Flow](https://flow.org/en/docs/getting-started/) 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 做了静态类"
  },
  {
    "path": "docs/v2/prepare/index.md",
    "chars": 257,
    "preview": "# 准备工作\n\n那么从这一章开始我们即将分析 Vue 的源码,我们将会介绍一些前置知识如 flow、源码目录、构建方式、编译入口等。\n\n除此之外,我希望你已经用过 Vue 做过 2 个以上的实际项目,对 Vue 的思想有了一定的了解,对绝大"
  },
  {
    "path": "docs/v2/reactive/component-update.md",
    "chars": 19890,
    "preview": "# 组件更新\n\n在组件化章节,我们介绍了 Vue 的组件化实现过程,不过我们只讲了 Vue 组件的创建过程,并没有涉及到组件数据发生变化,更新组件的过程。而通过我们这一章对数据响应式原理的分析,了解到当数据发生变化的时候,会触发渲染 `wa"
  },
  {
    "path": "docs/v2/reactive/computed-watcher.md",
    "chars": 13662,
    "preview": "# 计算属性 VS 侦听属性\n\nVue 的组件对象支持了计算属性 `computed` 和侦听属性 `watch` 2 个选项,很多同学不了解什么时候该用 `computed` 什么时候该用 `watch`。先不回答这个问题,我们接下来从源"
  },
  {
    "path": "docs/v2/reactive/getters.md",
    "chars": 9810,
    "preview": "# 依赖收集\n\n通过上一节的分析我们了解 Vue 会把普通对象变成响应式对象,响应式对象 getter 相关的逻辑就是做依赖收集,这一节我们来详细分析这个过程。\n\n我们先来回顾一下 getter 部分的逻辑:\n\n```js\nexport f"
  },
  {
    "path": "docs/v2/reactive/index.md",
    "chars": 899,
    "preview": "# 深入响应式原理\n\n前面 2 章介绍的都是 Vue 怎么实现数据渲染和组件化的,主要讲的是初始化的过程,把原始的数据最终映射到 DOM 中,但并没有涉及到数据变化到 DOM 变化的部分。而 Vue 的数据驱动除了数据渲染 DOM 之外,还"
  },
  {
    "path": "docs/v2/reactive/next-tick.md",
    "chars": 6202,
    "preview": "# nextTick\n\n`nextTick` 是 Vue 的一个核心实现,在介绍 Vue 的 nextTick 之前,为了方便大家理解,我先简单介绍一下 JS 的运行机制。\n\n## JS 运行机制\n\nJS 执行是单线程的,它是基于事件循环的"
  },
  {
    "path": "docs/v2/reactive/props.md",
    "chars": 21939,
    "preview": "# Props (v2.6.11)\n\n`Props` 作为组件的核心特性之一,也是我们平时开发 Vue 项目中接触最多的特性之一,它可以让组件的功能变得丰富,也是父子组件通讯的一个渠道。那么它的实现原理是怎样的,我们来一探究竟。\n\n## 规"
  },
  {
    "path": "docs/v2/reactive/questions.md",
    "chars": 5949,
    "preview": "# 检测变化的注意事项\n\n通过前面几节的分析,我们对响应式数据对象以及它的 getter 和 setter 部分做了了解,但是对于一些特殊情况是需要注意的,接下来我们就从源码的角度来看 Vue 是如何处理这些特殊情况的。\n\n## 对象添加属"
  },
  {
    "path": "docs/v2/reactive/reactive-object.md",
    "chars": 10858,
    "preview": "# 响应式对象\n\n可能很多小伙伴之前都了解过 Vue.js 实现响应式的核心是利用了 ES5 的 `Object.defineProperty`,这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因,我们先来对它有个直观的认识。"
  },
  {
    "path": "docs/v2/reactive/setters.md",
    "chars": 9703,
    "preview": "# 派发更新\n\n通过上一节分析我们了解了响应式数据依赖收集过程,收集的目的就是为了当我们修改数据的时候,可以对相关的依赖派发更新,那么这一节我们来详细分析这个过程。\n\n我们先来回顾一下 setter 部分的逻辑:\n\n```js\n/**\n *"
  },
  {
    "path": "docs/v2/reactive/summary.md",
    "chars": 54,
    "preview": "# 原理图\n\n<img :src=\"$withBase('/assets/reactive.png')\">\n"
  },
  {
    "path": "docs/v2/vue-router/index.md",
    "chars": 1484,
    "preview": "# Vue-Router\n\n路由的概念相信大部分同学并不陌生,它的作用就是根据不同的路径映射到不同的视图。我们在用 Vue 开发过实际项目的时候都会用到 Vue-Router 这个官方插件来帮我们解决路由的问题。Vue-Router 的能力"
  },
  {
    "path": "docs/v2/vue-router/install.md",
    "chars": 4250,
    "preview": "# 路由注册\n\nVue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。Vue-Router 就是官方维护的路由插件,在介绍它的注册实现之前,我们先来分析一下 Vu"
  },
  {
    "path": "docs/v2/vue-router/matcher.md",
    "chars": 16705,
    "preview": "# matcher\n\n`matcher` 相关的实现都在 `src/create-matcher.js` 中,我们先来看一下 `matcher` 的数据结构:\n\n```js\nexport type Matcher = {\n  match: "
  },
  {
    "path": "docs/v2/vue-router/router.md",
    "chars": 8019,
    "preview": "# VueRouter 对象\n\nVueRouter 的实现是一个类,我们先对它做一个简单地分析,它的定义在 `src/index.js` 中:\n\n```js\nexport default class VueRouter {\n  static"
  },
  {
    "path": "docs/v2/vue-router/transition-to.md",
    "chars": 29813,
    "preview": "# 路径切换\n\n`history.transitionTo` 是 Vue-Router 中非常重要的方法,当我们切换路由线路的时候,就会执行到该方法,前一节我们分析了 `matcher` 的相关实现,知道它是如何找到匹配的新线路,那么匹配到"
  },
  {
    "path": "docs/v2/vuex/api.md",
    "chars": 17298,
    "preview": "# API\n\n上一节我们对 Vuex 的初始化过程有了深入的分析,在我们构造好这个 `store` 后,需要提供一些 API 对这个 `store` 做存取的操作,那么这一节我们就从源码的角度对这些 API 做分析。\n \n## 数据获取\n\n"
  },
  {
    "path": "docs/v2/vuex/index.md",
    "chars": 1232,
    "preview": "# Vuex\n\nVuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。\n\n## 什么是“状态管理模式”?\n\n让我们从一个简单的 Vue 计"
  },
  {
    "path": "docs/v2/vuex/init.md",
    "chars": 23959,
    "preview": "# Vuex 初始化\n\n这一节我们主要来分析 Vuex 的初始化过程,它包括安装、Store 实例化过程 2 个方面。\n\n## 安装\n\n当我们在代码中通过 `import Vuex from 'vuex'` 的时候,实际上引用的是一个对象,"
  },
  {
    "path": "docs/v2/vuex/plugin.md",
    "chars": 3333,
    "preview": "# 插件\n\nVuex 除了提供的存取能力,还提供了一种插件能力,让我们可以监控 `store` 的变化过程来做一些事情。\n\nVuex 的 `store` 接受 `plugins` 选项,我们在实例化 `Store` 的时候可以传入插件,它是"
  },
  {
    "path": "docs/v3/guide/index.md",
    "chars": 6811,
    "preview": "# Vue.js 3.x 源码解析先导\n\n## 前言\n\n2018 年 6 月我在慕课网发布了 Vue.js 2.x 的源码解析课程 [《Vue.js 源码全方位深入解析》](https://coding.imooc.com/class/22"
  },
  {
    "path": "docs/v3/new/index.md",
    "chars": 5668,
    "preview": "# 聊聊我的新课《Vue.js 3.0 核心源码解析》​\n\n## 我为什么做这门课程\n\n2020 年 7 月 6 日,我的课程 《Vue.js 3.0 核心源码解析》在拉勾教育平台上线了,我想和大家聊聊我为什么做这门课,源码学习的心得以及与"
  },
  {
    "path": "package.json",
    "chars": 655,
    "preview": "{\n  \"name\": \"vue-analysis\",\n  \"version\": \"1.0.0\",\n  \"description\": \"analysis vue.js deeply\",\n  \"directories\": {\n    \"doc"
  },
  {
    "path": "vue/flow/compiler.js",
    "chars": 5411,
    "preview": "declare type CompilerOptions = {\n  warn?: Function; // allow customizing warning in different environments; e.g. node\n  "
  },
  {
    "path": "vue/flow/component.js",
    "chars": 4875,
    "preview": "import type { Config } from '../src/core/config'\nimport type VNode from '../src/core/vdom/vnode'\nimport type Watcher fro"
  },
  {
    "path": "vue/flow/global-api.js",
    "chars": 805,
    "preview": "declare interface GlobalAPI {\n  cid: number;\n  options: Object;\n  config: Config;\n  util: Object;\n\n  extend: (options: O"
  },
  {
    "path": "vue/flow/modules.js",
    "chars": 984,
    "preview": "declare module 'he' {\n  declare function escape(html: string): string;\n  declare function decode(html: string): string;\n"
  },
  {
    "path": "vue/flow/options.js",
    "chars": 2120,
    "preview": "declare type InternalComponentOptions = {\n  _isComponent: true;\n  parent: Component;\n  _parentVnode: VNode;\n  render?: F"
  },
  {
    "path": "vue/flow/ssr.js",
    "chars": 450,
    "preview": "declare type ComponentWithCacheContext = {\n  type: 'ComponentWithCache';\n  bufferIndex: number;\n  buffer: Array<string>;"
  },
  {
    "path": "vue/flow/vnode.js",
    "chars": 1881,
    "preview": "declare type VNodeChildren = Array<?VNode | string | VNodeChildren> | string;\n\ndeclare type VNodeComponentOptions = {\n  "
  },
  {
    "path": "vue/flow/weex.js",
    "chars": 3365,
    "preview": "// global flag to be compiled away\ndeclare var __WEEX__: boolean;\n\n// global object in Weex\ndeclare var WXEnvironment: W"
  },
  {
    "path": "vue/package.json",
    "chars": 5499,
    "preview": "{\n  \"name\": \"vue\",\n  \"version\": \"2.5.17-beta.0\",\n  \"description\": \"Reactive, component-oriented view layer for modern we"
  },
  {
    "path": "vue/scripts/alias.js",
    "chars": 441,
    "preview": "const path = require('path')\n\nconst resolve = p => path.resolve(__dirname, '../', p)\n\nmodule.exports = {\n  vue: resolve("
  },
  {
    "path": "vue/scripts/build.js",
    "chars": 2193,
    "preview": "const fs = require('fs')\nconst path = require('path')\nconst zlib = require('zlib')\nconst rollup = require('rollup')\ncons"
  },
  {
    "path": "vue/scripts/config.js",
    "chars": 6426,
    "preview": "const path = require('path')\nconst buble = require('rollup-plugin-buble')\nconst alias = require('rollup-plugin-alias')\nc"
  },
  {
    "path": "vue/scripts/gen-release-note.js",
    "chars": 434,
    "preview": "const version = process.argv[2] || process.env.VERSION\nconst cc = require('conventional-changelog')\nconst file = `./RELE"
  },
  {
    "path": "vue/scripts/get-weex-version.js",
    "chars": 664,
    "preview": "var coreVersion = require('../package.json').version\nvar weexVersion = require('../packages/weex-vue-framework/package.j"
  },
  {
    "path": "vue/scripts/git-hooks/commit-msg",
    "chars": 483,
    "preview": "#!/usr/bin/env bash\n\n# Validate commit log\ncommit_regex='^Merge.+|(feat|fix|docs|style|refactor|perf|test|build|ci|chore"
  },
  {
    "path": "vue/scripts/git-hooks/pre-commit",
    "chars": 190,
    "preview": "#!/usr/bin/env bash\n\nfiles_to_lint=$(git diff --cached --name-only --diff-filter=ACM | grep '\\.js$')\n\nif [ -n \"$files_to"
  },
  {
    "path": "vue/scripts/release-weex.sh",
    "chars": 877,
    "preview": "#!/bin/bash\nset -e\nCUR_VERSION=$(node build/get-weex-version.js -c)\nNEXT_VERSION=$(node build/get-weex-version.js)\n\necho"
  },
  {
    "path": "vue/scripts/release.sh",
    "chars": 1700,
    "preview": "#!/bin/bash\nset -e\n\nif [[ -z $1 ]]; then\n  echo \"Enter new version: \"\n  read -r VERSION\nelse\n  VERSION=$1\nfi\n\nread -p \"R"
  },
  {
    "path": "vue/scripts/verify-commit-msg.js",
    "chars": 892,
    "preview": "const chalk = require('chalk')\nconst msgPath = process.env.GIT_PARAMS\nconst msg = require('fs').readFileSync(msgPath, 'u"
  },
  {
    "path": "vue/src/compiler/codegen/events.js",
    "chars": 4996,
    "preview": "/* @flow */\n\nconst fnExpRE = /^([\\w$_]+|\\([^)]*?\\))\\s*=>|^function\\s*\\(/\nconst simplePathRE = /^[A-Za-z_$][\\w$]*(?:\\.[A-"
  },
  {
    "path": "vue/src/compiler/codegen/index.js",
    "chars": 13960,
    "preview": "/* @flow */\n\nimport { genHandlers } from './events'\nimport baseDirectives from '../directives/index'\nimport { camelize, "
  },
  {
    "path": "vue/src/compiler/create-compiler.js",
    "chars": 1577,
    "preview": "/* @flow */\n\nimport { extend } from 'shared/util'\nimport { detectErrors } from './error-detector'\nimport { createCompile"
  },
  {
    "path": "vue/src/compiler/directives/bind.js",
    "chars": 307,
    "preview": "/* @flow */\n\nexport default function bind (el: ASTElement, dir: ASTDirective) {\n  el.wrapData = (code: string) => {\n    "
  },
  {
    "path": "vue/src/compiler/directives/index.js",
    "chars": 144,
    "preview": "/* @flow */\n\nimport on from './on'\nimport bind from './bind'\nimport { noop } from 'shared/util'\n\nexport default {\n  on,\n"
  },
  {
    "path": "vue/src/compiler/directives/model.js",
    "chars": 3094,
    "preview": "/* @flow */\n\n/**\n * Cross-platform code generation for component v-model\n */\nexport function genComponentModel (\n  el: A"
  },
  {
    "path": "vue/src/compiler/directives/on.js",
    "chars": 316,
    "preview": "/* @flow */\n\nimport { warn } from 'core/util/index'\n\nexport default function on (el: ASTElement, dir: ASTDirective) {\n  "
  },
  {
    "path": "vue/src/compiler/error-detector.js",
    "chars": 3430,
    "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": 4107,
    "preview": "/* @flow */\n\nimport { emptyObject } from 'shared/util'\nimport { parseFilters } from './parser/filter-parser'\n\nexport fun"
  },
  {
    "path": "vue/src/compiler/index.js",
    "chars": 783,
    "preview": "/* @flow */\n\nimport { parse } from './parser/index'\nimport { optimize } from './optimizer'\nimport { generate } from './c"
  },
  {
    "path": "vue/src/compiler/optimizer.js",
    "chars": 3617,
    "preview": "/* @flow */\n\nimport { makeMap, isBuiltInTag, cached, no } from 'shared/util'\n\nlet isStaticKey\nlet isPlatformReservedTag\n"
  },
  {
    "path": "vue/src/compiler/parser/entity-decoder.js",
    "chars": 198,
    "preview": "/* @flow */\n\nlet decoder\n\nexport default {\n  decode (html: string): string {\n    decoder = decoder || document.createEle"
  },
  {
    "path": "vue/src/compiler/parser/filter-parser.js",
    "chars": 2701,
    "preview": "/* @flow */\n\nconst validDivisionCharRE = /[\\w).+\\-_$\\]]/\n\nexport function parseFilters (exp: string): string {\n  let inS"
  },
  {
    "path": "vue/src/compiler/parser/html-parser.js",
    "chars": 9033,
    "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": 18583,
    "preview": "/* @flow */\n\nimport he from 'he'\nimport { parseHTML } from './html-parser'\nimport { parseText } from './text-parser'\nimp"
  },
  {
    "path": "vue/src/compiler/parser/text-parser.js",
    "chars": 1456,
    "preview": "/* @flow */\n\nimport { cached } from 'shared/util'\nimport { parseFilters } from './filter-parser'\n\nconst defaultTagRE = /"
  },
  {
    "path": "vue/src/compiler/to-function.js",
    "chars": 2841,
    "preview": "/* @flow */\n\nimport { noop, extend } from 'shared/util'\nimport { warn as baseWarn, tip } from 'core/util/debug'\n\ntype Co"
  },
  {
    "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": 3314,
    "preview": "/* @flow */\n\nimport { isRegExp, remove } from 'shared/util'\nimport { getFirstComponentChild } from 'core/vdom/helpers/in"
  },
  {
    "path": "vue/src/core/config.js",
    "chars": 2562,
    "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": 1052,
    "preview": "/* @flow */\n\nimport { ASSET_TYPES } from 'shared/constants'\nimport { isPlainObject, validateComponentName } from '../uti"
  },
  {
    "path": "vue/src/core/global-api/extend.js",
    "chars": 2641,
    "preview": "/* @flow */\n\nimport { ASSET_TYPES } from 'shared/constants'\nimport { defineComputed, proxy } from '../instance/state'\nim"
  },
  {
    "path": "vue/src/core/global-api/index.js",
    "chars": 1496,
    "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": 220,
    "preview": "/* @flow */\n\nimport { mergeOptions } from '../util/index'\n\nexport function initMixin (Vue: GlobalAPI) {\n  Vue.mixin = fu"
  },
  {
    "path": "vue/src/core/global-api/use.js",
    "chars": 629,
    "preview": "/* @flow */\n\nimport { toArray } from '../util/index'\n\nexport function initUse (Vue: GlobalAPI) {\n  Vue.use = function (p"
  },
  {
    "path": "vue/src/core/index.js",
    "chars": 682,
    "preview": "import Vue from './instance/index'\nimport { initGlobalAPI } from './global-api/index'\nimport { isServerRendering } from "
  },
  {
    "path": "vue/src/core/instance/events.js",
    "chars": 3550,
    "preview": "/* @flow */\n\nimport {\n  tip,\n  toArray,\n  hyphenate,\n  handleError,\n  formatComponentName\n} from '../util/index'\nimport "
  },
  {
    "path": "vue/src/core/instance/index.js",
    "chars": 549,
    "preview": "import { initMixin } from './init'\nimport { stateMixin } from './state'\nimport { renderMixin } from './render'\nimport { "
  },
  {
    "path": "vue/src/core/instance/init.js",
    "chars": 4659,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { initProxy } from './proxy'\nimport { initState } from './state'\nimpo"
  },
  {
    "path": "vue/src/core/instance/inject.js",
    "chars": 2228,
    "preview": "/* @flow */\n\nimport { hasOwn } from 'shared/util'\nimport { warn, hasSymbol } from '../util/index'\nimport { defineReactiv"
  },
  {
    "path": "vue/src/core/instance/lifecycle.js",
    "chars": 9094,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport Watcher from '../observer/watcher'\nimport { mark, measure } from '../"
  },
  {
    "path": "vue/src/core/instance/proxy.js",
    "chars": 2302,
    "preview": "/* not type checking this file because flow doesn't play well with Proxy */\n\nimport config from 'core/config'\nimport { w"
  },
  {
    "path": "vue/src/core/instance/render-helpers/bind-object-listeners.js",
    "chars": 597,
    "preview": "/* @flow */\n\nimport { warn, extend, isPlainObject } from 'core/util/index'\n\nexport function bindObjectListeners (data: a"
  },
  {
    "path": "vue/src/core/instance/render-helpers/bind-object-props.js",
    "chars": 1335,
    "preview": "/* @flow */\n\nimport config from 'core/config'\n\nimport {\n  warn,\n  isObject,\n  toObject,\n  isReservedAttribute\n} from 'co"
  },
  {
    "path": "vue/src/core/instance/render-helpers/check-keycodes.js",
    "chars": 985,
    "preview": "/* @flow */\n\nimport config from 'core/config'\nimport { hyphenate } from 'shared/util'\n\nfunction isKeyNotMatch<T> (expect"
  },
  {
    "path": "vue/src/core/instance/render-helpers/index.js",
    "chars": 1034,
    "preview": "/* @flow */\n\nimport { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'\nimport { createTextVNode, create"
  },
  {
    "path": "vue/src/core/instance/render-helpers/render-list.js",
    "chars": 895,
    "preview": "/* @flow */\n\nimport { isObject, isDef } from 'core/util/index'\n\n/**\n * Runtime helper for rendering v-for lists.\n */\nexp"
  },
  {
    "path": "vue/src/core/instance/render-helpers/render-slot.js",
    "chars": 1270,
    "preview": "/* @flow */\n\nimport { extend, warn, isObject } from 'core/util/index'\n\n/**\n * Runtime helper for rendering <slot>\n */\nex"
  },
  {
    "path": "vue/src/core/instance/render-helpers/render-static.js",
    "chars": 1406,
    "preview": "/* @flow */\n\n/**\n * Runtime helper for rendering static trees.\n */\nexport function renderStatic (\n  index: number,\n  isI"
  },
  {
    "path": "vue/src/core/instance/render-helpers/resolve-filter.js",
    "chars": 246,
    "preview": "/* @flow */\n\nimport { identity, resolveAsset } from 'core/util/index'\n\n/**\n * Runtime helper for resolving filters\n */\ne"
  },
  {
    "path": "vue/src/core/instance/render-helpers/resolve-slots.js",
    "chars": 1711,
    "preview": "/* @flow */\n\nimport type VNode from 'core/vdom/vnode'\n\n/**\n * Runtime helper for resolving raw children VNodes into a sl"
  },
  {
    "path": "vue/src/core/instance/render.js",
    "chars": 4133,
    "preview": "/* @flow */\n\nimport {\n  warn,\n  nextTick,\n  emptyObject,\n  handleError,\n  defineReactive\n} from '../util/index'\n\nimport "
  },
  {
    "path": "vue/src/core/instance/state.js",
    "chars": 9783,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport Watcher from '../observer/watcher'\nimport { pushTarget, popTarget } f"
  },
  {
    "path": "vue/src/core/observer/array.js",
    "chars": 954,
    "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": 1097,
    "preview": "/* @flow */\n\nimport type Watcher from './watcher'\nimport { remove } from '../util/index'\n\nlet uid = 0\n\n/**\n * A dep is a"
  },
  {
    "path": "vue/src/core/observer/index.js",
    "chars": 6764,
    "preview": "/* @flow */\n\nimport Dep from './dep'\nimport VNode from '../vdom/vnode'\nimport { arrayMethods } from './array'\nimport {\n "
  },
  {
    "path": "vue/src/core/observer/scheduler.js",
    "chars": 4000,
    "preview": "/* @flow */\n\nimport type Watcher from './watcher'\nimport config from '../config'\nimport { callHook, activateChildCompone"
  },
  {
    "path": "vue/src/core/observer/traverse.js",
    "chars": 940,
    "preview": "/* @flow */\n\nimport { _Set as Set, isObject } from '../util/index'\nimport type { SimpleSet } from '../util/index'\nimport"
  },
  {
    "path": "vue/src/core/observer/watcher.js",
    "chars": 6476,
    "preview": "/* @flow */\n\nimport {\n  warn,\n  remove,\n  isObject,\n  parsePath,\n  _Set as Set,\n  handleError\n} from '../util/index'\n\nim"
  },
  {
    "path": "vue/src/core/util/debug.js",
    "chars": 2695,
    "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": 2879,
    "preview": "/* @flow */\n\n// can we use __proto__?\nexport const hasProto = '__proto__' in {}\n\n// Browser environment sniffing\nexport "
  },
  {
    "path": "vue/src/core/util/error.js",
    "chars": 1180,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { inBrowser, inWeex } from './env'\n\nex"
  },
  {
    "path": "vue/src/core/util/index.js",
    "chars": 263,
    "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": 764,
    "preview": "/* @flow */\n\n/**\n * Check if a string starts with $ or _\n */\nexport function isReserved (str: string): boolean {\n  const"
  },
  {
    "path": "vue/src/core/util/next-tick.js",
    "chars": 3554,
    "preview": "/* @flow */\n/* globals MessageChannel */\n\nimport { noop } from 'shared/util'\nimport { handleError } from './error'\nimpor"
  },
  {
    "path": "vue/src/core/util/options.js",
    "chars": 10512,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { nativeWatch } from './env'\nimport { "
  },
  {
    "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": 5174,
    "preview": "/* @flow */\n\nimport { warn } from './debug'\nimport { observe, toggleObserving, shouldObserve } from '../observer/index'\n"
  },
  {
    "path": "vue/src/core/vdom/create-component.js",
    "chars": 7618,
    "preview": "/* @flow */\n\nimport VNode from './vnode'\nimport { resolveConstructorOptions } from 'core/instance/init'\nimport { queueAc"
  },
  {
    "path": "vue/src/core/vdom/create-element.js",
    "chars": 4359,
    "preview": "/* @flow */\n\nimport config from '../config'\nimport VNode, { createEmptyVNode } from './vnode'\nimport { createComponent }"
  },
  {
    "path": "vue/src/core/vdom/create-functional-component.js",
    "chars": 3985,
    "preview": "/* @flow */\n\nimport VNode, { cloneVNode } from './vnode'\nimport { createElement } from './create-element'\nimport { resol"
  },
  {
    "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": 409,
    "preview": "/* @flow */\n\nimport { isDef } from 'shared/util'\nimport { isAsyncPlaceholder } from './is-async-placeholder'\n\nexport fun"
  },
  {
    "path": "vue/src/core/vdom/helpers/index.js",
    "chars": 271,
    "preview": "/* @flow */\n\nexport * from './merge-hook'\nexport * from './extract-props'\nexport * from './update-listeners'\nexport * fr"
  },
  {
    "path": "vue/src/core/vdom/helpers/is-async-placeholder.js",
    "chars": 120,
    "preview": "/* @flow */\n\nexport function isAsyncPlaceholder (node: VNode): boolean {\n  return node.isComment && node.asyncFactory\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/merge-hook.js",
    "chars": 1012,
    "preview": "/* @flow */\n\nimport VNode from '../vnode'\nimport { createFnInvoker } from './update-listeners'\nimport { remove, isDef, i"
  },
  {
    "path": "vue/src/core/vdom/helpers/normalize-children.js",
    "chars": 3287,
    "preview": "/* @flow */\n\nimport VNode, { createTextVNode } from 'core/vdom/vnode'\nimport { isFalse, isTrue, isDef, isUndef, isPrimit"
  },
  {
    "path": "vue/src/core/vdom/helpers/resolve-async-component.js",
    "chars": 3412,
    "preview": "/* @flow */\n\nimport {\n  warn,\n  once,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject,\n  hasSymbol\n} from 'core/util/index'\n\nim"
  },
  {
    "path": "vue/src/core/vdom/helpers/update-listeners.js",
    "chars": 2059,
    "preview": "/* @flow */\n\nimport { warn } from 'core/util/index'\nimport { cached, isUndef, isPlainObject } from 'shared/util'\n\nconst "
  },
  {
    "path": "vue/src/core/vdom/modules/directives.js",
    "chars": 3109,
    "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": 1046,
    "preview": "/* @flow */\n\nimport { remove, isDef } from 'shared/util'\n\nexport default {\n  create (_: any, vnode: VNodeWithData) {\n   "
  },
  {
    "path": "vue/src/core/vdom/patch.js",
    "chars": 26207,
    "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": 3172,
    "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": 198,
    "preview": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n\nexport default function html (el: ASTElement, dir: ASTDirective"
  },
  {
    "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": 5533,
    "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": 200,
    "preview": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n\nexport default function text (el: ASTElement, dir: ASTDirective"
  },
  {
    "path": "vue/src/platforms/web/compiler/index.js",
    "chars": 210,
    "preview": "/* @flow */\n\nimport { baseOptions } from './options'\nimport { createCompiler } from 'compiler/index'\n\nconst { compile, c"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/class.js",
    "chars": 1227,
    "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": 130,
    "preview": "import klass from './class'\nimport style from './style'\nimport model from './model'\n\nexport default [\n  klass,\n  style,\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/model.js",
    "chars": 2636,
    "preview": "/* @flow */\n\n/**\n * Expand input[v-model] with dyanmic type bindings into v-if-else chains\n * Turn this:\n *   <input v-m"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/style.js",
    "chars": 1330,
    "preview": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport { parseStyleText } from 'web/util/style'\nimp"
  },
  {
    "path": "vue/src/platforms/web/compiler/options.js",
    "chars": 516,
    "preview": "/* @flow */\n\nimport {\n  isPreTag,\n  mustUseProp,\n  isReservedTag,\n  getTagNamespace\n} from '../util/index'\n\nimport modul"
  },
  {
    "path": "vue/src/platforms/web/compiler/util.js",
    "chars": 904,
    "preview": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\nexport const isUnaryTag = makeMap(\n  'area,base,br,col,embed,frame,h"
  },
  {
    "path": "vue/src/platforms/web/entry-compiler.js",
    "chars": 190,
    "preview": "/* @flow */\n\nexport { parseComponent } from 'sfc/parser'\nexport { compile, compileToFunctions } from './compiler/index'\n"
  },
  {
    "path": "vue/src/platforms/web/entry-runtime-with-compiler.js",
    "chars": 2822,
    "preview": "/* @flow */\n\nimport config from 'core/config'\nimport { warn, cached } from 'core/util/index'\nimport { mark, measure } fr"
  },
  {
    "path": "vue/src/platforms/web/entry-runtime.js",
    "chars": 67,
    "preview": "/* @flow */\n\nimport Vue from './runtime/index'\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/platforms/web/entry-server-basic-renderer.js",
    "chars": 338,
    "preview": "/* @flow */\n\nimport modules from './server/modules/index'\nimport directives from './server/directives/index'\nimport { is"
  },
  {
    "path": "vue/src/platforms/web/entry-server-renderer.js",
    "chars": 894,
    "preview": "/* @flow */\n\nprocess.env.VUE_ENV = 'server'\n\nimport { extend } from 'shared/util'\nimport modules from './server/modules/"
  },
  {
    "path": "vue/src/platforms/web/runtime/class-util.js",
    "chars": 1438,
    "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": 5738,
    "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": 5636,
    "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": 4405,
    "preview": "/**\n * Not type checking this file because flow doesn't like attaching\n * properties to Elements.\n */\n\nimport { isTextIn"
  },
  {
    "path": "vue/src/platforms/web/runtime/directives/show.js",
    "chars": 1647,
    "preview": "/* @flow */\n\nimport { enter, leave } from '../modules/transition'\n\n// recursively search for possible transition defined"
  },
  {
    "path": "vue/src/platforms/web/runtime/index.js",
    "chars": 2203,
    "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": 3196,
    "preview": "/* @flow */\n\nimport { isIE, isIE9, isEdge } from 'core/util/env'\n\nimport {\n  extend,\n  isDef,\n  isUndef\n} from 'shared/u"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/class.js",
    "chars": 889,
    "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": 2969,
    "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": 2410,
    "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": 2702,
    "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": 8169,
    "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": 1517,
    "preview": "/* @flow */\n\nimport { namespaceMap } from 'web/util/index'\n\nexport function createElement (tagName: string, vnode: VNode"
  },
  {
    "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": 5089,
    "preview": "/* @flow */\n\nimport { inBrowser, isIE9 } from 'core/util/index'\nimport { addClass, removeClass } from './class-util'\nimp"
  },
  {
    "path": "vue/src/platforms/web/server/compiler.js",
    "chars": 281,
    "preview": "/* @flow */\n\nimport { baseOptions } from '../compiler/options'\nimport { createCompiler } from 'server/optimizing-compile"
  },
  {
    "path": "vue/src/platforms/web/server/directives/index.js",
    "chars": 90,
    "preview": "import show from './show'\nimport model from './model'\n\nexport default {\n  show,\n  model\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/directives/model.js",
    "chars": 1264,
    "preview": "/* @flow */\n\nimport { looseEqual, looseIndexOf } from 'shared/util'\n\n// this is only applied for <select v-model> becaus"
  },
  {
    "path": "vue/src/platforms/web/server/directives/show.js",
    "chars": 296,
    "preview": "/* @flow */\n\nexport default function show (node: VNodeWithData, dir: VNodeDirective) {\n  if (!dir.value) {\n    const sty"
  },
  {
    "path": "vue/src/platforms/web/server/modules/attrs.js",
    "chars": 1284,
    "preview": "/* @flow */\n\nimport { escape } from '../util'\n\nimport {\n  isDef,\n  isUndef,\n  extend\n} from 'shared/util'\n\nimport {\n  is"
  },
  {
    "path": "vue/src/platforms/web/server/modules/class.js",
    "chars": 284,
    "preview": "/* @flow */\n\nimport { escape } from '../util'\nimport { genClassForVnode } from 'web/util/index'\n\nexport default function"
  },
  {
    "path": "vue/src/platforms/web/server/modules/dom-props.js",
    "chars": 1356,
    "preview": "/* @flow */\n\nimport VNode from 'core/vdom/vnode'\nimport { renderAttr } from './attrs'\nimport { isDef, isUndef, extend } "
  },
  {
    "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": 756,
    "preview": "/* @flow */\n\nimport { escape } from '../util'\nimport { hyphenate } from 'shared/util'\nimport { getStyle } from 'web/util"
  },
  {
    "path": "vue/src/platforms/web/server/util.js",
    "chars": 1611,
    "preview": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\nconst isAttr = makeMap(\n  'accept,accept-charset,accesskey,action,al"
  },
  {
    "path": "vue/src/platforms/web/util/attrs.js",
    "chars": 1573,
    "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": 1992,
    "preview": "/* @flow */\n\nimport { isDef, isObject } from 'shared/util'\n\nexport function genClassForVnode (vnode: VNodeWithData): str"
  }
]

// ... and 369 more files (download for full content)

About this extraction

This page contains the full source code of the ustbhuangyi/vue-analysis GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 569 files (2.0 MB), approximately 549.9k tokens, and a symbol index with 1141 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!