vue-set/delete/nextTick源码简读

文章目录

set

  • Vue.setvm.$set是向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且能触发视图更新
  • vue中,一般会对初始化的数据进行响应式处理,但是如果对一个对象新增一个属性,如有obj= {id:1},现在相对obj添加一个name属性,直接写obj.name=‘xxx’并不会触发视图更新,需要通过vm.$set(vm.obj,'name','xxx')进行属性添加,这样才会使得这个属性为响应式的且能触发视图更新
  • 上面有了解到Vue.setvm.$set都是添加一个响应式对象,首先,来看一下这两个set方法的定义,下面有列出Vue.setvm.$set的定义,可以看到,其实这两个方法最终都指向了set
  // src\core\instance\state.js
  Vue.prototype.$set = set

  // src\core\global-api\index.js
  Vue.set = set
  • 下面是set方法的源码
  • 1)传入三个参数:taregt需要进行属性设置的对象;key需要设置成响应式的属性名;val为属性名赋值的属性值
  • 2)如果taregt为数组,则会判断key是否为合法索引,是的话则会调用target上的splice方法进行属性设置。之所以采用splice是因为在vue进行响应式处理时,会数组进行重写,并将其中会更改原数组的方法进行响应式处理;
  • 3)如果key在对象中已经存在,则直接对该属性进行复制操作;
  • 4)获取target上的__ob__observer对象ob
  • 5)如果ob不存在,target不是响应式对象直接赋值
  • 6)把key通过defineReactive设置成响应式属性,并通过ob发送通知
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是否是对象,key是否是合法的索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // 通过splice对key位置的元素进行替换
    // splice在array.js进行了响应式处理
    target.splice(key, 1, val)
    return val
  }
  // 如果key在对象中已经存在直接复制
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 获取target中的observer对象
  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
  }
  // 如果ob不存在,target不是响应式对象直接赋值
  if (!ob) {
    target[key] = val
    return val
  }
  // 把key设置成响应式属性
  defineReactive(ob.value, key, val)
  // 发送通知
  ob.dep.notify()
  return val
}

delete

  • 删除对象的属性。如果对象是响应式的,确保删除可以出发视图更新,这个方法主要用于避开vue不能检测到属性被删除的限制。Vue.deletevm.$delete同上面的set相同,最终都是指向同一函数del,下面具体看一下del函数的实现
  • 1)target需要删除属性的对象,key需要删除的属性
  • 2)判断是否为数组,以及key是否是有效索引,如果是数组,通过splice进行删除,上面有提到splice方法是做过响应式处理
  • 3)获取target上的observer对象__ob__
  • 4)target如果是Vue实例或者$data对象,直接返回
  • 5) 如果target对象没有key属性直接返回
  • 6)删除属性,并通过ob发送通知
export function del (target: Array<any> | Object, key: any) {
  // 如果是undefined或者原始值,错误警告
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 判断是否为数组,以及key是否是有效索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 如果是数组,通过splice进行删除
    // splice做过响应式处理
    target.splice(key, 1)
    return
  }
  // 获取target对象
  const ob = (target: any).__ob__
  // target如果是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
  }
  // 如果target对象没有key属性直接返回
  if (!hasOwn(target, key)) {
    return
  }
  // 删除属性
  delete target[key]
  // 不是响应式对象,则直接返回
  if (!ob) {
    return
  }
  // 通过ob发送通知
  ob.dep.notify()
}

nextTick

  • 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM,如下所示:

    // 修改数据
    vm.msg = 'Hello'
    // DOM 还没有更新
    Vue.nextTick(function () {
      // DOM 更新了
    })
    
  • 在静态方法中的Vue.nextTick与实例上的vm.$nextTick最终都是指向的\src\core\util\next-tick.js下的nextTick方法,下面具体看一下nextTick的实现

  • 1、在前面的文章中知道,当更改一个数据时,会进行派发更新,在queueWatcher(src\core\observer\scheduler.js)函数中最终会通过nextTick(flushSchedulerQueue)来实现flushSchedulerQueue的调用;

  • 2、那么在通过nextTick获取DOM上的最新数据时,其实DOM还未渲染到视图上,而DOM真正渲染是是在这次的事件循环之后;

  • 3、而在nextTick之所以可以获取到DOM上的最新数据,是由于此时已经生成了DOM树,而最新的数据也是从DOM树上获取到的


  • nextTick函数中,会将加入异常处理的cb存入callbacks数组中,之后通过timerFunc来进行回调的调用
// \src\core\util\next-tick.js
let _resolve;
function nextTick(cb?: Function, ctx?: Object) {
  let _resolve;
  // 1.将传入的 cb 方法添加到回调数组
  callbacks.push(() => {
    cb.call(ctx);
  });
  // 2.执行异步任务
  // 此方法会根据浏览器兼容性,选用不同的异步策略
  timerFunc();
}
  • 下面来一下timerFunc,可以看到timerFunc就是根据浏览器的兼容性,采用不同的异步策略,顺序为promise->MutationObserver->setImmediate->setTimeout
// \src\core\util\next-tick.js
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
  • timerFunc会执行flushCallbacks,该函数会一次执行cb
// \src\core\util\next-tick.js
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值