前言
本文是vue2.x源码分析的第二篇,主要讲解Vue初始化过程!
1、贯穿全文的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue</title>
<script src="./vue.js" type="text/javascript" charset="utf-8" ></script>
</head>
<body>
<div id="app">
{{message}}
<div v-text='abc' v-on:click='methods_a'></div>
</div>
<script>
var app=new Vue({
el:'#app',
name:'app',
mixins:{
beforeCreate(){
console.log('beforeCreate-1');
}
}
props:['header-value'],
data:{
message:'hello'
},
beforeCreate(){
console.log('beforeCreate-2');
},
computed:{
abc(){
return this.message+'s'
}
},
components:{
ABF:'abf'
},
directives:{
v-focus:function some(){}
},
extends:{
ext_a:'a'
},
methods:{
methods_a:function(){
alert('methods_a')
}
},
watch:{
'message':function(){
console.log('message changed');
},
methods_a:function(){}
}
})
// debugger;
// setTimeout(()=>app.message='world',0)
</script>
</body>
</html>
这里先放一个简单的例子,随着分析的深入,会一步步将例子变复杂。分析的手段就是通过设置断点一步步看执行过程。
2、new Vue({})执行过程分析
- 首先找到Vue构造函数(启用IDE的查找功能能快速锁定)
function Vue$3 (options) {
if ("development" !== 'production' &&
!(this instanceof Vue$3)) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
<!-- debugger; -->
this._init(options); //程序主入口
}
- 打开上面代码的debugger注释来分析_init函数,该函数是通过initMixin(Vue)定义在Vue.prototype上的
Vue.prototype._init = function (options) {
var vm = this;
// 每个vm实例都会有一个唯一的_uid属性
vm._uid = uid++;
// startTag, endTag 暂时不清楚干嘛的,不影响主流程,记为Unknown-2.1
var startTag, endTag;
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
startTag = "vue-perf-init:" + (vm._uid);
endTag = "vue-perf-end:" + (vm._uid);
mark(startTag);
}
// 设置_isVue标签避免被观测,记为Unknown-2.2
vm._isVue = true;
// 选项合并
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
//主要执行过程-1(合并选项)
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* 设置代理 */
{
initProxy(vm);
}
// 将vm实例同时挂在_self属性上,记为Unknown-2.3
vm._self = vm;
// 主要执行过程-2(实例的初始化过程)
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(((vm._name) + " init"), startTag, endTag);
}
if (vm.$options.el) {
// 主要执行过程-3(依赖收集、组件挂载等过程)
vm.$mount(vm.$options.el);
}
};
可以看到_init函数主要做了如下三件事,上述代码有标注
- 主要执行过程-1(合并选项过程)
- 主要执行过程-2(实例的初始化过程)
- 主要执行过程-3(依赖收集、组件挂载等过程)
- 这三件事后面会单独分析,除了这三件事,还有一个代理操作,来看看initProxy(vm)
initProxy = function initProxy (vm) {
//如果有本地Proxy类可用
if (hasProxy) {
// 判断用哪个代理处理对象
var options = vm.$options;
var handlers = options.render && options.render._withStripped
? getHandler
: hasHandler;
vm._renderProxy = new Proxy(vm, handlers);
} else {
vm._renderProxy = vm;
}
};
这里大家可能对Proxy类比较陌生,可以先看看这里了解基本用法。其实就是给vm实例做一个hasHandler代理,并将代理对象挂在_renderProxy上,使得在访问vm._renderProxy对象上的属性时先调用has方法,返回值为真才去取该属性。这在后面的_render函数会用到
3、小结
- 下一步分析内容
☑ 主要执行过程-1(合并选项过程)
☐ 主要执行过程-2(实例的初始化过程)
☐ 主要执行过程-3(依赖收集、组件挂载等过程) - 遗留问题解决情况
☐ Unknown2.1
☐ Unknown2.2
☐ Unknown2.3