剖析源码全局API & 执行流程 (二)

一.剖析全局Api

这里需要大家掌握Vue的基本应用,对Vue的全局API有一定的掌握

1.Vue.util

// 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
}
1
2
3
4
5
6
7
8
9

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

2.Vue.set / Vue.delete

set方法新增响应式数据

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

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

3.Vue.nextTick

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

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

timerFunc

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

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

4.Vue.observable

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

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

5.Vue.options

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

ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents) // 内置了 keep-alive
1
2
3
4
5

6.Vue.use

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

initUse(Vue)

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

7.Vue.mixin

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

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

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

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

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

8.Vue.extend

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

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

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

9.组件、指令、过滤器

initAssetRegisters(Vue)

ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition; // 将指令、过滤器、组件 绑定在Vue.options上

        // 备注:全局组件、指令过滤器其实就是定义在 Vue.options中,这样创建子组件时都会和Vue.options进行合并,所以子组件可以拿到全局的定义

        return definition
      }
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

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

二.剖析Vue的初始化过程

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

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) // 当new Vue时会调用 _init方法
}

initMixin(Vue)       // 初始化_init方法
stateMixin(Vue)      // $set / $delete / $watch
eventsMixin(Vue)     // $on $once $off $emit
lifecycleMixin(Vue)  // _update
renderMixin(Vue)     // _render $nextTick
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

1.Vue的初始化

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

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // 1.每个vue的实例上都有一个唯一的属性_uid
    vm._uid = uid++ 
    // 2.表示是Vue的实例
    vm._isVue = true

    // 3.选项合并策略
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    vm._self = vm
    // 4.进行初始化操作
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) 
    initState(vm)
    initProvide(vm) 
    callHook(vm, 'created')

    // 5.如果有el就开始进行挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

2._init方法中的初始化

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

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

3.挂载流程

// 1.如果有el就开始挂载 
if (vm.$options.el) {
    vm.$mount(vm.$options.el)
}

// 2.组件的挂载
Vue.prototype.$mount = function (el,hydrating){
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating);
}

// 3.创建渲染watcher进行渲染
export function mountComponent (vm,el,hydrating) {
  vm.$el = el
  let updateComponent
  updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  new Watcher(vm, updateComponent, noop, { // 创建渲染Watcher
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  return vm
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27