学习代码仓库

# 全局api解析

友情提示:点击每个api的标题可以直接打开API的文档

上篇说到了vue的初始化流程,其中给原型初始化的过程我们之后再分析,先把不执行new就能用的全局Api看了。 我的思路是根据initGlobalApi(Vue)方法的顺序往下看

# Vue.config

这是一个全局属性,默认是读取的这个配置

  • silent
    • 默认值:false
    • 作用:是否取消 Vue 所有的日志与警告。
  • optionMergeStrategies
    • 类型:{ [key: string]: Function }
    • 默认值 {}
    • 作用:自定义合并策略的选项
  • devtools
    • 默认值:开发版本默认为true,生产版本默认为false
    • 作用:配置是否允许vue-devtools检查代码。生产版本设为 true 可以启用检查。
  • errorHandler
    • 类型: function
    • 默认值:undefined
    • 作用:指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例。
  • warnHandler
    • 类型:Function
    • 默认值:undefined
    • 作用:为 Vue 的运行时警告赋予一个自定义处理函数。注意这只会在开发者环境下生效,在生产环境下它会被忽略。
  • ignoredElements
    • 类型:Array<string | RegExp>
    • 默认值:[]
    • 作用:须使 Vue 忽略在 Vue 之外的自定义元素 (e.g. 使用了 Web Components APIs)。否则,它会假设你忘记注册全局组件或者拼错了组件名称,从而抛出一个关于Unknown custom element的警告。
  • keyCodes
    • 类型:{ [key: string]: number | Array<number> }
    • 默认值:{}
    • 作用:给v-on自定义键位别名。
  • performance
    • 类型:boolean
    • 默认值:false
    • 作用: 设置为true以在浏览器开发工具的性能/时间线面板中启用对组件初始化、编译、渲染和打补丁的性能追踪。只适用于开发模式和支持performance.mark API的浏览器上
  • productionTip
    • 类型:boolean
    • 默认值:process.env.NODE_ENV !== 'production'
    • 作用:设置为false以阻止vue在启动时生成生产提示。

配置文件里还有一些属性,不过没有写到文档里,所以应该不推荐用户修改配置,所以就不说啦。

# Vue.set

相关问题文档

设置对象的属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开Vue不能检测属性被添加的限制。

// ./global-api/index.js
import { set, del } from '../observer/index'
// ...
Vue.set = set
// ...

// './observer/index.js'

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target, key, val) {
  // 如果target是一个数组,而且key是一个数组的索引值,直接用数组方法splice()可以直接触发响应的监听,返回
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 如果key在target里,而且key不是在Object本身就有的属性,直接更新返回
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 缓存观察者
  const ob = (target).__ob__
  // 判断target是否是Vue实例或者根数据对象,如果是,则报错,所以
  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
  }
  // 如果target不是响应式的,就直接返回修改后的值
  if (!ob) {
    target[key] = val
    return val
  }
  // 传入defineReactive方法,让target的key属性可响应式,然后触发观察者的依赖的notify()方法
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  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
38
39
40
41
42

# Vue.delete

// ./global-api/index.js
import { set, del } from '../observer/index'
// ...
Vue.delete = del
// ...

// './observer/index.js'

/**
 * Delete a property and trigger change if necessary.
 */
export function del (target: Array<any> | Object, key: any) {
  // 如果target是一个数组,而且key是一个数组的索引值,直接用数组方法splice()可以直接触发响应的监听,返回
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target).__ob__
  // 判断target是否是Vue实例或者根数据对象,如果是,则报错
  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
  }
  // 如果key不是target的属性,返回
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  // 如果target不是响应的,返回
  if (!ob) {
    return
  }
  // 手动通知
  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
32
33
34
35
36
37
38

# Vue.nextTick

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise

代码里的microTimerFuncmacroTimerFunc分别是 微任务宏任务 ,这里的microTimerFunc主要是用的Promise,若不支持PromisemicroTimerFunc=macroTimerFuncmacroTimerFunc则是用的setImmediate,若不支持setImmediate则用setTimeout

