Vue源码学习系列02——Vue的初始化都做了什么

博客更新地址啦~,欢迎访问:https://jerryyuanj.github.io/

开始之前,我们先看看src目录的结构

目录结构

  • compiler
    编译器
  • core
    vue的核心部分,包含响应式原理、vdom,内部组件等
  • platforms
    因为vue是跨平台的(web 与 weex),在不同平台的编译、运行等行为有所不同,所以这里是给这两个平台量身定制的
  • server
    服务端渲染相关
  • sfc
    单文件组件的解析
  • shared
    全局共享的工具函数(util.js)和常量(constants.js)

runtime-only版本与runtime-with-compiler版本的区别

参考官网:Vue完整版与编译版的区别 — Vue.js

补充一下,这段判断的代码位于:src/core/instance/lifecycle.js 中的 mountComponent 方法中,

if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
    vm.$options.el || el) {
    warn(
      'You are using the runtime-only build of Vue where the template ' +
      'compiler is not available. Either pre-compile the templates into ' +
      'render functions, or use the compiler-included build.',
      vm
    )
  }

通过判断条件我们可以大致的了解到,当传递 template 并且 template的第一个字符不是#号,或者 el 存在的时候,runtime-only 版本会报错,提示你需要使用带compiler的完整版。具体逻辑我们后面会说,这里先有个了解。

从入口文件开始

我们使用Vue的时候都是new一个Vue实例,很显然我们要找的入口就是Vue的构造函数。

Vue的构造函数位于 src/core/instance/index.js中,代码如下:

function Vue (options) {
  // 在非生产环境下, 如果不是使用new来调用这个构造器则会报警告
  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

啰嗦一句,关于如何找到这个构造函数,如果你经常看源码,那么可以凭经验找到core/index.js中来,这里面从其他地方import了Vue,再顺着依赖找就可以直接定位到这个构造器了。但是标准方法应该是先看根目录下的/scripts/config.js,这里面包含着rollup打包的各项配置,随便找一个entry,再顺着依赖,就可以一步一步定位到Vue的构造器中。

Vue的构造函数非常简单,只执行了一句代码:

this._init(options)

这个_init()方法从哪来,我们后面会讲。

好了,今天的源码分析到此结束~~

hahahaha,开个玩笑,接下来才是正文。
我们看到这个文件中还有五个看上去就很厉害的方法。

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

为什么说呢,因为这就像数学题一样,往往字数越少的题目越难。OK,我们先来看看这五个函数吧。

initMinxin

位于: src/core/instance/init.js

这个函数中其实就定义了一个方法,所以这个initMixin实际上就做了一件事,就是在给Vue的原型添加了一个_init方法(对,就是Vue构造器里面的唯一的方法):

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function(){
    //....具体实现逻辑
  }
}

虽然就一个,但是为了跟下面保持一致,还是先列出来吧:

  • _init

stateMixin

位于:src/core/instance/state.js

我们还是像上面一样,先粗略的看看吧:

...

Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)

Vue.prototype.$set = set
Vue.prototype.$delete = del

Vue.prototype.$watch = function(expOrFn, cb, options){
  // 返回一个函数
}

看上去也蛮清晰的,也是初始化了一些变量啊方法啊什么的,我们先列出来:

  • $data
  • $props
  • $set
  • $delete
  • $watch

等等,慢着,这些方法我好像在哪儿看过?
soga~原来这些是Vue的API啊~~

你可以在API中找到这五个属性或方法哦:API — Vue.js

好像有点意思了吧,我们继续往下看

eventMixin

位于: src/core/instance/events.js

好像找到规律了,这个方法做了这些事:

Vue.prototype.$on = function(..){...}
Vue.prototype.$once = function(..){...}
Vue.prototype.$off = function(..){...}
Vue.prototype.$emit = function(..){...}

列出来:

  • $on
  • $once
  • $off
  • $emit

这些方法是干啥的,我想你应该知道在哪看了吧。传送门:Vue实例 方法-事件 API — Vue.js

lifecycleMixin

位于:src/core/instance/lifecycle.js

同样的:

Vue.prototype._update = function(){}
Vue.prototype.$forceUpdate = function(){}
Vue.prototype.$destroy = function(){}

列一下:

  • _update
  • $forceUpdate
  • $destroy

renderMixin

位于:src/core/instance/render.js

老样子:

// install runtime convenience helpers
installRenderHelpers(Vue.prototype)

Vue.prototype.$nextTick = function(){}

Vue.prototype._render = function(){}

