接着第一节开始继续吧(GoGoGo)
上一节把mergeOptions函数弄完了,最后返回一个options赋给了vm.$options。
这一节继续跑代码:
function initMixin(Vue) { Vue.prototype._init = function(options) { //..上一节 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); // 从这里开始跑 /* istanbul ignore else */ { initProxy(vm); } // 疯狂保存自身引用 vm._self = vm; // 初始化跑起来 initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); initState(vm); initProvide(vm); 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); } // 判断是否有el属性 进行挂载 if (vm.$options.el) { vm.$mount(vm.$options.el); } }; }
剩余的代码主要有3件事:initProxy()、各种初始化函数、挂载vue。
在讲initProxy()之前,有必要先讲讲这个奇怪的注释:/* istanbul ignore else */。
这个注释代码段出现的频率和"development" !== 'production'不相上下,一开始我是无视的,但是慢慢的觉得很奇怪,这个注释可能并不简单。
首先百度了一下istanbul,是个国家,天气不错,各种旅游攻略。发现不对劲,果断在前面加个js关键字,发现了这其实是一个代码覆盖率工具,即是否所有代码都测试到了。
这个工具我就不介绍了,总之这行注释的意思就是,下一个代码段中的else语句不计入代码覆盖率计算。
initProxy()
开始跑initProxy()函数,只接受一个参数,即当前vue实例。
// Line-1620 initProxy = function initProxy(vm) { // 是否支持es6的代理 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,这是ES6新出的语法代理,详情可见阮一峰的书:http://es6.ruanyifeng.com/#docs/proxy。
若支持,再进行传进来的vm进行参数判断,选择代理。
当前vm只有三个参数(其实不止,先不管了):$options、_isVue、_uid,所以很明显,调用的是hasHandelr这个代理。
// Line-1562 var initProxy; { //..一些字符匹配集 if (hasProxy) { var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta'); config.keyCodes = new Proxy(config.keyCodes, { set: function set(target, key, value) { // ...定义事件别名 } }); } var hasHandler = { has: function has(target, key) { var has = key in target; // 判断key是否跟内置全局变量冲突 var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; // 不存在或者非法 if (!has && !isAllowed) { warnNonPresent(target, key); } return has || !isAllowed } }; var getHandler = { //..不管 }; initProxy = function initProxy(vm) { // 是否支持es6的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这个说实话我看不太懂,属于那种看看就好的语法,可能境界没达到吧,但不怎么影响我理解这段代码,有错欢迎指出来。
initProxy(vm)这个函数主要就做了一个方法劫持,相当于ng里面的拦截器,在vm._renderProxy中,每一个键都会进行合法性检测,如果与内置全局对象冲突,就会调用warnNonPresent()报警。
如果不支持,就算了。
初始化家族
这一部分包含8个初始化,个个都是怪物,然而由于案例比较简单,大部门都会直接跳过。
1、initLifecycle
// Line-2259 function initLifecycle(vm) { var options = vm.$options; // 没有parent 跳 var parent = options.parent; if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent; } parent.$children.push(vm); } vm.$parent = parent; vm.$root = parent ? parent.$root : vm; vm.$children = []; vm.$refs = {}; vm._watcher = null; vm._inactive = null; vm._directInactive = false; vm._isMounted = false; vm._isDestroyed = false; vm._isBeingDestroyed = false; }
这个函数主要对vm.$options.parent属性进行处理,很遗憾,我没有。
目前,vm的属性是5个,多了_self、_renderProxy,跑完这个函数,又多了一串,暂时不管。
2、initEvents
// Line-2074 function initEvents(vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // 又搞parent 没有 var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } }
多了2个事件相关属性。
3、initRender
// Line-3824 function initRender(vm) { vm._vnode = null; // the root of the child tree vm._staticTrees = null; var parentVnode = vm.$vnode = vm.$options._parentVnode; // the placeholder node in parent tree var renderContext = parentVnode && parentVnode.context; // 由于传了2个undefined 返回一个空对象 vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext); vm.$scopedSlots = emptyObject; // 2个属性方法 之后再看 vm._c = function(a, b, c, d) { return createElement(vm, a, b, c, d, false); }; vm.$createElement = function(a, b, c, d) { return createElement(vm, a, b, c, d, true); }; }
4、callHook
// Line-2532 function callHook(vm, hook) { // hook => beforeCreate // 没有这个钩子函数 var handlers = vm.$options[hook]; if (handlers) { for (var i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm); } catch (e) { handleError(e, vm, (hook + " hook")); } } } // 这个也没有 if (vm._hasHookEvent) { vm.$emit('hook:' + hook); } }
钩子函数都没有,这个初始化跳过。
5、initInjections
// Line-3218 function initInjections(vm) { // 由于没有inject属性 跳过 var result = resolveInject(vm.$options.inject, vm); if (result) { Object.keys(result).forEach(function(key) { /* istanbul ignore else */ { defineReactive$$1(vm, key, result[key], function() { warn( "Avoid mutating an injected value directly since the changes will be " + "overwritten whenever the provided component re-renders. " + "injection being mutated: \"" + key + "\"", vm ); }); } }); } }
6、initState
// Line-2947 function initState(vm) { vm._watchers = []; var opts = vm.$options; // 针对参数做初始化 if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } // 目前只有data参数 if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */ ); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch) { initWatch(vm, opts.watch); } }
这个地方开始对传进来的参数做初始化挂载,由于只传了el和data,而el参数在最后面才处理,所以目前只会执行initData函数。
来看看initData函数运作,这可能是最关键的部分了。
// Line-3011 function initData(vm) { // 取出data参数 这里的data不是传进来的对data象 而是一个函数 var data = vm.$options.data; // 判断类型 格式化data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; // data属性必须返回对象 if (!isPlainObject(data)) { data = {}; "development" !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ); } // 代理data属性 var keys = Object.keys(data); var props = vm.$options.props; var i = keys.length; while (i--) { if (props && hasOwn(props, keys[i])) { "development" !== 'production' && warn( "The data property \"" + (keys[i]) + "\" is already declared as a prop. " + "Use prop default value instead.", vm ); } // 禁止以$,_开头 else if (!isReserved(keys[i])) { proxy(vm, "_data", keys[i]); } } // 双绑入口 observe(data, true /* asRootData */ ); }
整理一下,data属性初始化主要有格式化、代理、双绑三个部分。
首先来看看格式化,在第一节中的mergeOptions函数最后面,options中的data属性变成了一个叫mergedInstanceDataFn的函数,因此三元表示式返回的是getData(data, vm)。
getData函数比较简单, 所以把两个弄一起得了。
// Line-3043 function getData(data, vm) { try { // 尝试执行data函数 return data.call(vm) } catch (e) { handleError(e, vm, "data()"); return {} } } // Line-1132 // 传进来的parentVal、childVal分别为undefined、{message:'Hello Vue!} function mergedInstanceDataFn() { // 这个是对象 var instanceData = typeof childVal === 'function' ? childVal.call(vm) : childVal; // 这个是undefined var defaultData = typeof parentVal === 'function' ? parentVal.call(vm) : undefined; // 合并数据 由于第二个参数为undefined 直接返回原data对象 if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } }
在getData函数中,会尝试以vm为上下文执行data函数,根据参数执行mergedInstanceDataFn函数后,最后返回的仍然是传进来的data对象。
再来看代理部分,首先会遍历data对象的键,判断是否不以$,_开头,然后进行代理操作。尝试了一下,如果使用$message,_message会报错,后面再看为什么。
这部分的关键在于proxy这个函数,ES6自带的是Proxy,不是一个东西,是一个正常的函数。
// Line-2930 var sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop }; // Line-2937 // target => vm | sourceKey => _data | key => 'message' function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); }
简单来讲,这个方法就是给vm添加了一个_data属性,值是data。
因为只传了一个message,所以 _data属性是这样的:
第三部分属于比较核心的部分,即数据监测,双绑的一部分,下次写吧!
用一张图片梳理一下第二节吧。