本文希望可以帮助那些想吃蛋糕,但又觉得蛋糕太大而又不知道从哪下口的人们。
一、如何开始第一步
- 将源码项目
clone
下来后,按照CONTRIBUTING中的Development Setup
中的顺序,逐个执行下来
$ npm install # watch and auto re-build dist/vue.js $ npm run dev
AI 代码解读
- 学会看package.json文件,就像你在使用MVVM去关注它的render一样。
既然$ npm run dev
命令可以重新编译出vue.js
文件,那么我们就从scripts
中的dev
开始看吧。
"dev":"rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
AI 代码解读
如果这里你还不清楚
rollup
是做什么的,可以戳这里,简单来说就是一个模块化打包工具。具体的介绍这里就跳过了,因为我们是来看vue的,如果太跳跃的话,基本就把这次主要想做的事忽略掉了,跳跳跳不一定跳哪里了,所以在阅读源码的时候,一定要牢记这次我们的目的是什么。
注意上面指令中的两个关键词scripts/config.js
和web-full-dev
,接下来让我们看看script/config.js
这个文件。
if (process.env.TARGET) { module.exports = genConfig(process.env.TARGET) } else { exports.getBuild = genConfig exports.getAllBuilds = () => Object.keys(builds).map(genConfig) }
AI 代码解读
回忆上面的命令,我们传入的
TARGET
是
web-full-dev
,那么带入到方法中,最终会看到这样一个
object
'web-full-dev': { // 入口文件 entry: resolve('web/entry-runtime-with-compiler.js'), // 输出文件 dest: resolve('dist/vue.js'), // 格式 format: 'umd', // 环境 env: 'development', // 别名 alias: { he: './entity-decoder' }, banner },
AI 代码解读
虽然这里我们还不知道它具体是做什么的,暂且通过语义来给它补上注释吧。既然有了入口文件,那么我们继续打开文件
web/entry-runtime-with-compiler.js
。OK,打开这个文件后,终于看到了我们的一个目标关键词
import Vue from './runtime/index'
AI 代码解读
江湖规矩,继续往这个文件里跳,然后你就会看到:
import Vue from 'core/index'
AI 代码解读
是不是又看到了代码第一行中熟悉的关键词
Vue
import Vue from './instance/index'
AI 代码解读
打开
instance/index
后,结束了我们的第一步,已经从package.json中到框架中的文件,找到了
Vue
的定义地方。让我们再回顾下流程:

二、学会利用demo
切记,在看源码时为了防止看着看着看跑偏了,我们一定要按照代码执行的顺序看。
-
项目结构中有
examples
目录,让我们也创建一个属于自己的demo在这里面吧,随便copy一个目录,命名为demo,后面我们的代码都通过这个demo来进行测试、观察。index.html内容如下:
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="../../dist/vue.js"></script> </head> <body> <div id="demo"> <template> <span>{{text}}</span> </template> </div> <script src="app.js"></script> </body> </html>
AI 代码解读
app.js文件内容如下:
var demo = new Vue({ el: '#demo', data() { return { text: 'hello world!' } } })
AI 代码解读
引入vue.js
上面demo的html中我们引入了dist/vue.js,那么window下,就会有Vue
对象,暂且先将app.js的代码修改如下:
console.dir(Vue);
AI 代码解读
如果这里你还不知道
console.dir
,而只知道console.log
,那你就亲自试试然后记住他们之间的差异吧。
从控制台我们可以看出,Vue
对象以及原型上有一系列属性,那么这些属性是从哪儿来的,做什么的,就是我们后续去深入的内容。
三、从哪儿来的
是否还记得我们在第一章中找到最终Vue
构造函数的文件?如果不记得了,就再回去看一眼吧,我们在本章会按照那个顺序倒着来看一遍Vue
的属性挂载。
instance(src/core/instance/index.js)
接下来我们就开始按照代码执行的顺序,先来分别看看这几个函数到底是弄啥嘞?import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' 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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
AI 代码解读
initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
AI 代码解读
initMixin(src/core/instance/init.js)
Vue.prototype._init = function (options?: Object) {}
AI 代码解读
-
在传入的
Vue
对象的原型上挂载了_init
方法。 -
stateMixin(src/core/instance/state.js)
// Object.defineProperty(Vue.prototype, '$data', dataDef) // 这里$data只提供了get方法,set方法再非生产环境时会给予警告 Vue.prototype.$data = undefined; // Object.defineProperty(Vue.prototype, '$props', propsDef) // 这里$props只提供了get方法,set方法再非生产环境时会给予警告 Vue.prototype.$props = undefined; Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function() {}
AI 代码解读
如果这里你还不知道
Object.defineProperty
是做什么的,我对你的建议是可以把对象的原型这部分好好看一眼,对于后面的代码浏览会有很大的效率提升,不然云里雾里的,你浪费的只有自己的时间而已。
eventsMixin(src/core/instance/events.js)
Vue.prototype.$on = function() {} Vue.prototype.$once = function() {} Vue.prototype.$off = function() {} Vue.prototype.$emit = function() {}
AI 代码解读
lifecycleMixin(src/core/instance/lifecycle.js)
Vue.prototype._update = function() {} Vue.prototype.$forceUpdate = function () {} Vue.prototype.$destroy = function () {}
AI 代码解读
renderMixin(src/core/instance/render.js)
// installRenderHelpers Vue.prototype._o = markOnce Vue.prototype._n = toNumber Vue.prototype._s = toString Vue.prototype._l = renderList Vue.prototype._t = renderSlot Vue.prototype._q = looseEqual Vue.prototype._i = looseIndexOf Vue.prototype._m = renderStatic Vue.prototype._f = resolveFilter Vue.prototype._k = checkKeyCodes Vue.prototype._b = bindObjectProps Vue.prototype._v = createTextVNode Vue.prototype._e = createEmptyVNode Vue.prototype._u = resolveScopedSlots Vue.prototype._g = bindObjectListeners // Vue.prototype.$nextTick = function() {} Vue.prototype._render = function() {}
AI 代码解读
instance
中对
Vue
的原型一波疯狂输出后,
Vue
的原型已经变成了:

如果你认为到此就结束了?答案当然是,不。让我们顺着第一章整理的图,继续回到core/index.js中。
Core(src/core/index.js)
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' import { FunctionalRenderContext } from 'core/vdom/create-functional-component' // 初始化全局API initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }) Vue.version = '__VERSION__' export default Vue
AI 代码解读
按照代码执行顺序,我们看看
initGlobalAPI(Vue)
方法内容:
// Object.defineProperty(Vue, 'config', configDef) Vue.config = { devtools: true, …} Vue.util = { warn, extend, mergeOptions, defineReactive, } Vue.set = set Vue.delete = delete Vue.nextTick = nextTick Vue.options = { components: {}, directives: {}, filters: {}, _base: Vue, } // extend(Vue.options.components, builtInComponents) Vue.options.components.KeepAlive = { name: 'keep-alive' …} // initUse Vue.use = function() {} // initMixin Vue.mixin = function() {} // initExtend Vue.cid = 0 Vue.extend = function() {} // initAssetRegisters Vue.component = function() {} Vue.directive = function() {} Vue.filter = function() {}
AI 代码解读
不难看出,整个Core在instance的基础上,又对Vue
的属性进行了一波输出。经历完Core后,整个Vue
变成了这样:

继续顺着第一章整理的路线,来看看runtime又对Vue
做了什么。
runtime(src/platforms/web/runtime/index.js)
这里还是记得先从宏观入手,不要去看每个方法的详细内容。可以通过
debugger
来暂停代码执行,然后通过控制台的console.dir(Vue)
随时观察Vue
的变化,
-
这里首先针对web平台,对Vue.config来了一小波方法添加。
Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement
AI 代码解读
向options中directives增加了
model
以及
show
指令:
// extend(Vue.options.directives, platformDirectives) Vue.options.directives = { model: { componentUpdated: ƒ …} show: { bind: ƒ, update: ƒ, unbind: ƒ } }
AI 代码解读
向options中components增加了
Transition
以及
TransitionGroup
:
// extend(Vue.options.components, platformComponents) Vue.options.components = { KeepAlive: { name: "keep-alive" …} Transition: {name: "transition", props: {…} …} TransitionGroup: {props: {…}, beforeMount: ƒ, …} }
AI 代码解读
在原型中追加
__patch__
以及
$mount
:
// 虚拟dom所用到的方法 Vue.prototype.__patch__ = patch Vue.prototype.$mount = function() {}
AI 代码解读
-
以及对devtools的支持。
entry(src/platforms/web/entry-runtime-with-compiler.js)
-
在entry中,覆盖了
$mount
方法。 -
挂载compile,
compileToFunctions
方法是将template
编译为render
函数
Vue.compile = compileToFunctions
AI 代码解读
小结
至此,我们完整的过了一遍在web中Vue的构造函数的变化过程:
- 通过instance对Vue.prototype进行属性和方法的挂载。
- 通过core对Vue进行静态属性和方法的挂载。
- 通过runtime添加了对platform === 'web'的情况下,特有的配置、组件、指令。
- 通过entry来为$mount方法增加编译
template
的能力。
四、做什么的
上一章我们从宏观角度观察了整个Vue构造函数的变化过程,那么我们本章将从微观角度,看看new Vue()后,都做了什么。
将我们demo中的app.js修改为如下代码:
var demo = new Vue({ el: '#demo', data() { return { text: 'hello world!' } } })
AI 代码解读
还记得instance/init中的Vue构造函数吗?在代码执行了
this._init(options)
,那我们就从
_init
入手,开始本章的旅途。
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ // 浏览器环境&支持window.performance&非生产环境&配置了performance if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` // 相当于 window.performance.mark(startTag) 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 { // 将options进行合并 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) } }
AI 代码解读
这个方法都做了什么?
- 在当前实例中,添加
_uid
,_isVue
属性。 - 当非生产环境时,用window.performance标记vue初始化的开始。
- 由于我们的demo中,没有手动处理_isComponent,所以这里会进入到else分支,将Vue.options与传入options进行合并。
- 为当前实例添加
_renderProxy
,_self
属性。 - 初始化生命周期,
initLifecycle
- 初始化事件,
initEvents
- 初始化render,
initRender
- 调用生命周期中的
beforeCreate
- 初始化注入值
initInjections
- 初始化状态
initState
- 初始化Provide
initProvide
- 调用生命周期中的
created
- 非生产环境下,标识初始化结束,为当前实例增加
_name
属性 - 根据
options
传入的el
,调用当前实例的$mount
OK,我们又宏观的看了整个_init
方法,接下来我们结合我们的demo,来细细的看下每一步产生的影响,以及具体调用的方法。
mergeOptions(src/core/util/options.js)
vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) function resolveConstructorOptions (Ctor: Class<Component>) { let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options }
AI 代码解读
还记得我们在第三章中,runtime对
Vue
的变更之后,options变成了什么样吗?如果你忘了,这里我们再回忆一下:
Vue.options = { components: { KeepAlive: { name: "keep-alive" …} Transition: {name: "transition", props: {…} …} TransitionGroup: {props: {…}, beforeMount: ƒ, …} }, directives: { model: { componentUpdated: ƒ …} show: { bind: ƒ, update: ƒ, unbind: ƒ } }, filters: {}, _base: ƒ Vue }
AI 代码解读
this.constructor
传入
resolveConstructorOptions
中,因为我们的demo中没有进行继承操作,所以在
resolveConstructorOptions
方法中,没有进入if,直接返回得到的结果,就是在
runtime
中进行处理后的
options
选项。而
options
就是我们在调用
new Vue({})
时,传入的
options
。此时,mergeOptions方法变为:
vm.$options = mergeOptions( { components: { KeepAlive: { name: "keep-alive" …} Transition: {name: "transition", props: {…} …} TransitionGroup: {props: {…}, beforeMount: ƒ, …} }, directives: { model: { componentUpdated: ƒ …} show: { bind: ƒ, update: ƒ, unbind: ƒ } }, filters: {}, _base: ƒ Vue }, { el: '#demo', data: ƒ data() }, vm )
AI 代码解读
接下来开始调用
mergeOptions
方法。打开文件后,我们发现在引用该文件时,会立即执行一段代码:
// config.optionMergeStrategies = Object.create(null) const strats = config.optionMergeStrategies
AI 代码解读
仔细往下看后面,还有一系列针对
strats
挂载方法和属性的操作,最终
strats
会变为:

