vue2.x源码解析之一 — 入口文件解析

290 阅读2分钟
  1. 本文基于v2.5.1版本
  2. 开启源码调试:将build/config.js文件中的genConfig函数内的config.sourceMap改为true

package.json文件中,通过script的dev命令,可以找到,源码构建命令入口文件:build/config.js,TARGET:web-full-dev,找到该文件,可以找到vue源码入口
src/platform/web/entry-runtime-with-compiler.js

import Vue from './runtime/index'

src/platforms/web/runtime/index.js

import Vue from 'core/index'

src/core/index.js 找到vue核心代码入口

// vue的核心方法
import Vue from './instance/index'
// 初始化了一些全局的API
import { initGlobalAPI } from './global-api/index'
// 判断是否是ssr,返回boolean类型
import { isServerRendering } from 'core/util/env'

// 初始化全局api
initGlobalAPI(Vue)

// 为Vue的原型定义$isServer属性
Object.defineProperty(Vue.prototype, '$isServer', {
    get: isServerRendering
})

// 为Vue的原型定义$ssrContext属性
Object.defineProperty(Vue.prototype, '$ssrContext', {
    get () {
        /* istanbul ignore next */
        return this.$vnode && this.$vnode.ssrContext
    }
})

Vue.version = '__VERSION__'

export default Vue

这个文件中,引入了Vue的核心方法,并通过initGlobalAPI,初始化了Vue的一些全局API。

export function initGlobalAPI (Vue: GlobalAPI) {
    ...
    
    Vue.set = set
    Vue.delete = del
    Vue.nextTick = nextTick

    // 定义Vue.options.directives, Vue.options.filters, Vue.options.components
    Vue.options = Object.create(null)
    ASSET_TYPES.forEach(type => {
        Vue.options[type + 's'] = Object.create(null)
    })

    ...

    // 为Vue.options.components添加KeepAlive
    extend(Vue.options.components, builtInComponents)

    // 定义Vue.use
    initUse(Vue)
    // 定义Vue.mixin
    initMixin(Vue)
    // 定义Vue.extend
    initExtend(Vue)
    // 定义Vue.component,Vue.directive,Vue.filter
    initAssetRegisters(Vue)
}

src/core/instance/index.js

...

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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

...

看一个简单的例子:

<div id="app"></div>

new Vue({
    el: '#app',
    template: '<div>{{message}}</div>',
    components: {
    },
    data: () => {
        return {
            message: 'Hello World',
        }
    },
    created() {
    }
})

当调用new Vue的时候,会调用this._init(options)方法,下面看一下_init方法:
src/core/instance/init.js

export function initMixin (Vue: Class<Component>) {
    Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        // 表明uid,可能有多个vue实例
        vm._uid = uid++

        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            startTag = `vue-perf-start:${vm._uid}`
            endTag = `vue-perf-end:${vm._uid}`
            mark(startTag)
        }

        // a flag to avoid this being observed
        // 如果是Vue的实例,则不需要被observe
        vm._isVue = true

        // merge options
        // options参数的处理
        if (options && options._isComponent) {  // _isComponent 标识当前为 内部Component
            // 内部Component 的 options 初始化
            initInternalComponent(vm, options)
        } else {
            // 非内部Component的 options 初始化
            const ctorOption = resolveConstructorOptions(vm.constructor)
            vm.$options = mergeOptions(
                ctorOption,
                options || {},
                vm
            )
        }

        // 在render中将this指向vm._renderProxy
        if (process.env.NODE_ENV !== 'production') {
            initProxy(vm)
        } else {
            vm._renderProxy = vm
        }

        vm._self = vm

        initLifecycle(vm)   // vm的生命周期相关变量初始化
        initEvents(vm)  // vm的事件监听初始化
        initRender(vm)  // 初始化渲染函数
        callHook(vm, 'beforeCreate')    // 回调 beforeCreate 钩子函数
        initInjections(vm) // resolve injections before data/props

        // vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化,因此也是Vue实例create的关键。
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created') // vm 已经创建好 回调 created 钩子函数

        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            vm._name = formatComponentName(vm, false)
            mark(endTag)
            measure(`vue ${vm._name} init`, startTag, endTag)
        }

        // render & mount
        if (vm.$options.el) {
            vm.$mount(vm.$options.el)
        }
    }
}