Vue.js源码分析之new Vue的this._init

论坛 期权论坛 期权     
黄素红web大前端学习空间   2019-7-8 06:27   2835   0
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,找到该方法:
  1. export function initMixin (Vue: Class) {
复制代码
  1.   Vue.prototype._init = function (options?: Object) {
复制代码
  1.     const vm: Component = this
复制代码
  1.     vm._isVue = true
复制代码
  1.     // merge options
复制代码
  1.     vm.$options = mergeOptions(
复制代码
  1.       resolveConstructorOptions(vm.constructor),
复制代码
  1.       options || {},
复制代码
  1.       vm
复制代码
  1.     )
复制代码
  1.     // 把自己挂载这个属性
复制代码
  1.     vm._renderProxy = vm
复制代码
  1.     vm._self = vm
复制代码
  1.    
复制代码
  1.     initLifecycle(vm)
复制代码
  1.     initEvents(vm)
复制代码
  1.     initRender(vm)
复制代码
  1.     callHook(vm, 'beforeCreate')
复制代码
  1.     initInjections(vm)
复制代码
  1.     initState(vm)
复制代码
  1.     initProvide(vm)
复制代码
  1.     callHook(vm, 'created')
复制代码
  1.    
复制代码
  1.     if (vm.$options.el) {
复制代码
  1.       vm.$mount(vm.$options.el)
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1. }
复制代码
stateMixin只在Vue的原型上挂了_init方法,接着往下看,有这么几行:

  1. vm.$options = mergeOptions(
复制代码
  1.         resolveConstructorOptions(vm.constructor),
复制代码
  1.         options || {},
复制代码
  1.         vm
复制代码
  1.       )
复制代码
从字面上看,是将options合并起来,该方法的作用是将options参数挂载到vm.$options上,跳到该方法:
  1. export function mergeOptions (
复制代码
  1.   parent: Object,
复制代码
  1.   child: Object,
复制代码
  1.   vm?: Component
复制代码
  1. ): Object {  
复制代码
  1.   normalizeProps(child, vm) // props属性序列化
复制代码
  1.   normalizeInject(child, vm) // Inject序列化
复制代码
  1.   normalizeDirectives(child) // 指令序列化
复制代码
  1. [/code][code]  let key
复制代码
  1.   for (key in child) {
复制代码
  1.     if (!hasOwn(parent, key)) {
复制代码
  1.       mergeField(key)
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1.   return options
复制代码
  1. }
复制代码
一些属性做了序列化处理,序列化的过程比较简单,这里就不说明了。后面我们会用到这些属性。
在for循环里,将我们传进去的options合并起来了,接下来,我们看那几个最重要的方法。

先从initLifecycle(vm) 开始:

  1. export function initLifecycle (vm: Component) {
复制代码
  1.   const options = vm.$options
复制代码
  1.   // something else
复制代码
  1.   vm.$parent = parent
复制代码
  1.   vm.$root = parent ? parent.$root : vm
复制代码
  1.   vm.$children = []
复制代码
  1.   vm.$refs = {}
复制代码
  1.   // 每个组件实例是都有一个watcher,保存在vm._watcher上
复制代码
  1.   vm._watcher = null  
复制代码
  1.   vm._inactive = null
复制代码
  1.   vm._directInactive = false
复制代码
  1.   vm._isMounted = false
复制代码
  1.   vm._isDestroyed = false
复制代码
  1.   vm._isBeingDestroyed = false
复制代码
  1. }
复制代码
这个方法是初始化一些参数。

第二个方法是:initEvents(vm)

  1. export function initEvents (vm: Component) {
复制代码
  1.   // vm._events会把$on等方法的event放到这里
复制代码
  1.   vm._events = Object.create(null)
复制代码
  1.   vm._hasHookEvent = false
复制代码
  1.   const listeners = vm.$options._parentListeners
复制代码
  1.   if (listeners) {
复制代码
  1.     updateComponentListeners(vm, listeners)
复制代码
  1.   }
复制代码
  1. }
复制代码
可以看到,这里listeners是调用了父级的listeners,也就说:parent中的listeners实际的执行者是child,不是parent。

接下来是:initRender
这个方法涉及到虚拟DOM,这里先跳过,只需要知道,它会返回一个vnode对象即可。

然后是:callHook

  1. export function callHook (vm: Component, hook: string) {
复制代码
  1.     // 获取到该生命周期的钩子函数
复制代码
  1.     const handlers = vm.$options[hook]
复制代码
  1.     const info = `${hook} hook`
复制代码
  1.     if (handlers) {
复制代码
  1.         for (let i = 0, j = handlers.length; i < j; i++) {
复制代码
  1.             invokeWithErrorHandling(handlers[i], vm, null, vm, info) // 并在该函数内部执行
复制代码
  1.         }
复制代码
  1.     }
复制代码
  1. }
复制代码
这里有一个invokeWithErrorHandling方法,该方法的作用是:
1)做try catch处理
2)catch的话执行handleError
那handdleError是什么呢?我们直接来看代码:
  1. export function handleError (err: Error, vm: any, info: string) {
复制代码
  1.   try {
复制代码
  1.     if (vm) {
复制代码
  1.       let cur = vm
复制代码
  1.       while ((cur = cur.$parent)) {
复制代码
  1.         const hooks = cur.$options.errorCaptured
复制代码
  1.         if (hooks) {
复制代码
  1.           for (let i = 0; i < hooks.length; i++) {
复制代码
  1.             try {
复制代码
  1.               const capture = hooks[i].call(cur, err, vm, info) === false
复制代码
  1.               if (capture) return
复制代码
  1.             } catch (e) {
复制代码
  1.               globalHandleError(e, cur, 'errorCaptured hook')
复制代码
  1.             }
复制代码
  1.           }
复制代码
  1.         }
复制代码
  1.       }
复制代码
  1.     }
复制代码
  1.     globalHandleError(err, vm, info)
复制代码
  1.   }
复制代码
  1. }
复制代码
循环条件是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
  1. export function initInjections (vm: Component) {
复制代码
  1.     // 获取到options上的inject,为什么能获取到呢,
复制代码
  1.     // 因为我们在initLifeCycle做了normalizeInject
复制代码
  1.     const result = resolveInject(vm.$options.inject, vm)
复制代码
  1.     if (result) {
复制代码
  1.         toggleObserving(false)
复制代码
  1.         Object.keys(result).forEach(key => {
复制代码
  1.             defineReactive(vm, key, result[key])
复制代码
  1.         })
复制代码
  1.         toggleObserving(true)
复制代码
  1.     }
复制代码
  1. }
复制代码
这个函数做了两件事:
1)自底向上,获取inject结果(在resolveInject里做while循环)。
2)通知defineReactive函数不要将内容转换成响应式,因为在provide层已经做了响应式了。

