Vue2.X源码学习第一天
学习目标
学习vue的工作原理,编译以及初始化流程
修改配置
为了方便debug,首先要对配置文件进行修改,package.json文件中scrpts添加
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
其次,在examples文件中,复制一个html文件,将vue打包完成后的引入文件改为<script src="../../dist/vue.js"></script>
修改完成,打开页面,在f12中的source中就可以看到与源码对应的文件目录结构,
随后运行代码,直接修改代码,刷新页面就可以直接看到修改后的效果,方便debug
入口文件
所谓擒贼先擒王,找到入口文件是学习源码的第一步。因为调试使用的umd格式的代码,则入口文件为
src\platforms\web\entry-runtime-with-compiler.js,然后进入文件,进行追踪Vue的来源。
1.src\platforms\web\entry-runtime-with-compiler.js
-
文件主要功能:扩展$mount方法,解析option的render template el的处理方法
-
Vue来源:
src\platforms\web\runtime\index.js
2.src\platforms\web\runtime\index.js
-
文件主要功能:声明patch函数,实现$mount:执行挂载函数
mountComponent -
Vue来源:
src\core\index.js
3.src\core\index.js
-
文件主要功能:执行
initGlobalAPI进行公共方法挂载 -
Vue来源:
src\core\instance\index.js
4.src\core\instance\index.js
- 文件主要功能:Vue构造源码位置
这样,我们就找到了Vue构造函数的所在地在src\core\instance\index.js中,
既然 找到了构造函数,那么可以分两步进行探索
- Vue的初始化流程
- new Vue的初始化流程
为了更容易的学习Vue源码,先从new Vue的初始化流程进行学习
new Vue 流程
Vue构造函数
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)//初始化执行的主要方法
}
接下来跟上面追踪入口文件一样,进行new Vue 流程的追踪
构造函数中主要执行方法为this._init(options),全局搜索_init方法,定位其位置在 src\core\instance\init.js文件中
Vue.prototype._init方法,位置src\core\instance\init.js
Vue.prototype._init = function (options?: Object) {
//-----------------------------省略-------------------------------
// 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')
//-----------------------------省略-------------------------------
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
省略部分代码,将核心代码暴露出来,第一眼可以看到的就是callHook(vm, 'beforeCreate')和callHook(vm, 'created'),
没错,最熟悉的钩子函数出场了,中间夹杂了很多其他的方法,这就是为什么执行beforeCreate和created方法时候,this内部结构不一致,后者比前者多,就是因为两者之间还有其他方法执行,对实例进行操作。
接下来就对这些方法进行总结
initLifecycle
对实例属性的root,$children等属性进行赋值
initEvents
对实例属性事件进行监听
initRender
对实例属性createElement进行赋值
initInjections
对祖辈传递的数据进行赋值
initState
对象实例属性初始化methods,data,props,computed等
initProvide
$options里的provide赋值到当前实例上
总结_init方法
new Vue流程就是执行了上述代码后完成了对实例的创建,在代码的最后,执行了vm.$mount(vm.$options.el)方法,进行vue对象的下一步操作,接下来就继续追踪$mount方法
$mount
在找Vue入口文件的时候追踪的文件中,src\platforms\web\runtime\index.js文件中对$mount进行了赋值操作
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)//主要代码
}
$mount主要执行了mountComponent方法,继续追踪
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
//...
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
//...
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
? 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
}
这一块代码看着头都大,不过还是要头铁进行硬看,还是第一眼印象中,看到了两个熟悉的代码callHook(vm, 'beforeMount')和
callHook(vm, 'mounted'),钩子函数还是看得懂的。这个方法看起来很长,但是实际的功能比较少。
1。对实例属性$el进行了挂载
vm.$el = el
2.然后创建了一个updateComponent的声明,
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
3.创建Watcher,将updateComponent交给 Watcher进行管理
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
? callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
总结$mount
对实例进行收尾处理,render渲染。
总结
第一天学习,先学大概流程,后面再慢慢看细节。中间如果对某个方法感兴趣可以直接运行代码debug,可以更加直观的了解vue源码。
好好学习,升职加薪迎娶白富美不是梦。