当前位置: 首页 > article >正文

vue2.x 的依赖收集通知更新

Vue 的响应式系统是基于 观察者模式 设计的。当某些数据(状态)发生变化时,依赖这些数据的视图或计算属性会自动更新。为了实现这一点,Vue 会对数据进行 “劫持”(通过 Object.defineProperty 或 Proxy),并在读取数据时进行 依赖收集,在数据变化时通知所有依赖进行更新。

  1. 依赖收集

    • 当你使用 data 中的属性时,Vue 会自动创建一个 依赖关系,将当前组件的 Watcher (观察者) 与这个属性关联起来。
    • Watcher 是一个特殊的对象,它负责在数据发生变化时执行特定的函数。
    • 依赖收集的过程主要通过 getter 和 setter 函数实现。
  2. 通知更新

    • 当 data 中的属性发生变化时,Vue 会触发 setter 函数。
    • setter 函数会通知所有与该属性相关的 Watcher,并执行它们的更新逻辑。
    • 更新逻辑通常是重新渲染组件的模板,从而反映数据变化。
  3. 具体流程

    • 数据访问: 当你访问 data 中的属性时,Vue 会创建一个依赖关系,将当前组件的 Watcher 与该属性关联起来。
    • 依赖记录: Vue 会将 Watcher 记录到属性对应的 Dep 对象中。Dep 对象是一个专门用于管理依赖关系的对象。
    • 数据变化: 当 data 中的属性发生变化时,Vue 会触发 setter 函数。
    • 通知 Watcher: setter 函数会通知所有与该属性相关的 Watcher,并执行它们的更新逻辑。
    • 组件更新: Watcher 的更新逻辑通常是重新渲染组件的模板,从而反映数据变化。

示例:

<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!'
    }
  },
  mounted() {
    // 访问 data 中的属性,建立依赖关系
    console.log(this.message);
  }
}
</script>

在这个例子中,当组件被挂载时,访问 this.message 会建立一个依赖关系,将组件的 Watcher 与 message 属性关联起来。

如果 message 属性的值发生变化,Vue 会通知相关的 Watcher,并重新渲染组件的模板,从而显示新的消息。

Vue 使用 Observer、Watcher 和 Dep 类来实现依赖收集。

依赖收集
  1. Observer:
    • Observer 类负责将数据转换为响应式对象。它会遍历对象的所有属性,并使用defineReactive 方法为每个属性设置 getter 和 setter。
    • defineReactive 方法会为每个属性创建一个 Dep 实例,用于收集依赖。
// core/observer/index.js
function observe(value, asRootData) {
  if (!isObject(value)) {
    return
  }
  let ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  return ob
}

class Observer {
  constructor(value) {
    this.value = value; // 保存被观察的对象
    this.dep = new Dep(); // 创建一个 Dep 实例,用于收集依赖
    this.vmCount = 0; // 记录有多少个 Vue 实例依赖于这个观察者
    def(value, '__ob__', this); // 将观察者实例附加到对象上
    if (Array.isArray(value)) {
      // 处理数组的特殊情况
    } else {
      this.walk(value); // 遍历对象的所有属性
    }
  }

  walk(obj) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]); // 为每个属性设置 getter 和 setter
    }
  }
}

function defineReactive(obj, key, val, customSetter, shallow) {
  const dep = new Dep(); // 创建一个 Dep 实例,用于收集依赖

  const property = Object.getOwnPropertyDescriptor(obj, key); // 获取属性描述符
  if (property && property.configurable === false) {
    return
  }

  const getter = property && property.get; // 获取原始的 getter
  const setter = property && property.set; // 获取原始的 setter

  let childOb = !shallow && observe(val); // 递归观察子属性
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val; // 获取属性值
      if (Dep.target) {
        dep.depend(); // 收集依赖
        if (childOb) {
          childOb.dep.depend(); // 收集子属性的依赖
        }
        if (Array.isArray(value)) {
          dependArray(value); // 处理数组的依赖
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val; // 获取属性值
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal); // 调用原始的 setter
      } else {
        val = newVal; // 更新属性值
      }
      childOb = !shallow && observe(newVal); // 递归观察新的子属性
      dep.notify(); // 通知所有依赖更新
    }
  });
}
  1. Dep:
    • Dep 类维护一个订阅者列表,记录所有依赖于该属性的 Watcher 实例。
    • 每当属性被访问时,Dep 会将当前的 Watcher 添加到订阅者列表中。
// core/observer/dep.js
class Dep {
  constructor() {
    this.id = uid++; // 唯一标识符
    this.subs = []; // 订阅者列表
  }

  addSub(sub) {
    this.subs.push(sub); // 添加订阅者
  }

