Vue2源码解读(一)-Vue声明

7,111 阅读2分钟

前篇

前端发展迅猛,当前最流行的框架非react和vue莫属,vue凭借着傻瓜式编程,使得前端开发更加简单高效,能够更快速的进行业务迭代。 文章将介绍Vue2源码,包括Vue的声明和实例的运行,从全方位解读Vue的实现和逐行解读。 文章对应Vue2的版本为:2.6.12,所使用浏览器为chrome,只介绍浏览器层面,不涉及小程序 作者所使用电脑node版本为:v12.13.0,npm版本为:6.13.0。

Vue声明-平台相关

Vue文件有两个入口,一个是带编译的src/platforms/web/entry-runtime-with-compiler.js;一个是不带编译的src/platforms/web/entry-runtime.js;第一个带编译的入口也会引用第二个入口,so咱们从第一个入口开启探索之旅。

文件:src/platforms/web/entry-runtime-with-compiler.js

// 保存runtime的$mount函数
const mount = Vue.prototype.$mount
// 重写$mount函数
Vue.prototype.$mount = function (
    el?: string | Element,
    hydrating?: boolean
): Component {
    return mount.call(this, el, hydrating)
}

从上面可以看出,mount是下面runtime里面声明的mount函数,在runtimewithcompiler里面会对mount函数,在runtime-with-compiler里面会对mount函数进行重写,挂载到Vue的原型对象上面。

文件:src/platforms/web/runtime/index.js

// 配置平台相关方法
// 判断是否是必须绑定prop的元素,比如input必须有value属性等
Vue.config.mustUseProp = mustUseProp
// 检测是否是保留标签,包含HTML标签和svg标签
Vue.config.isReservedTag = isReservedTag
// 检测是否是保留属性,包含class和style
Vue.config.isReservedAttr = isReservedAttr
// 获取标签命名空间
Vue.config.getTagNamespace = getTagNamespace
// 检测是否是未知类型的元素
Vue.config.isUnknownElement = isUnknownElement

// 挂载平台相关指令,platformDirectives 包含v-model和v-show
extend(Vue.options.directives, platformDirectives)
// 挂载平台相关组件,platformComponents 包含Transition和TransitionGroup
extend(Vue.options.components, platformComponents)

// 挂载patch函数,即vnode dom diff函数
Vue.prototype.__patch__ = inBrowser ? patch : noop

// 挂载$mount 函数
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

从上面可以看到,runtime里面,除了声明了$mount函数外,还挂载了平台相关的两个指令(model和show)以及两个组件(Transition和TransitionGroup),同时对Vue的config进行了扩展,添加了平台相关的几个方法。

Vue声明-核心

前面介绍了与平台(web类型,浏览器)相关的入口,现在了解到在此进行的一些配置、指令和组件,下面咱们来看核心部分的代码

文件:src/core/index.js

// 初始化配置
initGlobalAPI(Vue)

// 定义是否是服务端渲染
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
// 定义服务端渲染的全局对象
Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// 定义服务端渲染时的函数
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

文件:src/core/global-api/index.js

export function initGlobalAPI (Vue: GlobalAPI) {
  const configDef = {}
  configDef.get = () => config
  // def Vue配置config
  Object.defineProperty(Vue, 'config', configDef)
  // 定义util对象
  Vue.util = {
    warn, // 警告函数
    extend, // 扩展,继承函数
    mergeOptions, // 对options进行合并的函数
    defineReactive // 对属性进行设置的函数
  }
  // 定义set函数
  Vue.set = set
  // 定义delete函数
  Vue.delete = del
  // 定义nextTick函数
  Vue.nextTick = nextTick

  //暴露observable函数
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

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

  // 定义base
  Vue.options._base = Vue
  // 挂载组件,KeepLive
  extend(Vue.options.components, builtInComponents)
  // 定义Vue.use函数
  initUse(Vue)
  // 定义Vue.mixin函数
  initMixin(Vue)
  // 定义Vue.extend函数
  initExtend(Vue)
  // 定义Vue.compoenent、Vue.directive、Vue.filter函数
  initAssetRegisters(Vue)
}

initGlobalAPI文件里面对Vue的util对象、config对象、options和Vue本身进行了扩展。

文件:src/core/instance/index.js

// 本命函数
function Vue (options) {
  // 初始化,主函数
  this._init(options)
}

// 往Vue函数上绑定原型函数,例如上面的_init函数
initMixin(Vue)
// 初始化$data、$props、$set、$delete、$watch等函数
stateMixin(Vue)
// 定义$on、$once、$off、$emit等函数
eventsMixin(Vue)
// 定义update、$forceUpdate、$destroy等函数
lifecycleMixin(Vue)
// 定义$nextTick、render等函数
renderMixin(Vue)

上面instance文件对Vue函数进行了声明,并在Vue的原型对象上添加了各种属性方法。 简单流程图如下