initState方法:

  1. export function initState (vm: Component) {
复制代码
  1.     vm._watchers = [] // 这主要是给$watch用的,每个watch都会新建一个Watcher
复制代码
  1.     const opts = vm.$options
复制代码
  1.     // 初始化props
复制代码
  1.     // 因为在initLifeCycle调用了normalizeProps方法,
复制代码
  1.     // 所以options能直接方法props
复制代码
  1.     if (opts.props) initProps(vm, opts.props)   
复制代码
  1.     // 初始化methods
复制代码
  1.     if (opts.methods) initMethods(vm, opts.methods)
复制代码
  1.     if (opts.data) {
复制代码
  1.         // 初始化data
复制代码
  1.         initData(vm)   
复制代码
  1.     } else {
复制代码
  1.         observe(vm._data = {}, true /* asRootData */)
复制代码
  1.     }
复制代码
  1.     // 初始化computed
复制代码
  1.     if (opts.computed) initComputed(vm, opts.computed)   
复制代码
  1.     // Firefox的window上也有一个watch方法,所以要判断以下
复制代码
  1.     if (opts.watch && opts.watch !== nativeWatch) {
复制代码
  1.         initWatch(vm, opts.watch)    // 初始化watch
复制代码
  1.     }
复制代码
  1. }
复制代码
这里值得关注的是:在初始化的时候,包括props、methods、computed、watch都做了响应式处理,感兴趣的详细看看,都比较简单。

再来看看:initProvide方法

  1. export function initProvide (vm: Component) {   
复制代码
  1.     const provide = vm.$options.provide
复制代码
  1.     if (provide) {
复制代码
  1.         vm._provided = typeof provide === 'function'
复制代码
  1.             ? provide.call(vm)
复制代码
  1.             : provide
复制代码
  1.     }
复制代码
  1. }
复制代码
这个方法就比较好理解了,如果有options有provide,直接挂载到vm._provide上。
那为啥这里能直接访问vm.$options.provide呢?因为我们在mergeOptions做了序列化处理,能直接访问$options.provide 。


最后会调一下created的钩子,和我们上面说的callHook是一样的,就不重复了。

_init方法的最后会判断options是否有el元素,有的话直接调用$mount挂载。

  1. if (vm.$options.el) {
复制代码
  1.   vm.$mount(vm.$options.el)
复制代码
  1. }
复制代码
以上就是new Vue里_intit做的事了。
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP