数据驱动

  • 数据驱动
    • 只需要关心数据处理,不用关心数据是如何渲染到视图的
  • 属性响应式
    • 数据驱动的具体实现方式
  • 双向数据绑定
    • 数据更新会导致视图更新,视图变化会影响数据变化

Vue2 响应式原理

  • Vue 2.x 的数据响应式通过 Object.defineProperty() 实现
    • 设置 data 后,遍历所以属性,转换为 Getter 和 Setter,从而在数据变化时进行视图更新操作
      1. <div id="app">原始内容</div>
      2. <script>
      3. // 声明数据对象,模拟 Vue 实例的 data 属性
      4. let data = {
      5. msg: 'hello'
      6. }
      7. // 模拟 Vue 实例的对象
      8. let vm = {}
      9. // 通过数据劫持的方式,将 data 的属性设置为 getter/setter
      10. Object.defineProperty(vm, 'msg', {
      11. // 可遍历
      12. enumerable: true,
      13. // 可配置
      14. configurable: true,
      15. get () {
      16. console.log('访问了属性')
      17. return data.msg
      18. },
      19. set (newValue) {
      20. // 更新数据
      21. data.msg = newValue
      22. // 数据更改,更新视图中 DOM 元素的内容
      23. document.querySelector('#app').textContent = data.msg
      24. }
      25. })
      26. </script>

处理多个属性的情况

  1. <div id="app">原始内容</div>
  2. <script>
  3. // 声明数据对象,模拟 Vue 实例的 data 属性
  4. let data = {
  5. msg1: 'hello',
  6. msg2: 'world',
  7. }
  8. // 模拟 Vue 实例的对象
  9. let vm = {}
  10. // 遍历被劫持对象的所有属性
  11. Object.keys(data).forEach(key => {
  12. // 通过数据劫持的方式,将 data 的属性设置为 getter/setter
  13. Object.defineProperty(vm, key, {
  14. enumerable: true,
  15. configurable: true,
  16. get () {
  17. console.log('访问了属性')
  18. return data[key]
  19. },
  20. set (newValue) {
  21. // 更新数据
  22. data[key] = newValue
  23. // 数据更改,更新视图中 DOM 元素的内容
  24. document.querySelector('#app').textContent = data[key]
  25. }
  26. })
  27. })
  28. </script>

监测数组方法

  1. <div id="app">原始内容</div>
  2. <script>
  3. // 声明数据对象,模拟 Vue 实例的 data 属性
  4. let data = {
  5. msg1: 'hello',
  6. msg2: 'world',
  7. arr: [1, 2, 3]
  8. }
  9. // 模拟 Vue 实例的对象
  10. let vm = {}
  11. // --- 添加数组方法支持 ---
  12. const arrMethodName = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
  13. // 用于存储处理结果的对象,准备替换掉数组实例的原型指针 __proto__
  14. const customProto = {}
  15. // 为了避免数组实例无法再使用其他的数组方法
  16. customProto.__proto__ = Array.prototype
  17. arrMethodName.forEach(method => {
  18. customProto[method] = function () {
  19. // 确保原始功能可以使用(this 为数组实例)
  20. const result = Array.prototype[method].apply(this, arguments)
  21. // 进行其他自定义功能设置,例如,更新视图
  22. document.querySelector('#app').textContent = this
  23. return result
  24. }
  25. })
  26. // 遍历被劫持对象的所有属性
  27. Object.keys(data).forEach(key => {
  28. // 检测是否为数组
  29. if (Array.isArray(data[key])) {
  30. // 将当前数组实例的 __proto__ 更换为 customProto 即可
  31. data[key].__proto__ = customProto
  32. }
  33. // 通过数据劫持的方式,将 data 的属性设置为 getter/setter
  34. Object.defineProperty(vm, key, {
  35. enumerable: true,
  36. configurable: true,
  37. get () {
  38. console.log('访问了属性')
  39. return data[key]
  40. },
  41. set (newValue) {
  42. // 更新数据
  43. data[key] = newValue
  44. // 数据更改,更新视图中 DOM 元素的内容
  45. document.querySelector('#app').textContent = data[key]
  46. }
  47. })
  48. })
  49. </script>

