前言
本文是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.1var 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.2vm._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.3vm._self = vm;// 主要执行过程-2(实例的初始化过程)initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm, 'beforeCreate');initInjections(vm); // resolve injections before data/propsinitState(vm);initProvide(vm); // resolve provide after data/propscallHook(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
