跟着 Vue源码学习 Vue api 系列 (二) - 全局 api
Vue.extend({})
源码
1 | function initExtend (Vue) { |
通过以上源码,我们可以看出 Vue 在 extend 的时候主要做了以下几个事情
- 首先进行兼容处理,保证传入的对象不为空
- 创建一个变量 Super 用来存储 Vue 实例,并且存储 Vue 的 cid
- 判断当前 extend 是否已经被使用过,如果已经使用过,直接返回,以减少内存消耗
- 创建一个新的构造函数 Sub ,原型指向 Super 的原型,并创建一个唯一的 cid
- 接下来通过 mergeOptions 方法,将传入的 参数 与 Vue 实例的 options(data,lifeCycle,methods,props,methods 等) 合并,遵循 Vue 内部 config 中定义的optionMergeStrategies 配置
- 初始化 props 和 computed 属性
- 重新对 extend,mixin,use,filter,directive,component 属性进行赋值
- 最后 通过 _Ctor 缓存 当前创建的 Vue.extend 实例
- 返回 当前 Sub 构造函数,该构造函数 具有 Vue 所有属性及方法
使用
1 | var Profile = Vue.extend({ |
当我们 使用 Vue.extend 为 Vue 扩展一些信息的时候,会根据 Vue 的 optionMergeStrategies 配置的默认属性合并规则进行属性 合并,这样我们在任意组件都可以使用到这些属性和方法
Vue.nextTick([callback: Function,context: Object])
Vue 更新页面并不是同步更新的,而是采用异步更新的。
浏览器有一个更新循环 tick,这个 tick 时间间隔大概十几毫秒,Vue 在到浏览器执行下一个 tick 的时间段内搜集所有需要更新的 Dom 数据,在 下一个 tick 循环到来的时候同步更新到页面上。这时候就会造成一个问题,如果我们想在改变数据之后立刻获取页面的数据的话,这时候还没有触发浏览器的 tick 更新,页面没有变化,我们是获取不到更新后的 DOM 数据
使用 Vue.nextTick 可以使我们在页面更新完成之后获取 DOM
源码
1 | function nextTick (cb, ctx) { |
在 timerFunc() 函数 中 通过判断 Promise MutationObserver 及 setImmediate函数是否存在 如在就使用对应函数,不存在就使用setTimeout方法
使用
1 | Vue.nextTick(() => { |
Vue.set(target: Object | Array, index: Number | string, value: any)
用于修改一些 Vue 响应式无法监听到的对象属性变更,同时触发视图更新
源码
1 | function set (target, key, val) { |
- 首先判断传过来的 target 是否存在,及数据类型是否是基本数据类型
- 判断 target 是否是数组,以及 key 值 是否可用,然后调用 Array 的 slice 方法替换数组数据
- 判断 key 是否属于 target
- 判断 传入 target 对象 是否是 Vue 实例
- 调用 defineReactive$$1 监听数据变化
- 调用 发布订阅模式 的 发布方法
使用
1 | Vue.set(target, key, value) |
Vue.delete(target: Object | Array, index: Number | string)
删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。
源码
1 | function del (target, key) { |
- 首先判断传过来的 target 是否存在,及数据类型是否是基本数据类型
- 判断 target 是否是数组,以及 key 值 是否可用,然后调用 Array 的 slice 方法替换数组数据
- 判断 传入 target 对象 是否是 Vue 实例
- 删除
target[key]
- 调用 发布订阅模式 的 发布方法
使用
1 | Vue.delete(target, key) |
Vue.directive( id: string, [definition]: Function | Object )
此方法主要是为了给 Vue 设置自定义指令 及获取 Vue 指令
源码
首先 定义了一个 platformDirectives
存储 directive 指令的属性,然后通过 extend 方法,将 platformDirectives
上的属性 复制到 Vue.options.directives
上1
2
3
4
5
6var platformDirectives = {
model: directive,
show: show
};
extend(Vue.options.directives, platformDirectives)
directive 对象的属性
1 | var directive = { |
- install我们可以看到在 inserted 函数中对 指令绑定的元素做了一个判断, 针对 select,textarea标签及 input 标签且属性是 是文本输入类型的元素做了特殊处理
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
28inserted: function inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', function () {
directive.componentUpdated(el, binding, vnode);
});
} else {
setSelected(el, binding, vnode.context);
}
el._vOptions = [].map.call(el.options, getValue);
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers;
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart);
el.addEventListener('compositionend', onCompositionEnd);
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd);
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true;
}
}
}
},
componentUpdated
1 | componentUpdated: function componentUpdated (el, binding, vnode) { |
在 componentUpdated 方法中 对select 标签的元素做了特殊处理,渲染其子元素 option
bind,update,unbind
针对元素的 display 进行处理
使用
insert 函数传入参数有四个
- el 当前指令挂载的元素节点
- binding
v-[name]:[arg].[modifies].[modifies]="[expression]"
name: 指令名
value: 指令的绑定值
oldValue: 指令绑定的前一个值,value参数改变之前的值
expression: 字符串形式的指令表达式
arg: 传递指令的参数
modifiers: 包含修饰符的对象
首先 如果 express 没有话 不会获取到 value, 如果想要获取 value,就要包含有 expression, 则该 expression 必须在 data 或者 computed 上必须要在第一层上
就算是 expression 用 ex1.ex2 或者 ex1[ex2],value 的值为data[ex1]
或者computed[ex1]
当 expression 为一个可以计算的表达式的时候 例如1+1
,ex1 + 1
等,value 的结果为 expression 计算之后的结果 - vnode 虚拟node节点
- oldVnode 上一个虚拟 DOM 节点
1 | <div id="app"> |
Vue.filter(id:string, definition: Function)
源码
1 | var ASSET_TYPES = [ |
以上代码 是 ‘component’, ‘directive’, ‘filter’ 三个全局API 注册到 Vue 实例上的方法
如果是 component 则检查 组件名称是否是合格的组件名称 然后在判断 是否是 definition 参数是否是对象,如果是,则使用 Vue 实例的 extend 方法扩展全局的 extend 属性
如果是 directive 如果传入的 definition 是函数 则默认调用 bind 和 update 方法
使用
1 | <div>{{ test | uppcase}}<div> |
Vue.component(id: string, definition: Function | Object)
源码
请查看 Vue.filter 的讲解
使用
1 | Vue.componet('my-component', { |
definition 可以传递 Vue.extend({}),也可以直接传一个对象,Vue会自动调用 Vue.extend 方法
Vue.use(plugin)
源码
1 | function initUse (Vue) { |
使用
1 | // plugin必须是一个函数 或者包含 install 的对象 |
Vue.mixin(plugin)
源码
1 | function initMixin$1 (Vue) { |
此方法 调用了 mergeOptions 方法将 传入的参数 与 Vue 的 options 属性合并
使用
这个方法会影响 所有创建的 Vue 组件,不推介使用,但是可以用于 optionMergeStrategies 自定义 选项1
2
3
4
5
6
7
8
9
10
11
12
13Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
Vue.compile
将一个模板字符串编译成 render 函数
源码
没看懂,有时间再来研究(2021-01-24)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
86
87
88
89
90
91
92
93
94
95
96
97
98function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (
template,
options,
vm
) {
options = extend({}, options);
var warn$$1 = options.warn || warn;
delete options.warn;
/* istanbul ignore if */
{
// detect possible CSP restriction
try {
new Function('return 1');
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn$$1(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
);
}
}
}
// check cache
var key = options.delimiters
? String(options.delimiters) + template
: template;
if (cache[key]) {
return cache[key]
}
// compile
var compiled = compile(template, options);
// check compilation errors/tips
{
if (compiled.errors && compiled.errors.length) {
if (options.outputSourceRange) {
compiled.errors.forEach(function (e) {
warn$$1(
"Error compiling template:\n\n" + (e.msg) + "\n\n" +
generateCodeFrame(template, e.start, e.end),
vm
);
});
} else {
warn$$1(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
vm
);
}
}
if (compiled.tips && compiled.tips.length) {
if (options.outputSourceRange) {
compiled.tips.forEach(function (e) { return tip(e.msg, vm); });
} else {
compiled.tips.forEach(function (msg) { return tip(msg, vm); });
}
}
}
// turn code into functions
var res = {};
var fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
{
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn$$1(
"Failed to generate render function:\n\n" +
fnGenErrors.map(function (ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
}
return (cache[key] = res)
}
}
使用
1 | var res = Vue.compile('<div><span>{{ msg }}</span></div>') |
Vue.observable(object)
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象
源码
1 | function observe (value, asRootData) { |
调用了 通過 new Observer() 在 Observer 内部通过 walk 方法 调用 defineReactive$$1 进行对象响应式处理
使用
1 | const state = Vue.observable({ count: 0 }) |
Vue.version
提供 Vue 的版本号
1 | Vue.version = '2.6.12'; |