set函数#
下面看下set函数,我们在扩展 Vue 的构造函数的原型对象时创建Vue.prototype.$set方法,然后在扩展 Vue 的构造函数的静态方法时创建Vue.set方法,这两个方法所赋值的函数都是set函数。
在 Vue 的官方文档中,提供了这两个 API 供用户使用,用来向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性。另外,注意设置的对象不能是 Vue 实例或者 Vue 实例的根数据对象。
下面查看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)}`
);
}
// ...
}首先在非生产环境中,判断target的类型是不是未定义的值或者是一个原始类型的值,如果满足其中之一的话会报错,并提示不能设置null、undefined或者原始类型中的响应式属性。
继续看下面的代码。
// ! target 是数组且 key 有效时
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key); // ! 修改数组长度 key, 否则大于原数组长度时 splice 无效
target.splice(key, 1, val); // ! 使用重写的 splice 增加或者替换元素,并触发响应
return val;
}如果定义的响应式对象是数组时且它里面的key值是有效值时,先获取数组长度和key的中最大值,把它设置为target的长度。为什么要这样做呢?因为如果设置的key大于原来target的长度,使用splice是插入元素时,插入后的元素在数组中的位置会不正确,即索引不正确。
示例代码:
const arr = [1, 2, 3]; // length = 3
const key = 5;
arr.splice(key, 1, 4);
console.log(arr); // [ 1, 2, 3, 4 ]上面代码中 key 是5,它比数组arr的长度3要大,把数字4传入到key的位置后,它的位置是错误的。
如果修改了target的长度为key和原来长度的最大值后,插入后的位置是不一样的。
示例代码:
const arr = [1, 2, 3];
const key = 5;
arr.length = Math.max(arr.length, key);
arr.splice(key, 1, 4);
console.log(arr); // [ 1, 2, 3, <2 empty items>, 4 ]继续看下面的代码。
// ! key 已经存在对象 target 且不在它的原型中时
if (key in target && !(key in Object.prototype)) {
target[key] = val; // ! 直接修改原来的值,会自动触发 setter
return val;
}设置的key已经存在于响应式对象中但不在它的原型对象上时,直接赋值即可,会自动触发setter,最后返回设置的新值。
继续看下面的代码。
// ! 获取原对象的 __ob__ 属性
const ob = (target: any).__ob__;
// ! 不允许设置 Vue 实例 和 根实例数据对象(根 data 不是响应式的)的属性
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;
}声明常量ob存储target的__ob__属性,判断target是否有_isVue属性或者ob.vmCount的值是否大于零。如果同时满足其中的一个条件且在非生产环境中会报错,提示不能设置 Vue 实例对象的属性或者根组件的data选项的值,最后返回设置的值val。
继续看剩下的代码。
// ! 原对象不是响应式对象,新属性直接赋值并返回
if (!ob) {
target[key] = val;
return val;
}
defineReactive(ob.value, key, val); // ! 新属性变成响应式
ob.dep.notify(); // ! 手动触发依赖
return val;判断ob是否存在,即判断target是否是一个响应式对象,如果不是的话,直接给它的key赋值为val,并返回val。
如果target是一个响应式对象,把它新增的值也变成响应式数据,然后手动触发依赖,并返回设置的值val。
小结,虽然 Vue 的官方文档中说明只能是为响应式数据设置值,但是它的代码中也可以为非响应式的对象设置值,不过我们在实际开发中没有必要这样,因为可以直接在这个对象上操作,而不需要额外使用这个 API。
del#
下面看下del函数,我们在扩展 Vue 的构造函数的原型对象时创建Vue.prototype.$delete方法,然后在扩展 Vue 的构造函数的静态方法时创建Vue.delete方法,这两个方法所赋值的函数都是del函数。
在 Vue 的文档中,提供了这两个 API 供用户使用,用来删除对象的属性。如果对象是响应式对象,确保删除值能触发依赖,更新视图。如果不是响应式对象,直接删除它即可。
这个方法是为了避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。
下面查看del函数的代码,它的结构和set函数很相似,下面结合set来对比说明。
export function del(target: Array<any> | Object, key: any) {
if (
process.env.NODE_ENV !== "production" &&
(isUndef(target) || isPrimitive(target))
) {
warn(
`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`
);
}
// ...
}首先和set函数一样,判断target的类型是不是未定义的值或者是一个原始类型的值,如果满足其中之一的话会报错,并提示不能删除null、undefined或者原始类型中的响应式属性。
继续看下面的代码。
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1); // ! splice 删除元素,并触发响应
return;
}判断target的类型是不是数组且它的索引是否有效的,如果都满足条件的话,直接调用splice删除索引key位置的值即可。
继续看下面的代码。
const ob = (target: any).__ob__;
// ! 不能删除 Vue 实例 和 根实例数据对象(根 data 不是响应式的)的属性
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== "production" &&
warn(
"Avoid deleting properties on a Vue instance or its root $data " +
"- just set it to null."
);
return;
}和set函数中逻辑的一样,声明常量ob存储target的__ob__属性,判断target是否有_isVue属性或者ob.vmCount的值是否大于零。如果同时满足其中的一个条件且在开发中会报错,提示不能删除 Vue 实例对象的属性或者根组件的data选项的值,最后直接返回。
// ! 没有要删除的属性
if (!hasOwn(target, key)) {
return;
}
delete target[key]; // ! 删除目标的字段如果target对象上没有要删除的key值,直接返回。如果有的话,调用delete运算符删除target中的key字段或者索引。
继续看剩下最后的代码。
// ! 目标不是响应式,不处理
if (!ob) {
return;
}
ob.dep.notify(); // ! 手动触发依赖如果ob不存在,即target不是响应式对象,那么直接返回,不做处理。如果是响应式对象,手动触发依赖,更新视图。
注意del不需要返回值,也不需要返回一个布尔值来表示删除属性是否成功,这也从侧面说明这个函数关联的Vue.delete和vm.$delete的 API 使用的场景非常少。