vue2与vue3源码解析对比——vue2本质是什么(2)

276 阅读3分钟

Runtime Only VS Runtime+Compiler

通常我们利用vue_cli去初始化项目的时候会询问我们用Runtime Only版本还是Runtime+Compiler版本。下面我们来对比这两个版本。

  • Runtime Only

我们在使用Runtime Only版本的Vue.js的时候,通常需要借助如webpack的vue-loader工具把.vue文件编译成JavaScript,因为是在编译阶段做的,所以它只包含运行时的Vue.js代码,因此代码体积也会更轻量。

  • Runtime+Compiler

如果我们没有对代码做预编译,但又使用了Vue的template属性并传入一个字符串,则需要在客户端编译模版,如下所示:

// Runtime+Compiler
new Vue({
    template: '<div>{{hi}}</div>'
})

// Runtime Only
new Vue({
    render (h){
        return h('div', this.hi)
    }
})

在Vue.js 2.0中,最终渲染都是通过render函数,如果写template属性,则需要编译成render函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本。

很显然,这个编译过程对性能会有一定损耗,所以通常我们更推荐使用Runtime-Only 的Vue.js。

入口文件

我们通过Runtime+Compiler版本来追踪一下Vue的定义。找到以下目录文件 src/platforms/web/entry-runtime-with-compiler.js

/* @flow */

import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

...

Vue.compile = compileToFunctions

export default Vue

当我们的项目里执行 import Vue from 'vue'的时候,就是从这个入口执行代码来初始化Vue,我们从这个文件追踪下去。

Vue初始化定义

在上面的文件中我们看到import Vue from './runtime/index'这一行,打开文件

// 1.src/platforms/web/runtime/index.js
/* @flow */

import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'

import {
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

...

export default Vue

这个文件也只是扩展了vue的属性和一些方法,继续往下走,根据import Vue from 'core/index',找到src/core/index.js文件

// 2.src/core/index.js
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue)

...

Vue.version = '__VERSION__'

export default Vue

接着往下追踪,import Vue from './instance/index'

// 3.src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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)

export default Vue

Congratulations!我们终于走到了终点,Vue原来是一个function,也就是说vue本质是使用一个构造函数进行实例化。 当我们使用new Vue()的时候,是调用了this._init(options),而这个方法是由initMixin(Vue)挂载在Vue的原型prototype上。为什么不使用ES6的Class去实现而使用这个方式呢?主要是可以把功能扩展分散到多个模块中去实现,方便维护和管理;而不是在一个Class模块中实现所有。

initGlobalAPI

在初始化过程中,除了在prototype上扩展外,还给Vue这个对象本身扩展了全局的静态方法

// src/core/global-api/index.js
/* @flow */

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
    ...
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

    ...

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}


这里就是Vue上扩展的一些全局方法的定义,比如Vue.util.Vue.util暴露的方法最好不要依赖,因为它可能会经常发生变化,是不稳定的。

总结

Vue本质上就是一个用Function实现的Class,然后它的原型prototype和它本身都扩展了一系列的方法和属性。