Vue 源码阅读(9):vm.$set、vm.$delete 的底层原理

 我的开源库:

我们上文说了,Vue 是通过 Object.defineProperty 和重写数组的原型方法来达到监控数据的目的。但是,在某些情况下,上面两种方案无法做到监控数据的变化,例如:

(1):当我们给对象设置一个新属性的时候,obj.newProperty = xxxxx;

(2):当我们删除对象中的某个属性的时候,delete obj.oldProperty;

上面两种情况,Vue 的响应式系统都监控不到,为了弥补这两个缺陷,Vue 提供了 $set 和 $delete API,当我们想设置新的属性,或者删除某个属性的时候,不要用 js 原生的语法操作,而是使用 $set 和 $delete API 来完成任务。

这两个 API 的思路其实和重写数组的原型方法是一样的,都是对 JS 中的某些原生操作进行重写,当我们调用这些重写的方法对数据进行操作的时候,Vue 自然就能监控到我们对数据做了哪些事情,进而做相对应的处理就可以了,接下来看源码。

1,这两个 API 是如何挂载到 Vue 原型中的

1-1,首先看 src/core/instance/index.js

function Vue (options) {
  // 如果当前的环境不是生产环境,并且当前命名空间中的 this 不是 Vue 的实例的话,
  // 发出警告,Vue 必须通过 new Vue({}) 使用,而不是把 Vue 当做函数使用
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 执行 vm 原型上的 _init 方法,该方法在 initMixin 方法中定义
  this._init(options)
}

// 写入 vm.$set、vm.$delete、vm.$watch
stateMixin(Vue)

export default Vue

挂载这两个 API 到 Vue 原型上是在 stateMixin 方法中。

1-2,src/core/instance/state.js ==> stateMixin()

import {
  set,
  del
} from '../observer/index'

export function stateMixin (Vue: Class<Component>) {
  Vue.prototype.$set = set
  Vue.prototype.$delete = del
}

从上面的代码可知,$set 和 $del API 的实现定义在 ../observer/index 文件中

2,$set 的实现

/**
 * vm.$set 的底层实现
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  // 如果 target 是一个数组,并且 key 也是一个有效的数组索引值的话
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 设置数组的 length 属性,设置的属性值是 "数组原长度" 和 "key" 中的最大值
    target.length = Math.max(target.length, key)
    // 然后通过数组原型上的方法,将 val 添加到数组中
    // 在前面数组响应式源码的阅读中可以知道,通过数组原型的方法添加的元素,其是会被转换成响应式的
    target.splice(key, 1, val)
    return val
  }
  // 这里用于处理 key 已经存在于 target 中的情况
  if (hasOwn(target, key)) {
    // 由于这个 key 已经存在于对象中了,也就是说这个 key 已经被侦测了变化,在这里,只不过是修改下属性而已
    // 所以在这里,直接修改属性,并返回 val 即可
    target[key] = val
    return val
  }

  const ob = (target: any).__ob__

  // 如果 target 没有 __ob__ 属性的话,说明 target 并不是一个响应式的对象
  // 所以在这里也不需要做什么额外的处理,将 val 设到 target 上,并且返回这个 val 即可
  if (!ob) {
    target[key] = val
    return val
  }
  // 如果上面所有的判断条件都不满足的话,说明用户是在响应式数据上新增了一个数据,这种情况需要跟踪这个新增属性的变化
  // 在这里使用 defineReactive 将 val 变成 getter/setter 的形式
  defineReactive(ob.value, key, val)
  // 因为新增了一个属性,所以 ob.value 变化了,所以在这里需要出发依赖的更新
  ob.dep.notify()
  return val
}

源码的详细解释都在注释中,这里就不在过多赘述了。

3,$delete 的实现

/**
 * Delete a property and trigger change if necessary.
 */
// Vue 对数据的监控是通过 Object.defineProperty() 实现的,所以当用户通过 delete 关键字删除某个字段时,Vue 是检测不到的,
// 为了解决这个问题,Vue 提供了 vm.$delete 来解决这个问题
export function del (target: Array<any> | Object, key: any) {
  // 如果 target 是一个数组,并且 key 是一个下标值的话
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 执行数组原型上的 splice 方法,该方法会执行删除的操作,并且会出发依赖的更新
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  // 如果 target 上面没有 key 属性的话,直接 return 即可,什么都不用干
  if (!hasOwn(target, key)) {
    return
  }
  // 使用 js 中原生的 delete 关键字删除指定的 key
  delete target[key]
  // 在这里判断 target 是不是响应式的,如果不是的话,就不用出发依赖的更新操作了。在这里,直接 return
  if (!ob) {
    return
  }
  // 出发依赖的更新操作
  ob.dep.notify()
}

$delete 源码的实现思路和 $set,大家自行看上面的注释即可,比较简单。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值