Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 源码学习(二) - 构造函数 #9

Open
zhuping opened this issue Apr 8, 2019 · 0 comments
Open

Vue 源码学习(二) - 构造函数 #9

zhuping opened this issue Apr 8, 2019 · 0 comments

Comments

@zhuping
Copy link
Owner

zhuping commented Apr 8, 2019

通过上文 Vue 源码学习(一) - 目录结构与构建,我们了解了整个项目的目录结构,这次我们就从构造函数开始,看 vue 到 export default Vue 都经历了什么。

声明:为了更好的理解 vue 源码,我们将以完整版 umd 模块的 vue 为入口

构造函数

根据 entry 配置,找到入口文件,

// src/platforms/web/entry-runtime-with-compiler.js

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

import Vue from './runtime/index'
...

根据文件的引用关系找到 Vue 的构造函数所在

// src/platforms/web/runtime/index.js

import Vue from 'core/index'
...
// src/core/index.js

import Vue from './instance/index'
...
// 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)

export default Vue

最终我们在 src/core/instance/index.js 中找到了 Vue 的构造函数,内容部分也很简单,传入一个 options 参数,如果是非生产环境且没用使用 new Vue 的方式调用函数,给出警告,然后执行 _init 方法。

接着经过如下

  • initMixin
  • stateMixin
  • eventsMixin
  • lifecycleMixin
  • renderMixin

5个方法的包装处理,最终对外抛出 Vue

我们后面的分析,也是根据这几个方法的顺序来分析,他们内部到底做了哪些处理。

全局API

在此之前,我们再来看下

// src/core/index.js

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'

initGlobalAPI(Vue)
...

通过上一篇文章的目录可知,global-api 是负责给 Vue 构造函数挂载全局的方法(静态方法)或属性的代码。它对我们后续的代码分析很重要,所以在分析 _init 前,我们不妨先看看 global-api 具体挂载了哪些方法和属性呢?

// src/core/global-api/index.js

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'

export function initGlobalAPI (Vue: GlobalAPI) {
  // 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.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

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

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  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

  extend(Vue.options.components, builtInComponents)

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

我们来逐行看下代码,首先是在构造函数 Vue 上增加了一个 config 的静态只读属性,读取 config 返回的是 core/config.js 中导出的对象,如果试图修改该值时,在非生产环境下会给一个警告。

接着是扩展了 util 属性,增加了 warnextendmergeOptionsdefineReactive 四个方法。

Vue.util = {
  warn,
  extend,
  mergeOptions,
  defineReactive
}

然后是在 Vue 上增加了 setdelete 以及 nextTick 三个方法,同时在 2.6 以上版本,新增了 observable 方法,原本必须在一个 Vue 实例中配置的响应对象,现在可以在 Vue 实例外部通过 Vue.observable(data) 来创建了。

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

// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
  observe(obj)
  return obj
}

下面是对 options 属性的创建,后面的 options 合并章节中起着关键的作用。

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

首先是创建一个空对象,然后遍历 src/shared/constants.jsASSET_TYPES 数组,分创建一个空对象,

// src/shared/constants.js

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

经过处理后的 options 最终变成:

Vue.options = {
  components: Object.create(null),
  directives: Object.create(null),
  filters: Object.create(null),
  _base: Vue
}

紧接着的这句代码:

extend(Vue.options.components, builtInComponents)

builtInComponents 返回的值与 Vue.options.components 进行合并操作,

// src/core/components/index.js

import KeepAlive from './keep-alive'

export default {
  KeepAlive
}

最终 Vue.options 变为:

Vue.options = {
  components: {
    KeepAlive
  },
  directives: Object.create(null),
  filters: Object.create(null),
  _base: Vue
}

在文件最后,还有4个 init* 方法,我们再来逐个看下每个方法都是干嘛的。

// src/core/global-api/use.js

export function initUse(Vue: GlobalAPI) {
  Vue.use = function(plugin: Function | Object) {
    ...
  }
}

该方法的作用是在 Vue 构造函数上添加 use 方法,该方法是用来安装 Vue 插件的。

// src/core/global-api/mixin.js

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

该方法是在 Vue 上添加 mixin 方法,该方法是全局注册一个混入,并且会影响注册之后所有创建的每个 Vue 实例。

// src/core/global-api/extend.js

export function initExtend(Vue: GlobalAPI) {
  Vue.cid = 0;
  let cid = 1;
  
  ...
  
  Vue.extend = function(extendOptions: Object): Function {
    
  }
}

该方法是在 Vue 上添加了 Vue.cid 静态属性和 extend 静态方法。该方法继承自 Vue 构造器,创建一个子类。

// src/core/global-api/assets.js

export function initAssetRegisters(Vue: GlobalAPI) {
  ASSET_TYPES.forEach(type => {
    Vue[type] = function() {
      // ...
    }  
  })
}

ASSET_TYPES 我们已不陌生,就是上面提到的 componentdirectivefilter。该方法在 Vue 上增加了三个静态方法,他们分别用来全局注册组件,指令和过滤器。

我们再回溯到前面的文件,分别扩展了 directivescomponents

// src/platforms/web/runtime/index.js

import platformDirectives from './directives/index'
import platformComponents from './components/index'

extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// src/platforms/web/runtime/directives/index.js

export default {
  model,
  show
}
// src/platforms/web/runtime/components/index.js

export default {
  Transition,
  TransitionGroup
}

小结

至此整个构造函数都讲解完了,主要是通过 global-api/index.js 这个文件,对 Vue 的构造函数增加了许多静态属性和方法,方便后续的使用。此时 Vue 上挂载的属性和方法如下:

Vue.config

Vue.util = {
  warn,
  extend,
  mergeOptions,
  defineReactive
}

Vue.set
Vue.delete
Vue.nextTick
Vue.observable

Vue.options = {
  components: {
    KeepAlive,
    Transition,
    TransitionGroup
  },
  directives: {
    model,
    show
  },
  filters: Object.create(null),
  _base: Vue
}

Vue.use
Vue.mixin
Vue.extend
Vue.cid
Vue.components
Vue.directives
Vue.filters

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant