一只很酷的蘑菇

vue源码学习笔记--全局api解析

阅读量:

学习代码仓库

全局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不能检测属性被添加的限制。

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
// ./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
}

Vue.delete

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
// ./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()
}

Vue.nextTick

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

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

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

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

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
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
})
}
}

Vue.use

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}
}

Vue.mixin

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

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

Vue.extend

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

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
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)
}
}
}

Vue.componentsVue.directiveVue.filter

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

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

它们的用法类似:

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
// 注册组件,传入一个扩展过的构造器
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
28
29
30
31
32
33
34
35
36
37
38
39
// 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
}
}
})
}

知识点

陈柯伊

梦想一克拉 热血一卡车 眼泪一酒瓶 熬夜一光年

评论

文章目录