封装递归

  1. <div id="app">原始内容</div>
  2. <script>
  3. // 声明数据对象,模拟 Vue 实例的 data 属性
  4. let data = {
  5. msg1: 'hello',
  6. msg2: 'world',
  7. arr: [1, 2, 3],
  8. obj: {
  9. name: 'jack',
  10. age: 18
  11. }
  12. }
  13. // 模拟 Vue 实例的对象
  14. let vm = {}
  15. // 封装为函数,用于对数据进行响应式处理
  16. const createReactive = (function () {
  17. const arrMethodName = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
  18. const customProto = {}
  19. customProto.__proto__ = Array.prototype
  20. arrMethodName.forEach(method => {
  21. customProto[method] = function () {
  22. const result = Array.prototype[method].apply(this, arguments)
  23. document.querySelector('#app').textContent = this
  24. return result
  25. }
  26. })
  27. // 需要进行数据劫持的主体功能,也是递归时需要的功能
  28. return function (data, vm) {
  29. // 遍历被劫持对象的所有属性
  30. Object.keys(data).forEach(key => {
  31. // 检测是否为数组
  32. if (Array.isArray(data[key])) {
  33. // 将当前数组实例的 __proto__ 更换为 customProto 即可
  34. data[key].__proto__ = customProto
  35. } else if (typeof data[key] === 'object' && data[key] !== null) {
  36. // 检测是否为对象,如果为对象,进行递归操作
  37. vm[key] = {}
  38. createReactive(data[key], vm[key])
  39. return
  40. }
  41. // 通过数据劫持的方式,将 data 的属性设置为 getter/setter
  42. Object.defineProperty(vm, key, {
  43. enumerable: true,
  44. configurable: true,
  45. get () {
  46. console.log('访问了属性')
  47. return data[key]
  48. },
  49. set (newValue) {
  50. // 更新数据
  51. data[key] = newValue
  52. // 数据更改,更新视图中 DOM 元素的内容
  53. document.querySelector('#app').textContent = data[key]
  54. }
  55. })
  56. })
  57. }
  58. })()
  59. createReactive(data, vm)
  60. </script>

Vue3 响应式原理

  • Vue 3.x 与 Vue 2.x 的区别为数据响应式是通过 Proxy 实现的,其他的都相同

    1. <div id="app">原始内容</div>
    2. <script>
    3. const data = {
    4. msg: 'hello',
    5. content: 'world',
    6. arr: [1, 2, 3],
    7. obj: {
    8. name: 'william',
    9. age: 18
    10. }
    11. }
    12. const vm = new Proxy(data, {
    13. get (target, key) {
    14. return target[key]
    15. },
    16. set (target, key, newValue) {
    17. // 数据更新
    18. target[key] = newValue
    19. // 视图更新
    20. document.querySelector('#app').textContent = target[key]
    21. }
    22. })
    23. </script>

观察者模式

  • 观察者模式(Observer pattern)指的是在对象间定义一个—对多个(被观察者与多个观察者)的关联,当一个对象改变了状态,所有其他相关的对象会被通知并且自动刷新
  • 核心概念
    • 观察者Observer
    • 被观察者(观察目标)Subject

截屏2021-03-29 下午10.03.33.png

  1. // 被观察者 (观察目标)
  2. // 1 添加观察者
  3. // 2 通知所有观察者
  4. class Subject {
  5. constructor () {
  6. // 存储所有的观察者
  7. this.observers = []
  8. }
  9. // 添加观察者功能
  10. addObserver (observer) {
  11. // 检测传入的参数是否为 观察者 实例
  12. if (observer && observer.update) {
  13. this.observers.push(observer)
  14. }
  15. }
  16. // 通知所有观察者
  17. notify () {
  18. // 调用观察者列表中每个观察者的更新方法
  19. this.observers.forEach(observer => {
  20. observer.update()
  21. })
  22. }
  23. }
  24. // 观察者
  25. // 1 当观察目标发生状态变化时,进行"更新"
  26. class Observer {
  27. update () {
  28. console.log('事件发生了,进行相应的处理...')
  29. }
  30. }
  31. // 功能测试
  32. const subject = new Subject()
  33. const ob1 = new Observer()
  34. const ob2 = new Observer()
  35. // 将观察者添加给要观察的观察目标
  36. subject.addObserver(ob1)
  37. subject.addObserver(ob2)
  38. // 通知观察者进行操作(某些具体的场景下)
  39. subject.notify()

