前言

在上一篇《Vue2.0 如何实现数据响应式》中我们知道,Vue2.0 是通过 ES5 Object.defineProperty 来实现响应式的。但是也正是因为使用了这个方法,所以在 Vue2.0 的数据响应式中存在着缺陷:

  1. Vue 实例化后新增的属性,不会被监听。但可以使用 Vue.set(data, 'a', 1) 设置新属性。
  2. Object.defineProperty 的一个缺陷是无法监听数组变化(数组的7个改变原数组本身能被监听)。同样可以使用 Vue.set 来设置数组项。

这一篇,我们将来看一下新的 Vue3.0 是如何利用 ES6 Proxy 来重写实现的。

实践

在开始之前,先把 vue-next 源码克隆到本地,执行 npm run dev,生成 vue 包。

image.png
在 dist 目录下新建一个 index.html,尝试写一下 3.0 语法:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="container"></div>
  10. <script src="./vue.global.js"></script>
  11. <script>
  12. const App = {
  13. // created 只会执行一次
  14. // Vue.reactive() 返回一个 Proxy
  15. setup () {
  16. let state = Vue.reactive({
  17. name: 'zhuojiawei'
  18. })
  19. // 更新数据
  20. function changeName () {
  21. state.name = 'allen'
  22. }
  23. // data
  24. return {
  25. state,
  26. changeName
  27. }
  28. },
  29. template: `<div @click="changeName">{{state.name}}</div>`
  30. }
  31. // 创建应用
  32. // App 对象
  33. // 父容器
  34. Vue.createApp().mount(App, container)
  35. </script>
  36. </body>
  37. </html

我们打印一下 state,看到 state 返回的是一个 Proxy
image.png

这里,就引出了 Vue3.0 的实现数据响应式的核心就是 ES6 Proxy(代理),Proxy 的具体用法可以参考 Proxy
**

响应式代码实现

基础版(对象只有一层)

  1. // vue2.0 缺点:
  2. // 1. 默认递归,性能问题
  3. // 2. 数组改变长度无效
  4. // 3. 对象不存在的属性,不会被拦截
  5. // ***************************************************
  6. // vue3.0 响应式原理
  7. // 判断是不是对象
  8. function isObject (val) {
  9. return typeof val === 'object' && val !== null
  10. }
  11. // 响应式的核心方法
  12. function reactive (target) {
  13. // 创建响应式对象
  14. return createReactiveObject(target)
  15. }
  16. // 创建响应式对象
  17. function createReactiveObject (target) {
  18. if (!isObject(target)) { // 如果当前不是对象,直接返回
  19. return target
  20. }
  21. let baseHandler = {
  22. // Reflect 优点不会报错,会有返回值 会替代掉 Object 上的方法
  23. get (target, key, receiver){
  24. console.log('获取')
  25. let result = Reflect.get(target, key, receiver)
  26. return result
  27. },
  28. set (target, key, value, receiver) {
  29. console.log('设置')
  30. let result = Reflect.set(target, key, value, receiver)
  31. return result
  32. },
  33. deleteProperty (target, key) {
  34. console.log('删除')
  35. let result = Reflect.deleteProperty(target, key)
  36. return result
  37. }
  38. }
  39. let observed = new Proxy(target, baseHandler) // es6
  40. return observed
  41. }
  42. let proxy = reactive({
  43. name: 'zhoujiawei'
  44. })
  45. // 获取
  46. console.log(proxy.name)
  47. // 设置
  48. proxy.name = 'allen'
  49. console.log(proxy.name)
  50. // 删除
  51. delete proxy.name
  52. console.log(proxy.name)

image.png

对象多层嵌套

  1. // 弱映射表
  2. // 用来屏蔽多次 new 以及多层代理
  3. let toProxy = new WeakMap() // es6 放置的是 原对象:代理过的对象
  4. let toRaw = new WeakMap() // 被代理过的对象:原对象
  5. // 判断是不是对象
  6. function isObject (val) {
  7. return typeof val === 'object' && val !== null
  8. }
  9. // 判断是否含有属性
  10. function hasOwn (target, key) {
  11. return target.hasOwnProperty(key)
  12. }
  13. // 响应式的核心方法
  14. function reactive (target) {
  15. // 创建响应式对象
  16. return createReactiveObject(target)
  17. }
  18. // 创建响应式对象
  19. function createReactiveObject (target) {
  20. if (!isObject(target)) { // 如果当前不是对象,直接返回
  21. return target
  22. }
  23. // 如果代理过了 就将代理过的结果返回
  24. let proxy = toProxy.get(target)
  25. if (proxy) {
  26. return proxy
  27. }
  28. // 防止代理过的对象,再次代理
  29. if (toRaw.has(target)) {
  30. return target
  31. }
  32. let baseHandler = {
  33. // Reflect 优点不会报错,会有返回值 会替代掉 Object 上的方法
  34. get (target, key, receiver){
  35. console.log('获取')
  36. // Reflect 反射
  37. let result = Reflect.get(target, key, receiver)
  38. // result 是当前获取到的值
  39. // **********************************
  40. return isObject(result) ? reactive(result) : result // 有选择的递归
  41. // **********************************
  42. },
  43. set (target, key, value, receiver) {
  44. // 识别是改属性 还是新增属性
  45. let hadKey = hasOwn(target, key)
  46. let oldValue = target[key]
  47. let result = Reflect.set(target, key, value, receiver)
  48. if (!hadKey) {
  49. console.log('新增属性')
  50. } else if (oldValue !== value) {
  51. console.log('修改属性')
  52. }
  53. console.log('设置')
  54. return result
  55. },
  56. deleteProperty (target, key) {
  57. console.log('删除')
  58. let result = Reflect.deleteProperty(target, key)
  59. return result
  60. }
  61. }
  62. let observed = new Proxy(target, baseHandler) // es6
  63. toProxy.set(target, observed)
  64. toRaw.set(observed, target)
  65. return observed
  66. }
  67. let proxy = reactive([1, 2, 3])
  68. proxy.push(4)

