vue3是通过Proxy来拦截对数据的操作,实现响应式的。

    1. 为什么执行副作用函数前要有清除操作(cleanup)?
    2. 为什么加了清除上一次的依赖关系操作后 effects.forEach(fn => fn())会变成死循环
    3. 为什么要支持嵌套effect?
    4. 在注册副作用函数中既读取属性值,又设置属性值时,会发生堆栈溢出?
    1. let activeEffect = null
    2. // 副作用函数存储栈,保留外层副作用函数,解决问题3
    3. let effectStack = []
    4. // 存储收集到的响应式数据
    5. // Map{target: Map{key: Set[...effects]}}
    6. const bucket = new WeakMap()
    7. const data = {
    8. text: 'Hello world!',
    9. ok: true
    10. }
    11. let dataProxy = new Proxy(data, {
    12. get(target, key){
    13. track(target, key)
    14. return target[key]
    15. },
    16. set(target, key, newValue){
    17. target[key] = newValue
    18. trigger(target, key)
    19. }
    20. })
    21. effect(() => {
    22. document.body.innerHTML = dataProxy.ok ? dataProxy.text : 'not'
    23. })
    24. dataProxy.ok = false
    25. // 此方法是为了清除当前副作用函数之前执行时收集到的依赖,解决问题1
    26. // 例如此例中,当 dataProxy.ok 为 false 时,dataProxy.text将不会再被读取,
    27. // 也就是说之后修改text不应该再触发 dataProxy 的 set 去执行副作用函数
    28. function cleanup(effectFn){
    29. effectFn.deps.forEach(deps => deps.delete(effectFn))
    30. effectFn.deps.length = 0
    31. }
    32. // 注册副作用函数
    33. function effect(fn){
    34. const effectFn = () => {
    35. cleanup(effectFn)
    36. activeEffect = effectFn
    37. effectStack.push(effectFn)
    38. fn()
    39. effectStack.pop()
    40. activeEffect = effectStack[effectStack.length - 1]
    41. }
    42. effectFn.deps = []
    43. effectFn()
    44. }
    45. function track(target, key){
    46. if(!activeEffect) return
    47. let depsMap = bucket.get(target)
    48. if(!depsMap){
    49. bucket.set(target, depsMap = new Map())
    50. }
    51. let deps = depsMap.get(key)
    52. if(!deps){
    53. depsMap.set(key, deps = new Set())
    54. }
    55. deps.add(activeEffect)
    56. activeEffect.deps.push(deps)
    57. }
    58. function trigger(target, key){
    59. let depsMap = bucket.get(target)
    60. if(!depsMap) return
    61. let effects = depsMap.get(key)
    62. // effects && effects.forEach(fn => fn());
    63. // 由上面的书写方式改为下面的 解决问题2
    64. // 原因: 当对dataPorxy的属性(key)进行修改时,trigger执行 => 获取key对应的effects =>
    65. // effects执行(即effect函数中effectFn执行) => cleanup函数执行 => key对应的effects中的值被删除
    66. // effect函数传入的fn执行 => dataPorxy的属性get触发 => key对应的effects中的值又被添加
    67. // 从上述过程可以看出: 获取key对应的effects 中的值被删除又重新添加,导致死循环
    68. // 见规范: https://262.ecma-international.org/6.0/#sec-set.prototype.foreach
    69. // 规范内容:每个值通常只被访问一次。但是,如果一个值在被访问后被删除,然后在调用完成之前被重新添加,则该值将被重新访问。
    70. // 例子:
    71. // const set = new Set([1])
    72. // set.forEach(item => {
    73. // set.delete(1)
    74. // set.add(1)
    75. // console.log('执行中')
    76. // })
    77. let effectToRun = new Set()
    78. effects && effects.forEach(effect => {
    79. // 解决问题4,避免副作用函数递归调用,栈溢出
    80. // 原因:当副作用函数中读取值后副作用函数会执行,在执行过程中 又设置了该属性值,
    81. // 便触发trigger函数执行,在trigger中又执行副作用函数,如此递归导致爆栈
    82. // 解决:当当前执行的副作用函数是 effect时,就不再执行了
    83. if(effect !== activeEffect) effectToRun.add(effect)
    84. });
    85. effectToRun.forEach(fn => fn());
    86. }