set
方法主要是为响应式对象中动态的添加响应式属性,该方法在全局方法Vue.set
和实例方法vm.$set
内被调用,可分别在src/core/global-api/index.js
和src/core/instance/state.js
文件中可查看相关定义
该方法会返回设置的响应式属性的值
// src/core/global-api/index.js
Vue.set = set
// src/core/instance/state.js
export function stateMixin (Vue: Class<Component>) {
// ......
Vue.prototype.$set = set
// ......
}
2
3
4
5
6
7
8
9
10
11
在src/core/observer/index.js
中可查看对set
方法的定义
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
该方法有三个参数,target
为需要动态添加响应式属性的响应式对象,key
和val
则分别为属性和值,方法内部首先会判断target
,如果为undefined
或为简单类型的值,则会给出warn
提示,接着会判断target
是否为数组并且key
是一个有效的索引
# 数组
在src/shared/util.js
中可查看对isValidArrayIndex
方法的定义
export function isValidArrayIndex (val: any): boolean {
const n = parseFloat(String(val))
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
2
3
4
isValidArrayIndex
逻辑很简单,主要判断val
是一个有限数值,大于等于0,且向下取证和原数值相等(确保val
不是小数)
如果target
为数组且key
是有效索引,先利用target.length
调整数组的长度,再利用splice
方法添加新增项
这里可能大家注意到了,为什么需要使用target.length
,而不是直接使用target.splice
动态添加?为什么target.splice
的第二个参数为1
?
索引值key
有两种情况
- 超出
target
的长度范围 - 在
target
的长度索引范围内
如果索引值key
超出了target
长度范围,直接使用splice
添加会直接在数组的末尾进行添加,这样target
的长度会不准确,比如:
var a = [1, 2]
a.splice(3, 1, 3) // 直接在数组的末尾添加
a // [1, 2, 3]
2
3
按正确的逻辑,数组的长度为数组的最后一项的索引值加1,所以target.length
应该为4
,结合target.length = Math.max(target.length, key)
,可以保证target
的长度准确性
如果索引值key
在target
的长度范围内,target.splice
的第二个参数1
,会先删除再添加
在响应式对象 (opens new window)小节说过,会对数组的原型进行重写,重写原型的逻辑在一个单独的文件中,可在src/core/observer/array.js
中查看
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
重写的方法中,会对响应式数组新增的数据循环调用Observe
方法,然后再根据响应式数组对应的dep.notify
触发更新
# 对象
继续回到set
方法中,后续则是对对象的分析处理
export function set (target: Array<any> | Object, key: any, val: any): any {
// ......
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
如果key
是响应式对象target
的自身属性,则会直接修改值,如果target
为Vue
的实例化对象,或者为根data
对象,则会报warn
提示,如果target
不是响应式对象则会直接做赋值处理,接着会直接调用defineReactive
方法为响应式对象target
动态添加响应式属性,并且调用响应式对象target
对应的dep.notify
方法派发更新