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

8 篇文章 1 订阅
4 篇文章 0 订阅

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
  • 接下来进入挂载阶段
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值