vue源码学习- Vue初始化过程

Posted ~往无前

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue源码学习- Vue初始化过程相关的知识,希望对你有一定的参考价值。

Vue 

/src/core/instance/index.js.    //vue初始化,也就是vue构造函数的位置

import initMixin from './init'
//vue 构造函数
function Vue(options)
    if (!(this instanceof Vue))  
       warn$2('Vue is a constructor and should be called with the `new` keyword');
    
    //调用Vue.prototype._init 方法,该方法是在initMinin中定义的
    this._init(options);


// 定义Vue.prototype._init方法
initMixin(Vue)

export default Vue

 Vue.prototype._init

/src/core/instance/init.js

/**
*定义Vue.prototype._init 方法
*@param* Vue构造函数
*/
var uid=0
export function initMixin(Vue: Class<Component>)
   //负责 Vue的初始化过程
    Vue.prototype._init=function(options?:Object)
        // vue实例
        const vm: Component = this
        // 每个vue实例都有一个_uid,并且是依次递增的
        vm.uid = uid++
  
 
        vm._isVue = true
        //处理组件配置项
    if(options && options_isComponent)

        /**
         *每个子组件初始化时走这里,这里只做了一些性能优化
         *将组件配置对象上的一些深层次属性放到vm.$options 选项中,以提高代码的执行效率
         */
        initInternalComponent(vm, options)
    else
       /**
         *初始化根组件时走这里,合并Vue的全局配置到根组件的局部配置,比如Vue.component 注册的全局组件会合并到根实例的components选项中
         *至于每个子组件的选项合并则发生在两个地方:
         *  1.Vue.component 方法注册的全局组件在注册时做了选项合并
         *  2.component:xx方式注册的局部组件在执行编译器生成的render函数时选项合并,包括根组件中的components配置
         */
        vm.$options=mergeOptions(
            resolveConstructorOptions(vm.constructor),options || , vm)
        )
    
    /* istanbul ignore else */
    if(process.env.NODE_ENV !== 'production')

        //设置代理,将vm实例上的属性代理到vm._renderProxy
        initProxy(vm)

    else
        vm._renderProxy  = vm
    
    //expose real self
    vm._self = vm
    //初始化组件实例关系属性,比如$parent、$children、$root、$refs等
    initLifecycle(vm)
    /**
      *初始化自定义事件,这里需要注意一点,所有我们在<comp @click='handleClick'/>上注册的事件,监听者不是父组件,而是子组件本身,也就是说事件的派发和监听者都是组件本身,和父组件无关

    */
    initEvents(vm)
    //解析组件的插槽信息,得到vm.$slot,处理渲染函数,得到vm$createElement方法,即h函数
    initRender(vm)
    //调用beforeCreate 钩子函数
    callHook(vm,'beforeCreate')
    //初始化组件的inject配置项,得到result[key]=val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个key
    initInjections(vm)
    //数据响应式的重点,处理props,methods,data.computed,watch
    initState(vm)
    //解析组件配置项上的provide对象,将其挂载到vm._provided属性上
    initProvide(vm)
    //调用created钩子函数
    callHook(vm,'created')
    //如果发现配置项上有el选项,则自动调用 $mount 方法,也就是说有了el选项,就不需要再手动调用$mount,反之,没有el则必须手动调用$mount
    if(vm.$options.el)
        //调用$mount方法,进入挂载阶段
        vm.$mount(vm.$options.el)
    
  

resolveConstructorOptions

 /src/core/instance/init.js

/**

   * 从组件构造函数中解析配置对象options,并合并基类选项
   * @param * Ctor
   * @returns
*/
export function resolveConstructorOptions (Ctor: Class<Component>)
    //配置项目,获取Vue上面绑定的属性
    let options = Ctor.options
    if(Ctor.super)
        //存在基类,递归解析基类构造函数的选项
        const superOptions = resolveConstructorOptions(Ctor.super)
        const cachedSuperOptions = Ctor.superOptions
        if(superOptions !== cachedSuperOptions)

            //说明基类构造函数选项已经发生改变,需要重新设置
            Ctor.superOptions = superOptions
            //检查Ctor.superOptions上是否有任何后期修改/附件的选项
            const modifiedOptions = resolveModifiedOptions(Ctor)
            // 如果存在被修改或增加的选项,则合并两个选项
            if(modifiedOptions)

                extend(Ctor.extendOptions,modifiedOptions);
            
            //选项合并,将合并结果赋值为Ctor.options
            options = Ctor.options = mergeOptions(superOptions,Ctor.extendOptions)
            if(options.name)
                //该组件构造函数的name为自身的组件构造函数时自己的constructor
                options.components[options.name] = Ctor
            
        
    
    return options

 resolveModifiedOptions

/src/core/instance/init.js

/**
  * 解析构造函数选项中后续被修改或者增加的选项
  */
