一、对象添加属性
对于使用 Object.defineProperty
实现响应式的对象,当我们去给这个对象添加一个新的属性的时候,是不能触发它的 setter
的,比如:
var vm = new Vue({
data: {
a: 1
}
})
// vm.b 是非响应的
vm.b = 2
但是添加新属性的场景是我们在平时开发中会经常遇到,Vue 为了解决以上问题,提供了一个全局Api Vue.set
方法,以下是源码部分。
二、 源码部分
在src/core/global-api/index.js
中,initGlobalAPI 方法会有以下赋值:
Vue.set = set
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)}`)
}
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
}
set
方法接收 3个参数,target
可能是数组或者是普通对象,key
代表的是数组的下标或者是对象的键值,val
代表添加的值。首先判断如果 target
是数组且 key
是一个合法的下标,则之前通过splice
去添加进数组然后返回,这里的 splice 其实已经不仅仅是原生数组的 splice 了。接着又判断 key
已经存在于 target
中,则直接赋值返回,因为这样的变化是可以观测到了。接着再获取到target.__ob__
并赋值给 ob
,它是在 Observer
的构造函数执行的时候初始化的,表示 Observer
的一个实例,如果它不存在,则说明 target
不是一个响应式的对象,则直接赋值并返回。最后通过 defineReactive(ob.value, key, val)
把新添加的属性变成响应式对象,然后再通过 ob.dep.notify()
手动的触发依赖通知,还记得我们在给对象添加getter
的时候有这么一段逻辑:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 当 val 为 Array或Object, childOb有值
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 依赖收集
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() // 依赖更新
}
})
}
在getter
过程中判断了 childOb
, 并调用了 childOb.dep.depend()
收集了依赖,这就是为什么执行 Vue.set
的时候通过ob.dep.notify()
能够通知到 watcher
,从而让添加新的属性到对象也可以检测到变化,这里如果value
是个数组,那么就通过 dependArray
把数组每个元素也去做依赖的收集。