[vue面试专问]Vue.set 和 Vue.delete 的实现

Vue.set($set) 

Vue.delete($delete)


我们发现 $set$delete 定义在 stateMixin 函数中,如下代码:

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object  
  // when using Object.defineProperty, so we have to procedurally build up  
  // the object here.  
  const dataDef = {}  
  dataDef.get = function () { return this._data }  
  const propsDef = {}  
  propsDef.get = function () { return this._props }  
if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',        this      )    
}    
  propsDef.set = function () {
        warn(`$props is readonly.`, this)    
  }  
}
  Object.defineProperty(Vue.prototype, '$data', dataDef)  
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  Vue.prototype.$set = set  
  Vue.prototype.$delete = del   
  Vue.prototype.$watch = function (
       expOrFn: string | Function,    
       cb: any,    
       options?: Object  ): Function {
   const vm: Component = this    
   if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)    
   }    
   options = options || {}   
   options.user = true    
   const watcher = new Watcher(vm, expOrFn, cb, options)
   if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }}复制代码

是不是太长,太复杂?看不懂?

不急我们慢慢往下看,逐步介绍:

上面定义常量和环境判断就不说了直接看核心:

Vue.prototype.$set = set  
Vue.prototype.$delete = del复制代码

可以看到 $set$delete 的值分别是是 setdel

其实我们发现initGlobalAPI 函数中定义了:

 Vue.set = set 
 Vue.delete = del复制代码

不难看出其实 Vue.set == $set ,Vue.delete == $delete

 接下来看看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)}`)
  }
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 函数接收三个参数:第一个参数 target 是将要被添加属性的对象,第二个参数 key 以及第三个参数 val分别是要添加属性的键名和值。

if判断中isUndef函数用来判断一个值是否是 undefinednull

isPrimitive 函数用来判断一个值是否是原始类型值

ECMAScript 有 5 种原始类型(primitive type),即 Undefined、Null、Boolean、Number 和 String

  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)   
    target.splice(key, 1, val)
    return val  
}复制代码

这个判断主要是对target与key做了校验判断是否是个数组和key是否为有效的数组索引

target.length = Math.max(target.length, key)  
target.splice(key, 1, val)复制代码

这就涉及到上篇博客讲的(数组变异处理)

target.length = Math.max(target.length, key)复制代码

将数组的长度修改为 target.lengthkey 中的较大者,否则如果当要设置的元素的索引大于数组长度时 splice 无效。

target.splice(key, 1, val)复制代码

数组的 splice 变异方法能够完成数组元素的删除、添加、替换等操作。而 target.splice(key, 1, val) 就利用了替换元素的能力,将指定位置元素的值替换为新值,同时由于 splice 方法本身是能够触发响应的

然后接下来一个if:

if (key in target && !(key in Object.prototype)) {
  target[key] = val
  return val
}复制代码

如果 target 不是一个数组,那么必然就是纯对象了,当给一个纯对象设置属性的时候,假设该属性已经在对象上有定义了,那么只需要直接设置该属性的值即可,这将自动触发响应,因为已存在的属性是响应式的

key in target复制代码

判断keytarget 对象上,或在 target 的原型链上

!(key in Object.prototype)复制代码

同时必须不能在 Object.prototype

const ob = (target: any).__ob__
复制代码

定义了 ob 常量,它是数据对象 __ob__ 属性的引用

  defineReactive(ob.value, key, val)  ob.dep.notify()复制代码

defineReactive 函数设置属性值,这是为了保证新添加的属性是响应式的。

 __ob__.dep.notify() 从而触发响应。这就是添加全新属性触发响应的原理

  if (!ob) {    target[key] = val    return val  }复制代码

target 也许原本就是非响应的,这个时候 target.__ob__是不存在的,所以当发现 target.__ob__ 不存在时,就简单的赋值即可

 if (target._isVue || (ob && ob.vmCount)) {
复制代码

Vue 实例对象拥有 _isVue 属性,所以当第一个条件成立时,那么说明你正在使用 Vue.set/$set 函数为 Vue 实例对象添加属性,为了避免属性覆盖的情况出现,Vue.set/$set 函数不允许这么做,在非生产环境下会打印警告信息

(ob && ob.vmCount)复制代码

这个就涉及比较深:主要是观测一个数据对象是否为根数据对象,所以所谓的根数据对象就是 data 对象

当使用 Vue.set/$set 函数为根数据对象添加属性时,是不被允许的

因为这样做是永远触发不了依赖的。原因就是根数据对象的 Observer 实例收集不到依赖(观察者)

set讲完了 讲讲delete


 Vue.delete/$delete

还是一样先看源码:

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)}`)  
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)    return  
  }  
   const ob = (target: any).__ob__  
   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  
    }  
   if (!hasOwn(target, key)) {
    return  
   }
   delete target[key]  
  if (!ob) {
    return  
  }
  ob.dep.notify()
}
复制代码

del 函数接收两个参数,分别是将要被删除属性的目标对象 target 以及要删除属性的键名 key

第一个if判断和set一样 就不讲了

  if (Array.isArray(target) && isValidArrayIndex(key)) {
       target.splice(key, 1)    
    return  
}复制代码

第二个判断其实和set也差不多。。。删除数组索引(同样是变异数组方法,触发响应)

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  
}复制代码

其实也不用说了 判断都一样(不能删除Vue 实例对象或根数据的属性)

  if (!hasOwn(target, key)) {    return  }复制代码

检测key 是否是 target 对象自身拥有的属性

 if (!ob) {    return  }复制代码

判断ob对象是否存在如果不存在说明 target 对象原本就不是响应的,所以直接返回(return)即可

如果 ob 对象存在,说明 target 对象是响应的,需要触发响应才行,即执行 ob.dep.notify()

进行观测和依赖收集。


转载于:https://juejin.im/post/5cad9b49e51d456e8a12ef5b

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值