其实这些散落在代码中的挂载操作,有点没想明白尤大没有放到一个方法里去统一处理一波?
继续往下翻,看到了我们进入这个文件的目标,那就是mergeOptions
方法:
function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { debugger; if (process.env.NODE_ENV !== 'production') { // 根据用户传入的options,检查合法性 checkComponents(child) } if (typeof child === 'function') { child = child.options } // 标准化传入options中的props 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 }
AI 代码解读
因为我们这里使用了最简单的hello world
,所以在mergeOptions
中,可以直接从30行开始看,这里初始化了变量options
,32行、35行的for
循环分别根据合并策略进行了合并。看到这里,恍然大悟,原来strats
是定义一些标准合并策略,如果没有定义在其中,就使用默认合并策略defaultStrat
。
这里有个小细节,就是在循环子options时,仅合并父options中不存在的项,来提高合并效率。
让我们继续来用最直白的方式,回顾下上面的过程:
// 初始化合并策略 const strats = config.optionMergeStrategies strats.el = strats.propsData = function (parent, child, vm, key) {} strats.data = function (parentVal, childVal, vm) {} constants.LIFECYCLE_HOOKS.forEach(hook => strats[hook] = mergeHook) constants.ASSET_TYPES.forEach(type => strats[type + 's'] = mergeAssets) strats.watch = function(parentVal, childVal, vm, key) {} strats.props = strats.methods = strats.inject = strats.computed = function(parentVal, childVal, vm, key) {} strats.provide = mergeDataOrFn // 默认合并策略 const defaultStrat = function (parentVal, childVal) { return childVal === undefined ? parentVal : childVal } function mergeOptions (parent, child, vm) { // 本次demo没有用到省略前面代码 ... 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 }
AI 代码解读
怎么样,是不是清晰多了?本次的demo经过
mergeOptions
之后,变为了如下:
OK,因为我们本次是来看_init
的,所以到这里,你需要清除Vue
通过合并策略,将parent与child进行了合并即可。接下来,我们继续回到_init
对options
合并处理完之后做了什么?
initProxy(src/core/instance/proxy.js)
在merge完options后,会判断如果是非生产环境时,会进入initProxy方法。
if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } vm._self = vm
AI 代码解读
带着雾水,进入到方法定义的文件,看到了Proxy
这个关键字,如果这里你还不清楚,可以看下阮老师的ES6,上面有讲。
- 这里在非生产环境时,对config.keyCodes的一些关键字做了禁止赋值操作。
- 返回了
vm._renderProxy = new Proxy(vm, handlers)
,这里的handlers
,由于我们的options中没有render,所以这里取值是hasHandler。
这部分具体是做什么用的,暂且知道有这么个东西,主线还是不要放弃,继续回到主线吧。
initLifecycle(src/core/instance/lifecycle.js)
初始化了与生命周期相关的属性。
function initLifecycle (vm) { const options = vm.$options // 省去部分与本次demo无关代码 ... vm.$parent = undefined vm.$root = vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }
AI 代码解读
initEvents(src/core/instance/events.js)
function initEvents (vm) { vm._events = Object.create(null) vm._hasHookEvent = false // 省去部分与本次demo无关代码 ... }
AI 代码解读
initRender(src/core/instance/render.js)
function initRender (vm: Component) { vm._vnode = null // the root of the child tree vm._staticTrees = null // v-once cached trees vm.$slots = {} vm.$scopedSlots = {} vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) vm.$createElement= (a, b, c, d) => createElement(vm, a, b, c, d, true) vm.$attrs = {} vm.$listeners = {} }
AI 代码解读
callHook(vm, 'beforeCreate)
调用生命周期函数beforeCreate
initInjections(src/core/instance/inject.js)
由于本demo没有用到注入值,对本次vm并无实际影响,所以这一步暂且忽略,有兴趣可以自行翻阅。
initState(src/core/instance/state.js)
本次的只针对这最简单的demo,分析
initState
,可能忽略了很多过程,后续我们会针对更复杂的demo来继续分析一波。

这里你可以先留意到几个关键词Observer
,Dep
,Watcher
。每个Observer
都有一个独立的Dep
。关于Watcher
,暂时没用到,但是请相信,马上就可以看到了。
initProvide(src/core/instance/inject.js)
由于本demo没有用到,对本次vm并无实际影响,所以这一步暂且忽略,有兴趣可以自行翻阅。
callHook(vm, 'created')
这里知道为什么在
created
时候,没法操作DOM了吗?因为在这里,还没有涉及到实际的DOM渲染。
vm.mount(vm.options.el)
这里前面有个if判断,所以当你如果没有在
new Vue
中的options
没有传入el
的话,就不会触发实际的渲染,就需要自己手动调用了$mount
。
这里的$mount
最终会调向哪里?还记得我们在第三章看到的compiler
所做的事情吗?就是覆盖Vue.prototype.$mount
,接下来,我们一起进入$mount
函数看看它都做了什么吧。
// 只保留与本次相关代码,其余看太多会影响视线 const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) const options = this.$options if (!options.render) { let template = getOuterHTML(el) if (template) { const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns } } return mount.call(this, el, hydrating) }
AI 代码解读
$mount
之前,先将原有的
$mount
保留至变量
mount
中,整个覆盖后的方法是将
template
转为
render
函数挂载至
vm
的
options
,然后调用调用原有的
mount
。所以还记得
mount
来自于哪嘛?那就继续吧
runtime/index
,方法很简单,调用了生命周期中
mountComponent
。
// 依然只保留和本demo相关的内容 function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el callHook(vm, 'beforeMount') let updateComponent = () => { vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
AI 代码解读
OK,精彩的部分来了,一个
Watcher
,盘活了整个我们前面铺垫的一系列东西。打开
src/core/observer/watcher.js
,让我们看看
Watcher
的构造函数吧。为了清楚的看到
Watcher
的流程。依旧只保留方法我们需要关注的东西:
constructor (vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm vm._watcher = this vm._watchers.push(this) this.getter = expOrFn this.value = this.get() } get () { pushTarget(this) let value const vm = this.vm value = this.getter.call(vm, vm) popTarget() this.cleanupDeps() return value }
AI 代码解读
- 在
Watcher
的构造函数中,本次传入的updateComponent
作为Wather
的getter
。 - 在
get
方法调用时,又通过pushTarget
方法,将当前Watcher
赋值给Dep.target
- 调用
getter
,相当于调用vm._update
,先调用vm._render
,而这时vm._render
,此时会将已经准备好的render
函数进调用。 -
render
函数中又用到了this.text
,所以又会调用text
的get
方法,从而触发了dep.depend()
-
dep.depend()
会调回Watcher
的addDep
,这时Watcher
记录了当前dep
实例。 - 继续调用
dep.addSub(this)
,dep
又记录了当前Watcher
实例,将当前的Watcher
存入dep.subs
中。 - 这里顺带提一下本次
demo
还没有使用的,也就是当this.text
发生改变时,会触发Observer
中的set
方法,从而触发dep.notify()
方法来进行update
操作。
最后这段文字太干了,可以自己通过断点,耐心的走一遍整个过程。如果没有耐心看完这段描述,可以看看笔者这篇文章100行代码带你玩vue响应式。
就这样,Vue
的数据响应系统,通过Observer
、Watcher
、Dep
完美的串在了一起。也希望经历这个过程后,你能对真正的对这张图,有一定的理解。

当然,$mount
中还有一步被我轻描淡写了,那就是这部分,将template转换为render,render实际调用时,会经历_render
, $createElement
, __patch__
, 方法,有兴趣可以自己浏览下'src/core/vdom/'目录下的文件,来了解vue
针对虚拟dom的使用。
最后
如果你喜欢,可以继续浏览笔者关于vue template转换部分的文章《Vue对template做了什么》。
原文作者:JserWang
本文来源: 掘金 如需转载请联系原作者