前端Vue篇之Vue是如何收集依赖的?

目录

    • Vue是如何收集依赖的?
    • [1. `Dep` 类](#1. Dep 类)
      • [1. 静态属性 `target`](#1. 静态属性 target)
      • [2. subs 属性](#2. subs 属性)
      • [3. 方法:addSub 和 removeSub](#3. 方法:addSub 和 removeSub)
      • [4. 方法:depend 和 notify](#4. 方法:depend 和 notify)
      • 案例需求
      • 案例需求
      • 总结
    • [2. `Watcher` 类](#2. Watcher 类)
      • 构造函数
      • [get 方法](#get 方法)
      • [addDep 方法](#addDep 方法)
      • [pushTarget 函数](#pushTarget 函数)
      • 总结
    • [3. `Vue` 类](#3. Vue 类)
      • [updateComponent 函数](#updateComponent 函数)
      • [创建 Watcher](#创建 Watcher)
      • 总结

Vue是如何收集依赖的?

在 Vue 中,依赖收集是一个重要的概念,它是 Vue 实现响应式数据的核心。当一个组件被初始化时,Vue 会对组件的 data 进行初始化,将普通的 JavaScript 对象变成响应式对象。在这个过程中,Vue 会进行依赖收集,以便在数据发生变化时,能够通知到所有依赖这个数据的地方。

依赖收集的过程主要涉及到三个类:Dep、Watcher 和 Vue。

1. Dep 类

Dep 是依赖收集的核心,它的主要作用是管理所有的 Watcher。Dep 类中有一个静态属性 target,它指向当前正在计算的 Watcher,保证了同一时间全局只有一个 Watcher 被计算。Dep 类中还有一个 subs 属性,它是一个 Watcher 的数组,用来存储所有依赖这个 Dep 的 Watcher。

1. 静态属性 target

Dep 类中有一个静态属性 target。这个属性的作用是指向当前正在计算的 Watcher,它的存在保证了在同一时间内全局只有一个 Watcher 被计算。你可以把它想象成一个焦点,告诉系统当前需要关注哪个 Watcher。

2. subs 属性

另一个属性是 subs,它是一个存储 Watcher 的数组。这意味着 Dep 类实际上就是对 Watcher 的管理者。当数据发生变化时,Dep 就会通知所有依赖于它的 Watcher 进行更新,而这些 Watcher 则会负责相应的视图更新。

3. 方法:addSub 和 removeSub

addSub 方法用于将 Watcher 添加到 subs 数组中,而 removeSub 方法则是用来从 subs 数组中移除对应的 Watcher。

4. 方法:depend 和 notify

  • depend 方法的作用是在计算属性或者渲染过程中建立依赖关系。如果当前存在正在计算的 Watcher(即 Dep.target 存在),那么就会将当前的 Dep 与该 Watcher 建立关联。
  • notify 方法则是在数据发生变化时通知所有依赖这个 Dep 的 Watcher 执行更新操作。它遍历 subs 数组中的所有 Watcher,并逐一触发它们的更新方法。

在实际工作项目中,Dep 类的概念对于理解 Vue.js 的响应式原理和数据双向绑定机制至关重要。在大型前端应用中,了解依赖收集的过程以及如何管理依赖关系是非常有益的,尤其是在构建自定义组件或处理复杂的数据流时。

案例需求

假设有一个用户界面,其中包含一个数字输入框和一个显示框。输入框中的值会影响显示框中的内容。我们希望在输入框的值改变时,显示框能够自动更新。

javascript 复制代码
class Dep {
  constructor() {
    this.subs = []; // 存储所有依赖这个Dep的Watcher
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this); // 将当前Dep与正在计算的Watcher关联起来
    }
  }

  notify() {
    this.subs.forEach(watcher => {
      watcher.update(); // 通知所有依赖这个Dep的Watcher进行更新
    });
  }
}

Dep.target = null; // 全局的当前正在计算的Watcher

class Watcher {
  constructor() {
    Dep.target = this;
  }

  addDep(dep) {
    dep.subs.push(this); // 将当前Watcher加入到Dep的subs数组中
  }

  update() {
    // 执行更新操作
  }
}

// 示例用法
let dep = new Dep();
let watcher = new Watcher();
dep.depend(); // 将当前Dep与正在计算的Watcher关联起来
dep.notify(); // 通知所有依赖这个Dep的Watcher进行更新
  1. 创建Dep类,其中包括subs数组和depend、notify方法。
  2. 创建Watcher类,其中包括addDep和update方法。
  3. 在Dep类中,通过target属性指向当前正在计算的Watcher,在depend方法中将当前Dep与正在计算的Watcher关联起来,在notify方法中通知所有依赖这个Dep的Watcher进行更新。
  4. 在Watcher类中,通过addDep方法将当前Watcher加入到Dep的subs数组中,在update方法中执行更新操作。
  • Dep类和Watcher类是数据响应式系统的核心,理解其原理对于理解Vue等前端框架的工作原理非常重要。
  • 在实际项目中,可以根据具体需求对Dep类和Watcher类进行定制和扩展,以满足不同的业务场景需求。

案例需求

假设我们有一个需求,希望在用户修改某个输入框的数值时,页面上的其他相关部分能够实时更新。这就需要利用 Vue.js 的响应式系统来实现数据的自动更新。在这种情况下,理解 Dep 类的作用将有助于我们更好地设计数据流和组件通信。

js 复制代码
<template>
  <div>
    <input v-model="value" @input="updateOtherSections" />
    <div>{{ calculatedValue }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      value: 0,
      calculatedValue: 0
    };
  },
  mounted() {
    this.$watch(
      () => this.value,
      () => {
        this.calculatedValue = this.value * 2; // 示例:当输入框数值改变时触发更新
      }
    );
  },
  methods: {
    updateOtherSections() {
      // 更新其他相关部分的逻辑
    }
  }
};
</script>
  1. 在 mounted 钩子中,通过 this.$watch 监听 value 的变化。
  2. 当 value 变化时,会触发相应的更新函数,计算新的值并更新到 calculatedValue。
  3. 用户输入框的值变化会引起 value 的变化,从而触发 calculatedValue 的更新。

总结

Dep 类通过管理依赖关系,用于管理所有的Watcher,确保了数据变化时相关的视图能够得到更新。它利用 subs 数组存储依赖它的 Watcher,并通过 notify 方法通知它们进行更新。同时,使用静态属性 target 确保了在同一时间内只有一个全局的 Watcher 被计算。通过理解 Dep 类的作用,我们可以更好地设计和构建具有复杂数据流的 Vue 组件,并更好地应对实际项目中的需求。

2. Watcher 类

Watcher 是一个用来计算表达式的类。在 Watcher 的构造函数中,它会执行表达式,这个表达式可能会触发数据的 getter,从而进行依赖收集。Watcher 类中还有一个 addDep 方法,它会将当前的 Watcher 添加到 Dep 的 subs 数组中。

javascript 复制代码
class Watcher {
  getter;
  ...
  constructor (vm, expression){
    ...
    this.getter = expression;
    this.get();
  }
  get () {
    pushTarget(this);
    value = this.getter.call(vm, vm)
    ...
    return value
  }
  addDep (dep){
        ...
    dep.addSub(this)
  }
  ...
}
function pushTarget (_target) {
  Dep.target = _target
}

构造函数

Watcher 类有一个构造函数,它接受两个参数:vm 和 expression。在构造函数中,将传入的 expression 赋值给 getter 属性,并立即调用 get 方法。

get 方法

  • get 方法是 Watcher 类中最关键的方法之一。当 Watcher 被创建时,会立即调用 get 方法。
  • 在 get 方法内部,会将当前的 Watcher 推入一个全局的位置(通过 pushTarget 函数),然后执行 expression,这个 expression 可能是一个函数或者计算属性的表达式。
  • 在执行过程中,会触发对应数据的 getter,从而建立起依赖关系,并且触发了依赖收集过程。

addDep 方法

  • addDep 方法用于将当前的 Watcher 添加到指定的 Dep 实例中,建立起 Watcher 和 Dep 之间的关联。
  • 这样,在数据变化时,Dep 就能够通知到相应的 Watcher 进行更新操作。

pushTarget 函数

pushTarget 函数的作用是将当前的 Watcher 设置为全局唯一的 Dep.target,以确保在同一时间内全局只有一个 Watcher 被计算。

  • 在 get 方法中,通过调用 expression 来触发数据的 getter,从而建立起依赖关系。
  • 使用 addDep 方法将当前的 Watcher 添加到对应的 Dep 实例中,确保在数据变化时能够及时通知到相关的 Watcher 进行更新操作。

总结

Watcher 类通过 get 方法建立了依赖关系,并且在初始化时会立即执行 expression,从而触发对应数据的 getter,进而进行依赖收集。通过 addDep 方法,Watcher 和 Dep 建立了关联,确保了数据变化时能够及时通知到相关的 Watcher 进行更新操作。

3. Vue 类

Vue 类是 Vue 的入口,它的主要作用是初始化 Vue 应用。在 Vue 类的初始化过程中,会对组件的 data 进行初始化,将普通的 JavaScript 对象变成响应式对象。在这个过程中,会进行依赖收集。

依赖收集的过程如下:

  1. 首先,Vue 会对组件的 data 进行初始化,将普通的 JavaScript 对象变成响应式对象。

  2. 然后,Vue 会实例化一个 Watcher,并执行 Watcher 的 get 方法。

  3. 在 get 方法中,Watcher 会执行表达式,这个表达式可能会触发数据的 getter,从而进行依赖收集。

  4. 在 getter 中,会调用 Dep.target.addDep(this),将当前的 Watcher 添加到数据的 Dep 的 subs 数组中。

  5. 这样,当数据发生变化时,Dep 就可以通过 subs 数组,找到所有依赖这个数据的 Watcher,并通知它们数据发生了变化。

这就是 Vue 如何进行依赖收集的过程。

javascript 复制代码
updateComponent = () => {
  vm._update(vm._render())
}
new Watcher(vm, updateComponent)

updateComponent 函数

updateComponent 函数是一个箭头函数,它的作用是更新 Vue 实例的视图。在这个函数内部,它调用了 vm._render() 方法生成虚拟 DOM,并将其传递给 vm._update() 方法来更新实际的 DOM。

创建 Watcher

通过 new Watcher(vm, updateComponent) 创建了一个新的 Watcher 实例。这里的 vm 是 Vue 实例,updateComponent 是上面定义的更新函数。

在创建 Watcher 实例时,会立即执行 updateComponent 函数,这样就建立了依赖关系,updateComponent 函数中所使用的数据发生变化时,对应的 Watcher 就能够收到通知,并触发更新操作。

  • 当创建 Watcher 实例时,会立即执行 updateComponent 函数,从而进行依赖收集并建立起对应的关联。
  • updateComponent 函数的执行将触发 Vue 实例的重新渲染过程,确保视图能够及时地响应数据的变化。

总结

通过创建 Watcher 实例,将 updateComponent 函数与 Vue 实例建立了关联,确保了当相关数据发生变化时,能够触发视图的更新操作。这种机制保证了 Vue 的响应式系统能够自动追踪数据的变化,并及时地更新视图,从而实现了数据驱动视图的效果。

持续学习总结记录中,回顾一下上面的内容:
Vue通过在数据属性的getter中收集依赖。当一个数据被访问时,Watcher会被添加到依赖列表中。这样,当数据变化时,Watcher就能够得到通知,并进行相应的更新操作,从而确保视图能够及时地反映数据的变化。

相关推荐
Setsuna_F_Seiei5 小时前
前端转型 Agent 开发 06 之 Agent Memory 记忆系统(让 Agent 更智能,更懂你)
前端·agent·ai编程
志尊宝5 小时前
Vue3 零基础每日笔记(073):keep-alive 页面缓存实战——列表页返回不丢状态
vue.js·笔记·缓存·#vue #前端 #前端开发·#javascript·#vue.js
zhangzeyuaaa6 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
默_笙6 小时前
🌊 向量库和 ES 都查不出"关系",我只好给奶茶建了一张人脉网
前端·javascript
优选资讯6 小时前
标签打印软件怎么对接 Excel 数据
前端·excel
一个风轻云淡7 小时前
GCC 和 GDB命令简单解读
java·linux·前端
u0111026758 小时前
图片处理接口如何统一错误信息 前端提示与后端错误码设计
前端·状态模式
福兮说9 小时前
浏览器里把 iPhone 的 HEIC 转成 JPG:Chrome 解不开、转完大了七成、拍摄时间全丢,六个坑实测
前端·javascript·图像处理·chrome·ios·iphone·heic
libokaifa9 小时前
把语音模型塞进手机:sherpa-onnx 在 Android 上的 ASR / TTS
前端
deli0079 小时前
DLA 枝晶生长实测:5000 个粒子集体停摆之后,分形维数收敛到 1.70
前端