vue2.x源码解析(第三部分)
这一章节是接着上一章节的,主要是讲解合并结果的.
默认的工具函数defaultStrat
const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal }
可以看到其很简单就是当childVal
参数!== undefined
时返回childVal
否则返回parentVal
,这个是没有对应的属性名处理函数默认的处理函数。
处理各项的工具函数集合strats
const strats = config.optionMergeStrategies
可以看到strats
变量获取的是配置对象中的optionMergeStrategies
属性,这个属性值就是一个空对象,下面来看看都为其添加了那些属性。
el属性和propsData属性处理函数
if (process.env.NODE_ENV !== 'production') { /*提示你 el 选项或者 propsData 选项只能在使用 new 操作符创建实例的时候可用*/ strats.el = strats.propsData = function (parent, child, vm, key) { if (!vm) { warn( `option "${key}" can only be used during instance ` + 'creation with the `new` keyword.' ) } return defaultStrat(parent, child) } }
上面这段代码必须在非生产环境下,才会执行.该函数当没有Vue
实例传入是会提示警告信息' el 选项或者 propsData 选项只能在使用 new 操作符创建实例的时候可用'.最后执行的还是 defaultStrat
函数
data属性处理函数
strats.data = function ( parentVal: any, childVal: any, vm?: Component ){ //判断是否有vue实例 if (!vm) { //没有实例并且childVal不是函数提示警告(这就是vue单文件中data要是函数的原因) if (childVal && typeof childVal !== 'function') { process.env.NODE_ENV !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ) return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }
1、当没有传入实例时。
`childVal`参数存在时并且`childVal`参数不属于`function`类型时提示错误信息'“data”选项应该是一个在组件定义中返回每个实例值的函数。',
然后返回`parentVal`参数
当`childVal`不参数或者`childVal`参数属于`function`类型时,执行mergeDataOrFn函数,2个参数就是处理data选项函数透传进来的。
总结:为什么一定要是函数,是为了保证每个组件实例间都有唯一一个数据副本,避免组件间数据互相影响。
2、当有实例传入时
也是执行mergeDataOrFn函数,只不过是把实例也透传进去了
这个mergeDataOrFn
函数很多地方都用到了,等看完所有的strats
中的处理函数在分析这个函数。
watch属性处理函数
export const nativeWatch = ({}).watch strats.watch = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ){ // work around Firefox's Object.prototype.watch... //Firefox浏览器的对象原型自带watch属性 if (parentVal === nativeWatch) parentVal = undefined if (childVal === nativeWatch) childVal = undefined /* istanbul ignore if */ //不存在childVal,直接返回原型为parentVal || null的空对象 if (!childVal) return Object.create(parentVal || null) //非生产环境对childVal不为对象时警告提示 if (process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } //不存在parentVal直接返回childVal if (!parentVal) return childVal const ret = {} //合并parentVal到一个新的空对象中 extend(ret, parentVal) //循环childVal for (const key in childVal) { //缓存与新对象中key名相同的属性的值 let parent = ret[key] //缓存childVal[key]的值 const child = childVal[key] //parent && parent不为数组则将parent转化为数组 if (parent && !Array.isArray(parent)) { parent = [parent] } //存在parent,则将childVal[key]值拼接进parent数组中 //不存在parent,则判断childVal[key]的值是否为数组 //是: 返回缓存childVal[key]值的child //不是: 将childVal[key]的值转换成数组返回 ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } //返回这个新对象 return ret } function assertObjectType (name: string, value: any, vm: ?Component) { //不是对象报警告 if (!isPlainObject(value)) { warn( `Invalid value for option "${name}": expected an Object, ` + `but got ${toRawType(value)}.`, vm ) } }
上面这代码我们依次执行下去: 因为Firefox
浏览器的对象原型自带watch
属性,所以有可能parentVal
参数或childVal
参数是空对象,那么就得把这个值变 为undefined
,以便不影响后面的代码。接着往后看,当没传childVal
参数时,如果存在parentVal
参数直接返回以parentVal
对象为原型的对象否则就是原型 为空的空对象;传了childVal
参数时: 在非生产环境下判断watch
选项值是否为纯对象,不为纯对象时提示警告信息 'watch选项给的不是纯对象'。不存在parentVal
参数时,直接返回childVal
参数。初始化一个空对象ret
变量,将parentVal
参数中的属性赋值到 ret
变量中,循环childVal
参数的属性名,将ret
变量中的该属性值和childVal
参数中的该属性值合并到数组中。
总结: 将watch选项中的相同的属性名的值合并在一个数组中,所以如果你监听了某个数据的变化,刚好其值数组中有多个函数,那么全都会执行一遍。那也就是说watch选项 的属性值可以写成数组形式、对象形式、函数
parentVal = { count () { console.log(5); } } childVal = { count () { console.log(6); }, num () { console.log(7) } } // 处理过后 ret = { count: [ () => console.log(5), () => console.log(6) ], num: [ () => console.log(7) ] }
题外话
一句一句解析下来太烦了,而且没有说明意义,就直接将这个函数干了什么吧,并举个例子吧!
props、methods、inject、computed属性处理函数
这四个属性引用的都是用一个处理函数.
strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ){ //参数childVal存在并且不是生产环境,检测childVal是否为对象 if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } //不存在parentVal参数,直接返回childVal if (!parentVal) return childVal const ret = Object.create(null) //将parentVal对象合并到一个原型为空的空对象中 extend(ret, parentVal) //存在childVal参数,那就将childVal对象合并进ret对象 if (childVal) extend(ret, childVal) return ret }
该函数的作用: 同样在非生产环境下,当childVal
存在 && 不为对象时提示警告信息 'props或methods或inject或computed选项给的不是纯对象'。没有parentVal
返回childVal
。将parentVal
中的属性值复制到 原型为空的ret
对象变量中,当存在childVal
时,将childVal
中的各个属性赋值到ret
对象变量,如果该属性名已存在则直接替换。
parentVal = { count: { type: Number, default: 3 } } childVal = { count: {}, num: { type: Number, default: 4 } } /// 处理后 ret = { count: {}, num: { type: Number, default: 4 } }
生命周期钩子选项处理函数
export const LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed', 'activated', 'deactivated', 'errorCaptured' ] LIFECYCLE_HOOKS.forEach(hook => { strats[hook] = mergeHook }) function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ){ return childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal }
以上是生命周期钩子函数选项处理函数,可以看到最后都执行了mergeHook
函数,该函数的作用起始就是将所有的钩子函数合并在一个数组中
parentVal = [ () => console.log('我是mounted') ] childVal = () => console.log('我是mounted1') // 处理后 ret = [ () => console.log('我是mounted'), () => console.log('我是mounted1') ]
component、directive、filter
export const ASSET_TYPES = [ 'component', 'directive', 'filter' ] ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets }) function mergeAssets ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ){ //__proto__是每个对象都有的一个属性,而prototype是函数才会有的属性 //parentVal存在则创建原型为parentVal的对象 const res = Object.create(parentVal || null) if (childVal) { //在非生产环境监测childVal是否为对象,并进行相应的警告提示 process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) } else { return res } }
作用同样是对相同属性的替换和props
、methods
、inject
、computed
属性处理函数处理的结果差不多,这里就不举例了
provide属性处理函数
这个处理函数其实就是mergeDataOrFn
,刚好在讲data
时说要最后讲的。
export function mergeDataOrFn ( parentVal: any, childVal: any, vm?: Component ) { //不存在vue实例 if (!vm) { // in a Vue.extend merge, both should be functions //不存在需要childVal直接返回parentVal if (!childVal) { return parentVal } //不存在需要parentVal直接返回childVal if (!parentVal) { return childVal } // when parentVal & childVal are both present, // we need to return a function that returns the // merged result of both functions... no need to // check if parentVal is a function here because // it has to be a function to pass previous merges. //当三者都没有时 || childVal和parentVal都存在时,返回下面这个函数 return function mergedDataFn () { //如果参数为函数则传入的实际参数是函数返回的值,否则直接传入这个参数 //下面这个函数返回深度合并过的childVal或childVal函数的值 return mergeData( typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } //存在vue实例 else { return function mergedInstanceDataFn () { // instance merge //缓存childVal或childVal为函数时返回的值 const instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal //缓存parentVal或parentVal为函数时返回的值 const defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal //instanceData是否存在,存在则合并这两个对象,不存在返回默认的defaultData if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } } }
可以看到如果其分两种情况:
1、存在实例: 直接返回mergedInstanceDataFn
这个函数
这里先假设执行了这个函数: 获取childVal(函数返回的值或对象)为instanceData变量、获取parentVal(函数返回的值或对象)为defaultData变量,
instanceData变量有值执行mergeData函数,参数为instanceData变量和defaultData变量;没有值则直接返回instanceData变量
2、不存在实例: 没有childVal
参数返回parentVal
;没有parentVal
返回childVal
参数;如果两者都存在则返回mergedDataFn
函数
这里先假设执行了这个函数: mergeData函数的两个参数就是(函数childVal或childVal对象)和(函数parentVal或parentVal对象)
以上两个最后都有可能执行mergeData
函数,那么就来看下这个函数都做了什么?
function mergeData (to: Object, from: ?Object): Object { if (!from) return to let key, toVal, fromVal const keys = Object.keys(from) for (let i = 0; i < keys.length; i++) { key = keys[i] //缓存form的key名 toVal = to[key] //缓存to[key]的值 fromVal = from[key] //缓存from[key]的值 //key不存在to对象上,将key和key值设置到to对象上 if (!hasOwn(to, key)) { set(to, key, fromVal) } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { /* 判断to[key]和from[key]的值是否都为对象 1、是: 递归这个函数 2、否: 继续下一个循环,直到退出循环 */ mergeData(toVal, fromVal) } } return to }
form
代表parentVal
,to
代表childVal
首先这个函数的作用是传入参数from
深度合并到传入参数to
(属性值为对象值采用递归方式),如果to
参数的原型链上存在from
参数中的属性, 那么to
参数的原型链中的这个属性值会被替换成参数from
的这个属性值。这个函数中set
函数的作用就是这个,举个例子:
function data() { function cs() { this.num = 'num1'; } cs.prototype = { count: 'count2' } return new cs(); } to = data(); from = { count: 'count5', sum: 'sum6' } // 处理过后 to = { count: 'count5', sum: 'sum6', num: 'num1' }
总结
最后options
对象中处理data
和provide
的值有可能是函数以外,另外的选项的值都已经明确。
下一章节我们来说说 拦截器
详细信息可访问https://github.com/maomao93/vue-2.x/tree/master/personal