Vue 源码分析 - $set 和 $del
前言
vue版本:
v2.6.9;$set的实现原理;
列举$set方法可以通知订阅者的情况;
$set 的实现原理
path: vue/src/core/observer/index.js
1 | /** |
$set 可以用来设置对象和数组。但target不允许是undefined 或 值类型(原始类型)数据;
新增数组元素
- 首先要是个数组,然后指定的index必须是自然数!
直接就使用了 splice 方法,因为此方法已经是被 vue 处理过的变异方法!详细参考 Vue:数组变异方法(Mutation Methods)的实现原理。
新增对象属性
- 更新数组原有属性:
if (key in target && !(key in Object.prototype)),直接赋值!此时的对象一般有以下几种情况:a. $data本身(tip:$data的原型就是执行Object.prototype);b. $data的子属性或后代属性;c. 非$data 对象。 - 被设置的对象是vue的实例或$data,此类情况会warning,而且不会做任何操作。控制流是:
if (target._isVue || (ob && ob.vmCount))。 - 被设置的对象没有被观测(劫持),情况和1一样,直接赋值。
- 正确设置,越过以上3道障碍!会对对象的新属性进行劫持,这样新属性才会有Dep关联,才能被watcher订阅!
__ob__ 是怎么来的?
在设置对象属性时,执行了 const ob = (target: any).__ob__,__ob__ 从 target 中取出,每个被劫持的数组或对象都会将劫持当前该引用类型数据的 Observer 实例挂载道该引用类型数据的 __ob__ 上!详细参考 Vue:数组变异方法(Mutation Methods)的实现原理。
在 defineReactive(ob.value, key, val),这句中,还见到,defineReactive 是对 ob.value 的 key 进行了劫持,ob.value 是什么?不是应该要操作的对象不应该是 target 吗?看下面:
1 | export function observe (value: any, asRootData: ?boolean): Observer | void { |
在正常的数据坚持中,vue 调用 observe 方法对目标对象的成员进行劫持,从上面可以看到这个传入的对象(代码中的形参 value),最中传入到 Observer 的构造函数赋值给 this.value!
所以其实,target 就是 __ob__.value。
最后是调用了 ob.dep.notify() 通知订阅该对象的订阅者!
换言之,使用 $set 操作对象,只有新增已经被劫持的且是 $data 后代的对象,才能通知订阅者。
$del 的实现原理
path: vue/src/core/observer/index.js
1 | /** |
$del 在一定程度上和 $set 很相似:
if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)),不允许对未定义属性或值类型数据使用$del方法;if (Array.isArray(target) && isValidArrayIndex(key));if (target._isVue || (ob && ob.vmCount)),不应该使用 $set 设置 vue 实例 和 $data 的成员属性;if (!ob),同样无 Observer,则不做通知和劫持操作,当然删除属性本就无必要劫持!- 同样可以用于操作对象,都是使用数组的变异方法
splice; - 同样会通知订阅者当前数据变动。
对于数组而言,$del 使用 target.splice(key, 1) 删除元素,且无第三个参数,即只能用于删除!
对于对象而言,$del 则是使用 delete 操作符删除对象属性,并通知订阅者!
总结
使用
$set新增对象属性与普通方式的区别在于,获取挂载在对象上的 Observer 实例(__ob__)对新增属性进行劫持,并且通知订阅了该对象的 watcher;$set 不但只可以新增对象属性,还可以修改对象原有属性,但不会通知订阅者;
$set 不能这是 $data 对象和 vue 实例;
$set 可以用来新增数组成员,效果等同与直接使用数组的变异方法
splice, 但限定了删除元素个数,因此对于 vue 数组来说,它只能用来替换或新增元素;$del 使用
splice(key, 1)删除数组元素,仅能做删除操作;$del 使用 delete 操作符删除对象属性,并在最后通知相关订阅者,无数据劫持。