effect 副作用

effect 副作用方法:默认先执行一次,当依赖数据变化的时候会再次执行,effect 也是响应式的核心。(watch 也是调用 effect )

  1. // 弱映射表
  2. // 用来屏蔽多次 new 以及多层代理
  3. let toProxy = new WeakMap() // es6 放置的是 原对象:代理过的对象
  4. let toRaw = new WeakMap() // 被代理过的对象:原对象
  5. // 判断是不是对象
  6. function isObject (val) {
  7. return typeof val === 'object' && val !== null
  8. }
  9. // 判断是否含有属性
  10. function hasOwn (target, key) {
  11. return target.hasOwnProperty(key)
  12. }
  13. // 响应式的核心方法
  14. function reactive (target) {
  15. // 创建响应式对象
  16. return createReactiveObject(target)
  17. }
  18. // 创建响应式对象
  19. function createReactiveObject (target) {
  20. if (!isObject(target)) { // 如果当前不是对象,直接返回
  21. return target
  22. }
  23. // 如果代理过了 就将代理过的结果返回
  24. let proxy = toProxy.get(target)
  25. if (proxy) {
  26. return proxy
  27. }
  28. // 防止代理过的对象,再次代理
  29. if (toRaw.has(target)) {
  30. return target
  31. }
  32. let baseHandler = {
  33. // Reflect 优点不会报错,会有返回值 会替代掉 Object 上的方法
  34. get (target, key, receiver){
  35. // Reflect 反射
  36. let result = Reflect.get(target, key, receiver)
  37. // result 是当前获取到的值
  38. // 收集依赖 订阅 把当前的 key 和这个 effect 对应起来
  39. track(target, key) // 如果目标上这个 key 变化了 重新让数组中的 effect 执行
  40. return isObject(result) ? reactive(result) : result // 有选择的递归
  41. },
  42. set (target, key, value, receiver) {
  43. // 识别是改属性 还是新增属性
  44. let hadKey = hasOwn(target, key)
  45. let oldValue = target[key]
  46. let result = Reflect.set(target, key, value, receiver)
  47. if (!hadKey) {
  48. trigger(target, 'add', key)
  49. } else if (oldValue !== value) {
  50. trigger(target, 'set', key)
  51. }
  52. return result
  53. },
  54. deleteProperty (target, key) {
  55. let result = Reflect.deleteProperty(target, key)
  56. return result
  57. }
  58. }
  59. let observed = new Proxy(target, baseHandler) // es6
  60. toProxy.set(target, observed)
  61. toRaw.set(observed, target)
  62. return observed
  63. }
  64. // 依赖收集(发布订阅)
  65. // effect 副作用方法:默认先执行一次,当依赖数据变化的时候会再次执行,effect 也是响应式的核心
  66. let activeEffectStacks = [] // 栈型结果
  67. let targetsMap = new WeakMap()
  68. function track (target, key) { // 如果目标上这个 key 变化了 重新让数组中的 effect 执行
  69. let effect = activeEffectStacks[activeEffectStacks.length - 1]
  70. if (effect) { // 有对应关系 创建关联
  71. // 动态创建依赖关系
  72. let depsMap = targetsMap.get(target)
  73. if (!depsMap) {
  74. targetsMap.set(target, depsMap = new Map)
  75. }
  76. let deps = depsMap.get(key)
  77. if (!deps) {
  78. depsMap.set(key, deps = new Set())
  79. }
  80. if (!deps.has(effect)) {
  81. deps.add(effect)
  82. }
  83. }
  84. }
  85. function trigger (target, type, key) {
  86. let depsMap = targetsMap.get(target)
  87. if (depsMap) {
  88. let deps = depsMap.get(key)
  89. if (deps) {
  90. deps.forEach(effect => {
  91. effect()
  92. })
  93. }
  94. }
  95. }
  96. function effect (fn) {
  97. // 需要把 fn 这个函数变成响应式的函数
  98. let effect = createReactiveEffect(fn)
  99. effect()
  100. }
  101. function createReactiveEffect (fn) {
  102. let effect = function () {
  103. return run(effect, fn) // 让 fn 执行,把这个 effect 存到栈中
  104. }
  105. return effect
  106. }
  107. function run (effect, fn) { // 运行 fn,并且将 effect 存起来
  108. try {
  109. activeEffectStacks.push(effect)
  110. fn() // js 单线程
  111. } finally {
  112. activeEffectStacks.pop()
  113. }
  114. }
  115. let proxy = reactive({
  116. name: 'zhoujiawei'
  117. })
  118. effect(() => {
  119. console.log(proxy.name)
  120. })
  121. proxy.name = 'allen'

image.png