一.剖析全局Api

1.Vue.util

  1. // exposed util methods.
  2. // NOTE: these are not considered part of the public API - avoid relying on
  3. // them unless you are aware of the risk.
  4. Vue.util = {
  5. warn,
  6. extend,
  7. mergeOptions,
  8. defineReactive
  9. }

暴露的工具方法。这些方法不被视为公共API的一部分,除非你知道里面的风险,否则避免使用。(这个 util 是Vue 内部的工具方法,可能会发生变动),例如:在 Vue.router 中就使用了这个工具方法

2.Vue.set / Vue.delete

set 方法新增响应式数据

  1. export function set (target: Array<any> | Object, key: any, val: any): any {
  2. // 1.是开发环境target没定义或者是基础类型则报错
  3. if (process.env.NODE_ENV !== 'production' &&
  4. (isUndef(target) || isPrimitive(target))
  5. ) {
  6. warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  7. }
  8. // 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)
  9. if (Array.isArray(target) && isValidArrayIndex(key)) {
  10. target.length = Math.max(target.length, key)
  11. target.splice(key, 1, val)
  12. return val
  13. }
  14. // 3.如果是对象本身的属性,则直接添加即可
  15. if (key in target && !(key in Object.prototype)) {
  16. target[key] = val
  17. return val
  18. }
  19. const ob = (target: any).__ob__
  20. // 4.如果是Vue实例 或 根数据data时 报错
  21. if (target._isVue || (ob && ob.vmCount)) {
  22. process.env.NODE_ENV !== 'production' && warn(
  23. 'Avoid adding reactive properties to a Vue instance or its root $data ' +
  24. 'at runtime - declare it upfront in the data option.'
  25. )
  26. return val
  27. }
  28. // 5.如果不是响应式的也不需要将其定义成响应式属性
  29. if (!ob) {
  30. target[key] = val
  31. return val
  32. }
  33. // 6.将属性定义成响应式的
  34. defineReactive(ob.value, key, val)
  35. // 7.通知视图更新
  36. ob.dep.notify()
  37. return val
  38. }

Vue的缺陷:新增之前不存在的属性不会发生视图更新,修改数组索引不会发生视图更新 ( 解决方案就是通过$set方法, 数组通过splice进行更新视图,对象则手动通知)


3.Vue.nextTick

  1. const callbacks = []; // 存放nextTick回调
  2. let pending = false;
  3. function flushCallbacks () { // 清空队列
  4. pending = false
  5. const copies = callbacks.slice(0)
  6. callbacks.length = 0
  7. for (let i = 0; i < copies.length; i++) {
  8. copies[i]()
  9. }
  10. }
  11. let timerFunc
  12. export function nextTick (cb?: Function, ctx?: Object) {
  13. let _resolve
  14. callbacks.push(() => {
  15. if (cb) {
  16. try {
  17. cb.call(ctx) // 1.将回调函数存入到callbacks中
  18. } catch (e) {
  19. handleError(e, ctx, 'nextTick')
  20. }
  21. } else if (_resolve) {
  22. _resolve(ctx)
  23. }
  24. })
  25. if (!pending) {
  26. pending = true
  27. timerFunc(); // 2.异步刷新队列
  28. }
  29. // 3.支持promise写法
  30. if (!cb && typeof Promise !== 'undefined') {
  31. return new Promise(resolve => {
  32. _resolve = resolve
  33. })
  34. }
  35. }

不难看出nextTick原理就是将回调函数存入到一个队列中,最后异步的清空这个队列

  1. // 1.默认先使用Promise 因为 mutationObserver 有 bug 可能不工作
  2. if (typeof Promise !== 'undefined' && isNative(Promise)) {
  3. const p = Promise.resolve()
  4. timerFunc = () => {
  5. p.then(flushCallbacks)
  6. // 解决队列不刷新问题
  7. if (isIOS) setTimeout(noop)
  8. }
  9. isUsingMicroTask = true
  10. // 2.使用MutationObserver
  11. } else if (!isIE && typeof MutationObserver !== 'undefined' && (
  12. isNative(MutationObserver) ||
  13. MutationObserver.toString() === '[object MutationObserverConstructor]'
  14. )) {
  15. let counter = 1
  16. const observer = new MutationObserver(flushCallbacks)
  17. const textNode = document.createTextNode(String(counter))
  18. observer.observe(textNode, {
  19. characterData: true
  20. })
  21. timerFunc = () => {
  22. counter = (counter + 1) % 2
  23. textNode.data = String(counter)
  24. }
  25. isUsingMicroTask = true
  26. // 3.使用 setImmediate
  27. } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  28. timerFunc = () => {
  29. setImmediate(flushCallbacks)
  30. }
  31. // 4.使用setTimeout
  32. } else {
  33. timerFunc = () => {
  34. setTimeout(flushCallbacks, 0)
  35. }
  36. }