列一下…慢着,这个有点不太一样啊,这个installRenderHelpers是干什么的?

点进去看看会发现,这实际上还是给Vue的原型添加了方法,不过这些方法是运行时用到的,相当于运行时的助手一样。

该方法位于: src/core/instance/render-helpers/index.js。
这个文件的方法就这一个,我们来看看:

/* @flow */

import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'
import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'
import { renderList } from './render-list'
import { renderSlot } from './render-slot'
import { resolveFilter } from './resolve-filter'
import { checkKeyCodes } from './check-keycodes'
import { bindObjectProps } from './bind-object-props'
import { renderStatic, markOnce } from './render-static'
import { bindObjectListeners } from './bind-object-listeners'
import { resolveScopedSlots } from './resolve-slots'

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
}

呃。。。也没什么嘛,就是给target扩充方法呗,这个target是我们的Vue.prototype,所以老样子,该列还得列:

  • _o
  • _n
  • _s
  • _l
  • _t
  • _q
  • _i
  • _m
  • _f
  • _k
  • _b
  • _v
  • _e
  • _u
  • _g
  • $nextTick
  • _render

你以为结束了吗?不,还差点东西。。。。

core/index.js

我们上面分析的,是最最最靠里面的Vue,即最原始的。这里暴露出去的Vue,在 src/core/index.js 中又做了一些工作。实际上,其他地方引入的Vue,都是这个 core/index.js 中的。所以我们再来看看这个文件对Vue做了什么。代码不多:

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)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

首先的一句 initGlobalAPI(Vue) 我们先不看,下面的一些代码也是给Vue添加属性或方法的,只不过用的是ES5的Object.defineProperty()来定义的。
首先,Vue.prototype.$isServerVue.prototype.$ssrContext 这样定义后,是只读的(无set),而且不可枚举(enumurable默认false)。然后再给Vue定义了一个FunctionRenderContext的属性,并且也是只读的不可枚举的。至于这几个属性是干什么的,看上去是跟ssr还有render相关的,后面分析到会讲。

还有这句:

Vue.version = '__VERSION__'

很显然是当前Vue的版本,那么这个 __VERSION__ 是什么东西呢?
它的定义是在: /scripts/config.js 中,rollup在构建的时候会自动给它赋值,用什么值呢?请看:

const version = process.env.VERSION || require('../package.json').version

OK, 一目了然啦。

再来看 initGlobalAPI(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 {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  // Vue 的全局配置对象
  // 不可以使用 Vue.config = XXX 的形式来修改这个 config 对象, 只可以修改其内部的属性
  // 否则非生产环境中会报错
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // 工具
  // 注意: 这些不算是公共的API, 所以除非你很清楚它的风险,否则不要使用他们
  // 所以你在 Vue 的 API 里面,看不到这四个util方法
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 给 options 添加 'components', 'directives', 'filters' 三个属性, 初始值 {}
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  // Vue.options.components 的扩充(目前只有一个 keep-alive 组件)
  extend(Vue.options.components, builtInComponents)
  // 插件: Vue.use
  initUse(Vue)
  // 混合: Vue.mixin
  initMixin(Vue)
  // 继承: Vue.extend
  initExtend(Vue)
  // 注册一些资源属性,'component', 'directive', 'filter'
  // 即: Vue.component, Vue.directive, Vue.filter 
  initAssetRegisters(Vue)
}

OK,有点熟悉,也是给Vue扩充方法,不过这里是直接给Vue扩充而不是给Vue.prototype扩充。比较简单,而且注释上写的也比较详细,就直接罗列了:

  • Vue.config (object)
  • Vue.util (object)
    {warn, extend, mergeOptions, defineReactive}
    
  • Vue.set (function)
  • Vue.delete (function)
  • Vue.nextTick (function)
  • Vue.options (object)
    {components: {keepAlive}, directives: {}, filters: {}, _base: Vue}
    
  • Vue.use (function)
  • Vue.mixin (function)
  • Vue.extend (function)
  • Vue.component (function)
  • Vue.directive (function)
  • Vue.filter (function)

OK,这下真的差不多了,这节表面上啥也没干,但是至少对Vue整个初始化对过程有个大概对印象了吧。从上面对分析中我们不难发现,_xxx一般都是Vue内部对方法,$xxx 一般都是api对方法,所以当我们不知道某个 $xxx方法是干啥的话,可以去api文档看看,至少可以带着结果去看源码,效率也高一点。


下一节我们将开始深入分析这些函数都做了哪些事。

你可能感兴趣的:(前端,vue,vue2源码学习)