  removeSub(sub) {
    remove(this.subs, sub); // 移除订阅者
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this); // 收集依赖
    }
  }

  notify() {
    const subs = this.subs.slice(); // 复制订阅者列表
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update(); // 通知所有订阅者更新
    }
  }
}

Dep.target = null; // 当前正在收集依赖的 Watcher
  1. Watcher:
    • Watcher 类负责执行依赖收集和更新。它会在组件渲染时或计算属性计算时被创建。
    • Watcher 实例会在访问响应式数据时被添加到相应 Dep 的订阅者列表中。
// core/observer/watcher.js
class Watcher {
 constructor(vm, expOrFn, cb, options, isRenderWatcher) {
   this.vm = vm; // 保存 Vue 实例
   if (isRenderWatcher) {
     vm._watcher = this; // 将当前 Watcher 设置为渲染 Watcher
   }
   vm._watchers.push(this); // 添加到 Vue 实例的 Watcher 列表中
   this.cb = cb; // 回调函数
   this.id = ++uid; // 唯一标识符
   this.deps = []; // 依赖列表
   this.newDeps = []; // 新依赖列表
   this.depIds = new Set(); // 依赖的唯一标识符集合
   this.newDepIds = new Set(); // 新依赖的唯一标识符集合
   this.expression = expOrFn.toString(); // 表达式
   this.getter = expOrFn; // 获取器函数
   this.value = this.get(); // 初始值
 }

 get() {
   pushTarget(this); // 将当前 Watcher 设置为 Dep.target
   let value = this.getter.call(this.vm); // 执行获取器函数
   popTarget(); // 恢复 Dep.target
   this.cleanupDeps(); // 清理旧的依赖
   return value
 }

 addDep(dep) {
   const id = dep.id;
   if (!this.newDepIds.has(id)) {
     this.newDepIds.add(id); // 添加新的依赖
     this.newDeps.push(dep); // 添加到新依赖列表
     if (!this.depIds.has(id)) {
       dep.addSub(this); // 添加订阅者
     }
   }
 }

 update() {
   this.run(); // 执行更新
 }

 run() {
   const value = this.get(); // 获取新值
   if (value !== this.value || isObject(value) || this.deep) {
     const oldValue = this.value; // 保存旧值
     this.value = value; // 更新值
     this.cb.call(this.vm, value, oldValue); // 调用回调函数
   }
 }
}
通知更新

通知更新是指在数据发生变化时,通知所有依赖于该数据的 Watcher,从而触发视图的更新。

  1. 数据变化:
    • 当响应式数据发生变化时,会触发 setter 方法。
    • setter 方法会调用 Dep 实例的 notify 方法,通知所有订阅者(即 Watcher 实例)。
  2. Watcher 更新:
    • 每个 Watcher 实例会在数据变化时被通知,并调用其 update 方法。
    • update 方法会调用 run 方法,重新计算依赖的数据,并触发组件的重新渲染。

http://www.kler.cn/news/363889.html

相关文章:

  • Vue项目的分页组件封装
  • U9的插件开发之BE插件(1)
  • windows|常见的文件伪装方法
  • qt项目使用其他项目的ui之单继承之成员变量
  • Linux定时器定时任务清理log日志文件
  • Kafka-Windows搭建全流程(环境,安装包,编译,消费案例,远程连接,服务自启,可视化工具)
  • 【力扣 | SQL题 | 每日4题】力扣1164,3293,1308,1270
  • 【scene_manager_msgs】ROS2 自定义消息、服务的包
  • 动态规划:17.简单多状态 dp 问题_买卖股票的最佳时机III_C++
  • OpenCV高级图形用户界面(17)设置一个已经创建的滚动条的最小值函数setTrackbarMin()的使用
  • 七、高级查询和数据操作及数据完整性和约束
  • 基于Linux来讲解Kconfig的基础知识
  • 【2024版】sql-liabs靶场前十关解题过程和思路----适合入门小白
  • Appium环境搭建全流程(含软件)
  • Java项目-基于springboot框架的社区疫情防控平台系统项目实战(附源码+文档)
  • React 纯手写一个 Modal 组件,除了样式不太美观以外,其他功能都不错呢?附上全部源码
  • vscode ssh连接远程服务器一直卡在正在打开远程
  • linux,socket编程,select,poll,epoll学习
  • MATLAB基础应用精讲-【数模应用】负二项回归(附R语言和python代码实现)
  • OpenCV高级图形用户界面(16)设置一个已经创建的滚动条的最大值函数setTrackbarMax()的使用
  • 【跑酷项目02】实现触发并在前方克隆金币
  • 编辑器加载与AB包加载组合
  • SQL注入原理、类型、危害与防御
  • 使用cmdline-tools安装Android SDK与NDK
  • 驱动开发系列20 - Linux Graphics Xorg-server 介绍
  • 在 Python 中使用 Tensorflow 时出错:google.protobuf