采用 EventLoop 中的微任务和宏任务,先采用微任务并按照优先级优雅降级的方式实现异步刷新

4.Vue.observable

2.6新增的方法,将对象进行观测,并返回观测后的对象。可以用来做全局变量,实现数据共享

  1. Vue.observable = <T>(obj: T): T => {
  2. observe(obj)
  3. return obj
  4. }

5.Vue.options

存放全局的组件、指令、过滤器的一个对象,及拥有_base属性保存Vue的构造函数

  1. ASSET_TYPES.forEach(type => {
  2. Vue.options[type + 's'] = Object.create(null)
  3. })
  4. Vue.options._base = Vue
  5. extend(Vue.options.components, builtInComponents) // 内置了 keep-alive
  6. export const ASSET_TYPES = [
  7. 'component',
  8. 'directive',
  9. 'filter'
  10. ]

6.Vue.use

Vue.use 主要的作用就是调用插件的 install 方法,并将 Vue 作为第一个参数传入,这样做的好处是可以避免我们编写插件时需要依赖 Vue 导致版本问题。

  1. initUse(Vue)
  2. Vue.use = function (plugin: Function | Object) {
  3. const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
  4. // 1.如果安装过这个插件直接跳出
  5. if (installedPlugins.indexOf(plugin) > -1) {
  6. return this
  7. }
  8. // 2.获取参数并在参数中增加Vue的构造函数
  9. const args = toArray(arguments, 1)
  10. args.unshift(this)
  11. // 3.执行install方法
  12. if (typeof plugin.install === 'function') {
  13. plugin.install.apply(plugin, args)
  14. } else if (typeof plugin === 'function') {
  15. plugin.apply(null, args)
  16. }
  17. // 4.记录安装的插件
  18. installedPlugins.push(plugin)
  19. return this
  20. }

7.Vue.mixin

全局混合方法, 可以用来提取公共方法及状态等.

  1. Vue.mixin = function (mixin: Object) {
  2. this.options = mergeOptions(this.options, mixin)
  3. return this
  4. }

Vue 对不同的属性做了不同的合并策略

  1. export function mergeOptions (
  2. parent: Object,
  3. child: Object,
  4. vm?: Component
  5. ): Object {
  6. if (!child._base) { // 1.组件先将自己的 extends 和 mixin 与父属性合并
  7. if (child.extends) {
  8. parent = mergeOptions(parent, child.extends, vm)
  9. }
  10. if (child.mixins) {
  11. for (let i = 0, l = child.mixins.length; i < l; i++) {
  12. parent = mergeOptions(parent, child.mixins[i], vm)
  13. }
  14. }
  15. }
  16. // 2.再用之前合并后的结果,与自身的属性进行合并
  17. const options = {}
  18. let key
  19. for (key in parent) {
  20. mergeField(key)
  21. }
  22. for (key in child) {
  23. if (!hasOwn(parent, key)) {
  24. mergeField(key)
  25. }
  26. }
  27. function mergeField (key) {
  28. const strat = strats[key] || defaultStrat; // 3.采用不同的合并策略
  29. options[key] = strat(parent[key], child[key], vm, key)
  30. }
  31. return options
  32. }

你可以通过查看 strats 这个对象来了解不同的合并策略。

8.Vue.extend

Vue 中非常核心的一个方法,可以通过传入的对象获取这个对象的构造函数,后续在组件初始化过程中会用到此方法

  1. Vue.extend = function (extendOptions: Object): Function {
  2. // ...
  3. const Super = this
  4. const Sub = function VueComponent (options) {
  5. this._init(options)
  6. }
  7. Sub.prototype = Object.create(Super.prototype)
  8. Sub.prototype.constructor = Sub
  9. Sub.options = mergeOptions( // 子组件的选项和 Vue.options进行合并
  10. Super.options,
  11. extendOptions
  12. )
  13. // ...
  14. return Sub;
  15. }

extend 创建的是 Vue 构造器,我们可以自己实例化并且将其挂载在任意的元素上

