Vue源码解析之数据响应系统的使用(13)

所以需要递归观测数组元素。

Vue.set($set) 和 Vue.delete($delete) 的实现

我们知道,为对象或数组直接添加或删除元素Vue是拦截不到的。我们需要使用Vue.set、Vue.delete去解决,Vue还在实例对象上定义了$set $delete方便我们使用。其实不管是实例方法还是全局方法它们的指向都是一样的。我们来看以下它们的定义。

$set $delete定义在core/instance/state.js文件中的stateMixin方法中

export function stateMixin (Vue: Class<Component>) {
 ...

 Vue.prototype.$set = set
 Vue.prototype.$delete = del

 ...
}

Vue.set和Vue.delete定义在core/global-api/index.js文件中的initGlobalAPI函数中:

export function initGlobalAPI (Vue: GlobalAPI) {
 ...

 Vue.set = set
 Vue.delete = del

 ...
}

可以看到它们的函数值是相同的。 set和del定义在core/observer/index.js文件中。我们先来看一下set的定义

set

从上到下来看set的函数体,显示这个if判断:

if (process.env.NODE_ENV !== 'production' &&
 (isUndef(target) || isPrimitive(target))
) {
 warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}

isUndef

export function isUndef (v: any): boolean %checks {
 return v === undefined || v === null
}

判断变量是否是未定义,或者值为null。

isPrimitive

export function isPrimitive (value: any): boolean %checks {
 return (
 typeof value === 'string' ||
 typeof value === 'number' ||
 // $flow-disable-line
 typeof value === 'symbol' ||
 typeof value === 'boolean'
 )
}

判断变量是否是原始类型。

所以这个if语句的作用就是,如果target是undefined或者null或者它的类型是原始类型,在非生产环境下打印警告信息。
再看下一个if语句:

if (Array.isArray(target) && isValidArrayIndex(key)) {
 target.length = Math.max(target.length, key)
 target.splice(key, 1, val)
 return val
}

isValidArrayIndex

export function isValidArrayIndex (val: any): boolean {
 const n = parseFloat(String(val))
 return n >= 0 && Math.floor(n) === n && isFinite(val)
}

判断变量是否是有效的数组索引。

如果target是一个数组,并且key是一个有效的数组索引,就执行if语句块内的代码

我们知道splice变异方法是可以触发响应的,target.splice(key, 1, val) 就利用了替换元素的能力,将指定位置元素的值替换为新值。所以数组就是利用splice添加元素的。另外,当要设置的元素的索引大于数组长度时 splice 无效,所以target的length取两者中的最大值。