vue版本:2.6.10
debug:entry-runtime-with-compiler.js
在打包的时候,加了--sourcemap,方便调试
我们用浏览器打断点的方式来看学习,今天的例子是官网上的vue/examples/commits/index.html
在new Vue()打了一个断点,然后点击单步执行,执行到这个位置:
可以看到进入了src/core/instance/index.js,vue的构造函数就是写在这里,看起来非常简洁,下面混入了五个方法,这里并没有定义_init方法,那这个方法哪来的呢?
到这里,我们要先去了解这五个方法:
- initMixin(Vue)
- stateMixin(Vue)
- eventsMixin(Vue)
- lifecycleMixin(Vue)
- renderMixin(Vue)
先来看下initMixin,找到该方法:
- export function initMixin (Vue: Class) {
复制代码- Vue.prototype._init = function (options?: Object) {
复制代码- const vm: Component = this
复制代码- vm.$options = mergeOptions(
复制代码- resolveConstructorOptions(vm.constructor),
复制代码- callHook(vm, 'beforeCreate')
复制代码- vm.$mount(vm.$options.el)
复制代码 stateMixin只在Vue的原型上挂了_init方法,接着往下看,有这么几行:
- vm.$options = mergeOptions(
复制代码- resolveConstructorOptions(vm.constructor),
复制代码 从字面上看,是将options合并起来,该方法的作用是将options参数挂载到vm.$options上,跳到该方法:
- export function mergeOptions (
复制代码- normalizeProps(child, vm) // props属性序列化
复制代码- normalizeInject(child, vm) // Inject序列化
复制代码- normalizeDirectives(child) // 指令序列化
复制代码- if (!hasOwn(parent, key)) {
复制代码 一些属性做了序列化处理,序列化的过程比较简单,这里就不说明了。后面我们会用到这些属性。
在for循环里,将我们传进去的options合并起来了,接下来,我们看那几个最重要的方法。
先从initLifecycle(vm) 开始:
- export function initLifecycle (vm: Component) {
复制代码- const options = vm.$options
复制代码- vm.$root = parent ? parent.$root : vm
复制代码- // 每个组件实例是都有一个watcher,保存在vm._watcher上
复制代码- vm._directInactive = false
复制代码- vm._isBeingDestroyed = false
复制代码 这个方法是初始化一些参数。
第二个方法是:initEvents(vm)
- export function initEvents (vm: Component) {
复制代码- // vm._events会把$on等方法的event放到这里
复制代码- vm._events = Object.create(null)
复制代码- const listeners = vm.$options._parentListeners
复制代码- updateComponentListeners(vm, listeners)
复制代码 可以看到,这里listeners是调用了父级的listeners,也就说:parent中的listeners实际的执行者是child,不是parent。
接下来是:initRender
这个方法涉及到虚拟DOM,这里先跳过,只需要知道,它会返回一个vnode对象即可。
然后是:callHook
- export function callHook (vm: Component, hook: string) {
复制代码- const handlers = vm.$options[hook]
复制代码- const info = `${hook} hook`
复制代码- for (let i = 0, j = handlers.length; i < j; i++) {
复制代码- invokeWithErrorHandling(handlers[i], vm, null, vm, info) // 并在该函数内部执行
复制代码 这里有一个invokeWithErrorHandling方法,该方法的作用是:
1)做try catch处理
2)catch的话执行handleError
那handdleError是什么呢?我们直接来看代码:
- export function handleError (err: Error, vm: any, info: string) {
复制代码- while ((cur = cur.$parent)) {
复制代码- const hooks = cur.$options.errorCaptured
复制代码- for (let i = 0; i < hooks.length; i++) {
复制代码- const capture = hooks[i].call(cur, err, vm, info) === false
复制代码- globalHandleError(e, cur, 'errorCaptured hook')
复制代码- globalHandleError(err, vm, info)
复制代码 循环条件是cur=cur.$parent,也就是每次循环都将$parent赋值给cur,直到$parent为空,所以errorCaptured可以捕获子孙组件的错误。
handleError会一次执行父组件的errorCaptured钩子函数和全局的config.errorHandler(globalHandleError)。
2)
总的来说,在钩子的执行过程中,会发生的错误由handleError捕获,handleError会把错误层层传递。也就说:这是一个Vue专门处理错误的方法!
生命周期的所有钩子:
beforeCreate
created
beforeMount
mounted
beforeUpdate
beforeDestroy
destroyed
activated
deactivated
errorCaptured
下面我们接着看:initInjections
- export function initInjections (vm: Component) {
复制代码- // 获取到options上的inject,为什么能获取到呢,
复制代码- // 因为我们在initLifeCycle做了normalizeInject
复制代码- const result = resolveInject(vm.$options.inject, vm)
复制代码- Object.keys(result).forEach(key => {
复制代码- defineReactive(vm, key, result[key])
复制代码 这个函数做了两件事:
1)自底向上,获取inject结果(在resolveInject里做while循环)。
2)通知defineReactive函数不要将内容转换成响应式,因为在provide层已经做了响应式了。
initState方法:
- export function initState (vm: Component) {
复制代码- vm._watchers = [] // 这主要是给$watch用的,每个watch都会新建一个Watcher
复制代码- // 因为在initLifeCycle调用了normalizeProps方法,
复制代码- if (opts.props) initProps(vm, opts.props)
复制代码- if (opts.methods) initMethods(vm, opts.methods)
复制代码- observe(vm._data = {}, true /* asRootData */)
复制代码- if (opts.computed) initComputed(vm, opts.computed)
复制代码- // Firefox的window上也有一个watch方法,所以要判断以下
复制代码- if (opts.watch && opts.watch !== nativeWatch) {
复制代码- initWatch(vm, opts.watch) // 初始化watch
复制代码 这里值得关注的是:在初始化的时候,包括props、methods、computed、watch都做了响应式处理,感兴趣的详细看看,都比较简单。
再来看看:initProvide方法
- export function initProvide (vm: Component) {
复制代码- const provide = vm.$options.provide
复制代码- vm._provided = typeof provide === 'function'
复制代码 这个方法就比较好理解了,如果有options有provide,直接挂载到vm._provide上。
那为啥这里能直接访问vm.$options.provide呢?因为我们在mergeOptions做了序列化处理,能直接访问$options.provide 。
最后会调一下created的钩子,和我们上面说的callHook是一样的,就不重复了。
_init方法的最后会判断options是否有el元素,有的话直接调用$mount挂载。
- vm.$mount(vm.$options.el)
复制代码 以上就是new Vue里_intit做的事了。
|
|