9.组件、指令、过滤器

  1. initAssetRegisters(Vue)
  2. ASSET_TYPES.forEach(type => {
  3. Vue[type] = function (
  4. id: string,
  5. definition: Function | Object
  6. ): Function | Object | void {
  7. if (!definition) {
  8. return this.options[type + 's'][id]
  9. } else {
  10. /* istanbul ignore if */
  11. if (process.env.NODE_ENV !== 'production' && type === 'component') {
  12. validateComponentName(id)
  13. }
  14. if (type === 'component' && isPlainObject(definition)) {
  15. definition.name = definition.name || id
  16. definition = this.options._base.extend(definition)
  17. }
  18. if (type === 'directive' && typeof definition === 'function') {
  19. definition = { bind: definition, update: definition }
  20. }
  21. this.options[type + 's'][id] = definition; // 将指令、过滤器、组件 绑定在Vue.options上
  22. // 备注:全局组件、指令过滤器其实就是定义在 Vue.options中,
  23. // 这样创建子组件时都会和 Vue.options进行合并,所以子组件可以拿到全局的定义
  24. return definition
  25. }
  26. }
  27. })

初始化全局的 api,Vue.component、Vue.directive、Vue.filter,这里仅仅是格式化用户传入的内容,将其绑定在 Vue.options选项上

二.剖析Vue的初始化过程

在上一节中我们已经查找到了Vue的构造函数

  1. function Vue (options) {
  2. if (process.env.NODE_ENV !== 'production' &&
  3. !(this instanceof Vue)
  4. ) {
  5. warn('Vue is a constructor and should be called with the `new` keyword')
  6. }
  7. this._init(options) // 当new Vue时会调用 _init方法
  8. }
  9. initMixin(Vue) // 初始化_init方法
  10. stateMixin(Vue) // $set / $delete / $watch
  11. eventsMixin(Vue) // $on $once $off $emit
  12. lifecycleMixin(Vue) // _update
  13. renderMixin(Vue) // _render $nextTick

Vue的源码结构非常的清晰,就是对原型进行了扩展而已。

1.Vue的初始化

通过Vue的_init方法,我们可以看到内部又包含了很多初始化的过程

  1. export function initMixin (Vue: Class<Component>) {
  2. Vue.prototype._init = function (options?: Object) {
  3. const vm: Component = this
  4. // 1.每个vue的实例上都有一个唯一的属性_uid
  5. vm._uid = uid++
  6. // 2.表示是Vue的实例
  7. vm._isVue = true
  8. // 3.选项合并策略
  9. if (options && options._isComponent) {
  10. initInternalComponent(vm, options)
  11. } else {
  12. vm.$options = mergeOptions(
  13. resolveConstructorOptions(vm.constructor),
  14. options || {},
  15. vm
  16. )
  17. }
  18. vm._self = vm
  19. // 4.进行初始化操作
  20. initLifecycle(vm)
  21. initEvents(vm)
  22. initRender(vm)
  23. callHook(vm, 'beforeCreate')
  24. initInjections(vm)
  25. initState(vm)
  26. initProvide(vm)
  27. callHook(vm, 'created')
  28. // 5.如果有el就开始进行挂载
  29. if (vm.$options.el) {
  30. vm.$mount(vm.$options.el)
  31. }
  32. }
  33. }

2._init方法中的初始化

这个代码写的真是一目了然,我们先看看每个方法”大概”干了什么事,切记不要死追到底!

  1. initLifecycle(vm) // 初始化组件间的父子关系
  2. initEvents(vm) // 更新组件的事件
  3. initRender(vm) // 初始化_c方法
  4. initInjections(vm)// 初始化inject
  5. initState(vm) // 初始化状态
  6. initProvide(vm) // 初始化provide


3.挂载流程

  1. // 1.如果有el就开始挂载
  2. if (vm.$options.el) {
  3. vm.$mount(vm.$options.el)
  4. }
  5. // 2.组件的挂载
  6. Vue.prototype.$mount = function (el,hydrating){
  7. el = el && inBrowser ? query(el) : undefined
  8. return mountComponent(this, el, hydrating);
  9. }
  10. // 3.创建渲染watcher进行渲染
  11. export function mountComponent (vm,el,hydrating) {
  12. vm.$el = el
  13. let updateComponent
  14. updateComponent = () => {
  15. vm._update(vm._render(), hydrating)
  16. }
  17. new Watcher(vm, updateComponent, noop, { // 创建渲染Watcher
  18. before () {
  19. if (vm._isMounted && !vm._isDestroyed) {
  20. callHook(vm, 'beforeUpdate')
  21. }
  22. }
  23. }, true /* isRenderWatcher */)
  24. return vm
  25. }