代码比较简单,就是推入一个callback数组,然后等待被调用

const callbacks = []
let pending = false

// Here we have async deferring wrappers using both micro and macro tasks.
// In < 2.4 we used micro tasks everywhere, but there are some scenarios where
// micro tasks have too high a priority and fires in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using macro tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use micro task by default, but expose a way to force macro task when
// needed (e.g. in event handlers attached by v-on).
let microTimerFunc
let macroTimerFunc
let useMacroTask = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    if (useMacroTask) {
      /**
       * macroTimerFunc = () => {
       *   setImmediate(flushCallbacks)
       * }
      */
      macroTimerFunc()
    } else {
      /**
       * const p = Promise.resolve()
       * microTimerFunc = () => {
       *  p.then(flushCallbacks)
       * }
      */
      microTimerFunc()
    }
  }
  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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# Vue.use

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。当 install 方法被同一个插件多次调用,插件将只会被安装一次。

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // 缓存或初始化 installedPlugins
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 若已经存在该plugin(引用相同),返回
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1) // Convert an Array-like object to a real Array
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin) // this._installedPlugins同时会改变
    return this
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Vue.mixin

全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。

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

# Vue.extend

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。 data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数。这里我的理解是因为创建出来的子类的实例会共享这个options.data配置,实例之间对于data的修改也会互相影响

export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1
  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) { // 如果有通过该构造器和options对象构造的示例缓存,直接返回
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name // 读取name或Super的name
    if (process.env.NODE_ENV !== 'production' && name) {
      // 校验name属性值
      validateComponentName(name)
    }

    const Sub = function VueComponent (options) { // Sub类,调用Vue的构造函数_init
      this._init(options)
    }
    // 组合继承
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub

    Sub.cid = cid++
    // 合并options
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    // 在./instance/init.js resolveConstructorOptions等方法中会用到这个字段
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 把props和computed放在原型链上,这样可以避免每次实例创建都要去调用Object.defineProperty
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // 保存一个Super.options的引用,在之后实例化的时候我们可以判断是否父类的options发生了更新
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor 缓存
    cachedCtors[SuperId] = Sub
    return Sub
  }

  function initProps (Comp) {
    const props = Comp.options.props
    for (const key in props) {
      proxy(Comp.prototype, `_props`, key)
    }
  }
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

# Vue.componentsVue.directiveVue.filter

Vue.components、Vue.directive、Vue.filter三个方法的定义都写在了initAssetRegisters方法里。

  • Vue.components:注册或获取全局组件。注册还会自动使用给定的id设置组件的名称 参考:组件
  • Vue.directive: 注册或获取全局指令。 参考:自定义指令
  • Vue.filter: 注册或获取全局过滤器。参考:过滤器

它们的用法类似:

// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))

// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })

// 获取注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')

// directive
// 注册
// 钩子函数
Vue.directive('my-directive', {
  bind: function () {}, // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  inserted: function () {}, // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  update: function () {}, // 所在组件的 VNode 更新时调用
  componentUpdated: function () {}, // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  unbind: function () {} // 只调用一次,指令与元素解绑时调用。
})

// 注册 (指令函数)
Vue.directive('my-directive', function () {
  // 这里将会被 `bind` 和 `update` 调用
})

// getter,返回已注册的指令
var myDirective = Vue.directive('my-directive')
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

定义:

// export const ASSET_TYPES = [
//   'component',
//   'directive',
//   'filter'
// ]
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // 如果没有传入definition, 则通过id去返回之前注册过的
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          // 校验components的名字
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          // 如果是component, 且传入的是个对象,则用component被初始化前的状态去调用extend
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          // 如果是directive,且传入的是个函数,则给bind和update赋值
          definition = { bind: definition, update: definition }
        }
        // 注册
        this.options[type + 's'][id] = definition
        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
30
31
32
33
34
35
36
37
38
39

# 知识点