Vue3响应式系统(三)

Vue3响应式系统(二)icon-default.png?t=N7T8https://blog.csdn.net/qq_55806761/article/details/135612738

七、无限递归循环。

        响应式系统里无限递归也是需要考虑到的。

     什么情况会出现无限递归循环?

        代码示例: 

const data = { foo: 1 }
const obj = new Proxy(/* * */)
effect(() => {
  obj.foo++
}) 

        obj.foo++会直接导致栈溢出,如图所示:

        为何会这样呢?

        obj.foo++  ====>   obj.foo = obj.foo + 1 ,首先会读取obj.foo的值,这就会触发get函数中的track收集函数,之后又会将obj.foo的值赋值给obj.foo,这时又会触发trigger触发函数。读取触发的副作用函数还没有执行完,又要去设置执行副作用函数,无限循环的去调用自己,所以产生了栈溢出。 所以,既会读取值,又会设置值,这就是出现栈溢出的根本原因。

       如何解决呢?

        由于读取和设置都是在同一个副作用函数中执行的,无论是track收集到的还是trigger触发的都是activeEffect,不变。所以,我们可以在trigger增加一个守卫:如果trigger触发的执行的副作用函数与当前正在执行的副作用函数一样,便不触发执行。

        更改trigger触发函数:

function trigger(target, key) {
  const depsMap = bucketMap.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const effectToRun = new Set()



  //增加守卫
  effects && effects.forEach(effectFn => {
    if(effectFn != activeEffect) {
      effectToRun.add(effectFn)
    }
  })
  effectToRun && effectToRun.forEach(effectFn => effectFn())
}

        此时,便不会无限循环,而是只执行一次。 

八、调度执行。

        可调度性是响应式系统非常重要的的特性。

      可调度性?

        简单来说就是,trigger触发副作用函数执行的时候,可以去决定副作用函数执行的时机、次数以及方式。

        示例:(结果如图所示)

// 依据上文扩展案例

effect(() => {
  console.log(obj.foo);
})

obj.foo++

console.log('结束了')

     如果我要实现下面的效果呢?(不改变代码顺序)

        这时就需要响应式系统支持可调度我们可以为effect函数设置一个选项参数options,允许用户执行调度器。 

effect(
  () => {
    console.log(obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数
    scheduler(fn) {
      // ...
    }
  }
)

        更改effect函数 

function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }



  // 将 options 挂载到 effectFn 上
  effectFn.options = options



  effectFn.deps = []
  effectFn()
}

        根据options,就要来更改trigger函数了

function trigger(target, key) {
  const depsMap = bucketMap.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const effectToRun = new Set()
  effects && effects.forEach(effectFn => {
    if(effectFn != activeEffect) {
      effectToRun.add(effectFn)
    }
  })


  effectToRun && effectToRun.forEach(effectFn => {
    // 如果存在调度器,则调用这个调度器,并将副作用函数作为参数传递
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else { // 没有调度器则直接执行副作用函数
      effectFn()
    }
  })


}

        去测试:将副作用函数加入宏任务队列中执行

effect(
  () => {
    console.log(obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数
    scheduler(fn) {
      // 将副作用函数加入宏任务队列
      setTimeout(fn)
    }
  }
)
console.log('结束了')
obj.foo++

        如图所示,效果得以实现。

      除了调度执行顺序,还可以做到调度执行次数。

effect(
  () => {
    console.log(obj.foo);
  }
)
obj.foo++
obj.foo++

/**
 * 打印结果为:
 * 1
 * 2
 * 3
*/

        从1到3,2只是过渡,我们并不关心,所以执行三次有点多余。我们希望只打印:(不包含过度)

/**
 * 打印结果为:
 * 1
 * 3
*/

         基于调度器我们实现一下子: 

//定义一个任务队列
const jobQueue = new Set()
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列中
const p = Promise.resolve()

//一个标志代表是否正在刷新队列
let isFlushing = false
function flushJob() {
  // 如果队列正在刷新, 则什么都不做
  if(isFlushing) return 
  // 正在刷新设置
  isFlushing = true
  // 在微任务对类中刷新JobQueue队列
  p.then(() => {
    jobQueue.forEach(job => job())
  }).finally(() => {
    //结束后重置 isFlushing
    isFlushing = false
  })
}

// 测试
effect(
  () => {
    console.log(obj.foo);
  },
  {
    scheduler(fn) {
      jobQueue.add(fn)
      flushJob()
    }
  }
)
obj.foo++
obj.foo++
/**
 * 打印:
 * 1
 * 3
*/

        当obj.foo执行两次自增操作,会同步且连续执行两次scheduler调度函数,这意味着同一个副作用函数会被jobQueue.add(fn)语句添加两次,由于Set去重能力,最终jobQueue中只会有一项,即当前副作用函数。同理,flushJob也会同步执行两次,由于isFlushing标志的存在,实际上flushJob函数在一个事件循环内只会执行一次,即在微任务队列内执行一次。当微任务队列开始执行的时候,jobQueue队列就会遍历里面存的副作用函数,当副作用函数调用的时候obj.foo已经是3了,这样我们就实现了所期望的值。

        其实这个功能类似于Vue.js中连续多次修改响应式数据但只会触发一次更新,实际上Vue.js内部实现了一个更加完善的调度器,思路与上面代码思路相同。

相关推荐

  1. VUE3-响应

    2024-01-17 08:48:09       37 阅读
  2. vue2响应vue3响应

    2024-01-17 08:48:09       11 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-17 08:48:09       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-17 08:48:09       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-17 08:48:09       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-17 08:48:09       18 阅读

热门阅读

  1. js arguments对象的由来和用法

    2024-01-17 08:48:09       26 阅读
  2. vue的sync语法糖的使用

    2024-01-17 08:48:09       28 阅读
  3. 正则表达式2 常见模式

    2024-01-17 08:48:09       38 阅读
  4. 正则表达式

    2024-01-17 08:48:09       30 阅读
  5. UML2.0 14种图简单介绍

    2024-01-17 08:48:09       27 阅读
  6. Js高级语法

    2024-01-17 08:48:09       24 阅读
  7. 面试题总结-MQ总结

    2024-01-17 08:48:09       32 阅读
  8. cmake构建动态库实例(cmakelist)

    2024-01-17 08:48:09       34 阅读