发布/订阅模式

  • 发布-订阅模式(Publish-subscribe pattern)可认为是为观察者模式解耦的进阶版本
    • 在发布与订阅者之间添加消息中心,所有的消息均通过消息中心管理,而发布者与订阅者不会直接联系,实现了两者的解耦
  • 核心概念
    • 消息中心 Dep
    • 订阅者 Subscriber
    • 发布者 Publisher

截屏2021-03-29 下午10.04.42.png

  1. <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
  2. <script>
  3. // 创建了一个 Vue 实例(消息中心)
  4. const eventBus = new Vue()
  5. // 注册事件(设置订阅者)
  6. eventBus.$on('dataChange', () => {
  7. console.log('事件处理功能1')
  8. })
  9. eventBus.$on('dataChange', () => {
  10. console.log('事件处理功能2')
  11. })
  12. // 触发事件(设置发布者)
  13. eventBus.$emit('dataChange')
  14. </script>

小结

  • 观察者模式是由观察者与观察目标组成的,适合组件内操作
    • 特性:特殊事件发生后,观察目标统一通知所有观察者
  • 发布/订阅模式是由发布者与订阅者以及消息中心组成,更在适合消息类型复杂的情况
    • 特性:特殊事件发生,消息中心接到发布指令后,会根据事件类型给对应的订阅者发送消息

Vue 响应式模拟

整体分析

截屏2021-03-29 下午10.36.35.png

  • Vue
    • 目标:将 data 数据注入到 Vue 实例,便于方法内操作
  • Observer(发布者)
    • 目标:数据劫持,监听数据变化,并在变化时通知 Dep
  • Dep(消息中心)
    • 目标:存储订阅者以及管理消息的发送
  • Watcher(订阅者)
    • 目标:订阅数据变化,进行视图的更新
  • Compiler
    • 解析模板中的指令与插值表达式,并替换相应的数据

Vue 类

  • 功能
    • 接收配置信息
    • 将 data 的属性转换成 Getter/Setter,并注入到 Vue 实例中
    • *监听 data 中所有属性的变化,设置成响应式数据
    • *调用解析功能(解析模板内插值表达式、指令等)
  • 结构
      • $optons
      • $el
    • $data
      • _proxyData()

Observer 类

  • 功能
    • 通过数据劫持方式监视 data 中属性变化,变化时通知消息中心 Dep
    • 需要考虑 data 的属性也可能是对象,也要转换成响应式数据
  • 结构
      • walk(data)
      • defineReactive(data, key, value)

Dep 类

  • Dep 是 Dependency 的简写,含义为“依赖”,指的是 Dep 用于收集与管理订阅者与发布者之间的依赖关系
  • 功能
    • *为每个数据收集对应的依赖,存储依赖
    • 添加并存储订阅者
    • 数据变化时,通知所有观察者
  • 结构
      • subs
      • addSub(sub)
      • notify()

Watcher 类

  • 功能
    • 实例化 Watcher 时,往 dep 对象中添加自己
    • 当数据变化触发 dep, dep通知所有对应的 Watcher 实例更新视图
  • 结构
      • vm
      • key
      • cb
    • oldValue
      • update()

Compiler 类

  • 功能
    • 进行编译模板,并解析内部指令与插值表达式
    • 进行页面的首次渲染
    • 数据变化后重新渲染视图
  • 结构
      • el
      • vm
      • compiler(el)
      • compilerElement(node)
      • compilerText(node)
      • isDirective(attrname)
      • isTextNode(node)
      • isElementNode(node)

总结

  • Vue类
    • 把 data 的属性注入到 Vue 实例
    • 调用 Observer 实现数据响应式处理
    • 调用 Compiler 编译模板
  • Observer 类
    • 将 data 的属性转换成 Getter/Setter
    • 为 Dep 添加订阅者 Watcher
    • 数据变化发送通知 Dep
  • Dep 类
    • 收集依赖,添加订阅者 (watcher)
    • 通知订阅者
  • Watcher 类
    • 编译模板时创建订阅者,订阅者数据变化
    • 接到 Dep 通知时,调用 Compiler 中的模板功能更新视图
  • Compiler 类
    • 编译模板,解析指令与差值表达式
    • 负责页面首次渲染与数据变化后重新渲染

QQ截图20210330134300.png