function resolveModifiedOptions (Ctor: Class<Component>): ?Object
        let modified;
        //构造函数选项
        const lastest = Ctor.options
        //密封的构造函数选项,备份
        const sealed = Ctor.sealedOptions
        //对比两个选项,记录不一致的选项
        for(const key in latest)
            if(latest[key] !== sealed[key]

               if(!modified) modified=
               modified[key]=latest[key]
        
        
        return modified
    

mergeOptions

/src/core/util/options.js

/**
  * 合并两个选项,出现相同配置项时,子选项会覆盖父选项的配置
  */
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
    
    // 标准化props、inject、directive选项,方便后续程序的处理
    normalizeProps(child ,vm)
    normalizeInject(child ,vm)
    normalizeDirectives(child)
    //处理原始child对象上的extends和mixins,分别执行mergeOptions.将这些继承而来的选项合并到parent
    //mergeOptions 处理过的对象会含有_base属性
    if(!child._base)
        if(child.extends)
            parent = mergeOptions(parent, child.extends, 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)
        // strats = Object.create(null)
        const strat = strats[key] || defaultStrat
        // 值如果为childVal 存在则优先使用childVal,否则使用parentVal
        options[key] = strat(parent[key],child[key],vm,key)
    
    
    return options

 initInjections

/src/core/instance/inject.js

 

/**
  *初始化inject配置项
  * 1.得到 result[key]=val
  * 2.对结果数据进行响应式处理,代理每个key到vm实例
  */
export function initInjections (vm:Component)
    //解析inject配置项,然后从祖代组件的配置中找到 配置项中每一个key对应的val,最后得到 result[key]=val的结果
    const result=resolveInject(vm.$options.inject,vm)
    //对 result做数据响应式处理,也有代理inject配置中每个key到vm实例的作用
    //不建议在子组件去更改这些数据,因为一旦祖代组件中 注入的provide发生更改,你在组件中做的更改就会被覆盖
    if(result)

        toggleObserving(false)
        Object.keys(result).forEach(key=>
            /* istanbul ignore else */
            if(process.env.NODE_ENV !== 'production')

                defineReactive(vm,key,result[key],()=>
                    warn(
                `Avoid mutating an injected value directly since the changes will be ` +`overwritten whenever the provided component re-renders. ` + `injection being mutated: "$key"`,
                vm)
                )
            else
                defineReactive(vm,key,result[key]
            
        )
       toggleObserving(true)
    

resolveInject

/src/core/instance/inject.js

/**
  *解析 inject配置项,从祖代组件的provide配置中找到key对应的值,否则用默认值,最后得到result[key]=val
  *inject 对象肯定是以下这个结构,因为在合并选项时对组件配置对象做了标准化处理
  *@param*inject=
  * key:
  *      from:provideKey,
  *      default:xx
  *  
  *
*/
export function resolveInject(inject:any,vm:Component): ?Object
    if(inject)
        const result=Object.create(null)
        //inject 配置项的所有key
        const keys = hasSymbol ? Reflect.ownKeys(inject):Object.keys(inject)
       
        //遍历key
        for(let i=0;i<keys.length;i++)
            const key=keys[i]
            //跳过__ob__对象
            if(key === '__ob__') continue
            //拿到provide中对应的key
            const provideKey=inject[key].from
            let source = vm
            //遍历所有的祖代组件,直到根组件,找到provide中对应的key的值,最后得到result[key]=provide[provideKey]
            while(source)
               if(source._provided && hasOwn(source._provided,provideKey))
                    result[key] = source._provided[provideKey)
                    break
                
                source = source.$parent
            
            //如果上一个循环未找到,则采用inject[key].default,如果没有设置default值,则抛出错误
            if(!source)

                if('default' in inject[key])
                    const provideDefault = inject[key].default
                    result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault
                else if(process.env.NODE_ENV !== 'production')
                    warn(`Injection "$key" not found`,vm)
                
            
        
        return result

    


initProvide 

/src/core/instance/inject.js

/**
  *解析组件配置项上的provide对象,将其挂载到vm._provided属性上

*/
export function initProvide (vm: Component)
    const provide = vm.$options.provide
    if(provide)
        vm._provided = typeof provide === 'function' ? provide.call(vm):provide
    

 总结:

Vue的初始化过程(new Vue(options))都做了什么?

  • 处理组件配置项
    • 初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上(也就是将Vue构造函数原型上添加的配置项合并到vm实例化对象的身上)
    • 初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到vm.$options选项中,以提高代码的执行效率
  • 初始化组件实例的关系属性,比如$parent,$children,$root,$refs等
  • 处理自定义事件
  • 调用beforeCreate钩子函数
  • 初始化组件的inject配置项,得到result[key]=val形式的配置对象,然后对该配置对象进行浅层的响应式处理(只处理了对象的第一层数据),并代理每个key到vm实例上;
  • 数据响应式,处理props,methods,data,computed,watch等选项
  • 解析组件配置项上的provide对象,将其挂载到vm._provided属性上
  • 调用created钩子函数
  • 如果发现配置项上有el选项,则自动调用$mount方法,也就是说有了el选项,就不需要再手动调用$mount方法,反之,没提供el选项则必须调用$mount
  • 接下来进入挂载阶段

以上是关于vue源码学习- Vue初始化过程的主要内容,如果未能解决你的问题,请参考以下文章

vue 2.0源码学习笔记—new Vue ( Vue 初始化过程 )

vue3.x源码剖析之初始化过程

Vue.js 源码学习笔记

Vue.js 源码学习笔记 -- 分析前准备 待续

手写vue3源码——创建项目

手写vue3源码——创建项目