logo

从零实现响应式系统:新手手写Reactive的完整指南

作者:新兰2025.09.19 12:47浏览量:0

简介:本文通过分步骤讲解,帮助新手理解响应式原理并手写实现,涵盖Observer、Dep、Watcher等核心模块,提供可运行的代码示例和调试技巧。

一、响应式系统的核心原理

响应式系统的本质是建立数据变化与视图更新的自动关联机制。Vue2.x采用的Object.defineProperty和Vue3.x的Proxy方案,核心都是通过劫持数据访问实现依赖收集和派发更新。对于新手而言,理解这个闭环过程比直接使用框架更重要。

1.1 依赖收集与派发更新

当访问data.name时,系统需要:

  1. 记录当前正在计算的watcher(如模板渲染watcher)
  2. 在name值改变时,通知所有关联的watcher重新执行

这种机制通过三个核心角色实现:

  • Observer:将数据对象转换为可观察对象
  • Dep:依赖管理器,维护watcher列表
  • Watcher:观察者,执行具体更新逻辑

二、手写Reactive系统实现步骤

2.1 基础Observer实现

  1. class Observer {
  2. constructor(value) {
  3. this.value = value;
  4. this.walk(value);
  5. }
  6. walk(obj) {
  7. Object.keys(obj).forEach(key => {
  8. defineReactive(obj, key, obj[key]);
  9. });
  10. }
  11. }
  12. function defineReactive(obj, key, val) {
  13. const dep = new Dep(); // 创建依赖收集器
  14. Object.defineProperty(obj, key, {
  15. enumerable: true,
  16. configurable: true,
  17. get() {
  18. if (Dep.target) {
  19. dep.depend(); // 收集依赖
  20. }
  21. return val;
  22. },
  23. set(newVal) {
  24. if (newVal === val) return;
  25. val = newVal;
  26. dep.notify(); // 通知更新
  27. }
  28. });
  29. }

2.2 Dep依赖管理器实现

  1. let targetStack = [];
  2. class Dep {
  3. constructor() {
  4. this.subscribers = new Set();
  5. }
  6. depend() {
  7. if (Dep.target) {
  8. this.subscribers.add(Dep.target);
  9. }
  10. }
  11. notify() {
  12. this.subscribers.forEach(sub => sub.update());
  13. }
  14. }
  15. Dep.target = null;
  16. function pushTarget(target) {
  17. targetStack.push(target);
  18. Dep.target = target;
  19. }
  20. function popTarget() {
  21. targetStack.pop();
  22. Dep.target = targetStack[targetStack.length - 1];
  23. }

2.3 Watcher观察者实现

  1. class Watcher {
  2. constructor(vm, expOrFn, cb) {
  3. this.vm = vm;
  4. this.getter = parsePath(expOrFn);
  5. this.cb = cb;
  6. this.value = this.get();
  7. }
  8. get() {
  9. pushTarget(this);
  10. const value = this.getter.call(this.vm, this.vm);
  11. popTarget();
  12. return value;
  13. }
  14. update() {
  15. const oldValue = this.value;
  16. this.value = this.get();
  17. this.cb.call(this.vm, this.value, oldValue);
  18. }
  19. }
  20. function parsePath(path) {
  21. const segments = path.split('.');
  22. return function(obj) {
  23. for (let i = 0; i < segments.length; i++) {
  24. if (!obj) return;
  25. obj = obj[segments[i]];
  26. }
  27. return obj;
  28. };
  29. }

2.4 完整初始化流程

  1. function observe(data) {
  2. if (!data || typeof data !== 'object') {
  3. return;
  4. }
  5. return new Observer(data);
  6. }
  7. class Vue {
  8. constructor(options) {
  9. this._data = options.data;
  10. observe(this._data);
  11. new Watcher(this, 'a', (newVal) => {
  12. console.log('a changed:', newVal);
  13. });
  14. }
  15. }
  16. // 测试用例
  17. const vm = new Vue({
  18. data: {
  19. a: 1
  20. }
  21. });
  22. // 触发更新
  23. vm._data.a = 2; // 控制台输出: a changed: 2

三、新手常见问题解决方案

3.1 嵌套对象处理

原始实现无法处理嵌套对象,需要递归观察:

  1. function observe(value) {
  2. if (!value || typeof value !== 'object') {
  3. return;
  4. }
  5. return new Observer(value);
  6. }
  7. class Observer {
  8. constructor(value) {
  9. this.value = value;
  10. if (Array.isArray(value)) {
  11. // 数组特殊处理
  12. } else {
  13. this.walk(value);
  14. }
  15. }
  16. walk(obj) {
  17. Object.keys(obj).forEach(key => {
  18. defineReactive(obj, key, obj[key]);
  19. observe(obj[key]); // 递归观察
  20. });
  21. }
  22. }

3.2 数组变化检测

数组需要重写变异方法:

  1. const arrayProto = Array.prototype;
  2. const arrayMethods = Object.create(arrayProto);
  3. ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  4. const original = arrayProto[method];
  5. Object.defineProperty(arrayMethods, method, {
  6. value: function(...args) {
  7. const result = original.apply(this, args);
  8. const ob = this.__ob__;
  9. // 触发依赖更新
  10. ob.dep.notify();
  11. return result;
  12. },
  13. enumerable: false
  14. });
  15. });

四、性能优化技巧

  1. 批量更新:使用异步队列合并更新

    1. class Watcher {
    2. constructor(...) {
    3. this.pending = false;
    4. this.queue = [];
    5. }
    6. update() {
    7. if (this.pending) return;
    8. this.pending = true;
    9. nextTick(this.flush.bind(this));
    10. }
    11. flush() {
    12. this.pending = false;
    13. this.queue.forEach(watcher => watcher.run());
    14. this.queue = [];
    15. }
    16. }
  2. 懒执行:首次渲染不触发更新

    1. class Watcher {
    2. constructor(...) {
    3. this.dirty = true; // 标记脏数据
    4. }
    5. get() {
    6. if (this.dirty) {
    7. this.value = this.getter.call(this.vm, this.vm);
    8. this.dirty = false;
    9. }
    10. return this.value;
    11. }
    12. update() {
    13. this.dirty = true; // 标记为需要更新
    14. }
    15. }

五、调试与验证方法

  1. 依赖可视化:在Dep类中添加调试信息

    1. class Dep {
    2. constructor() {
    3. this.subscribers = new Set();
    4. this.id = Math.random().toString(36).substr(2);
    5. }
    6. depend() {
    7. if (Dep.target) {
    8. console.log(`Dep ${this.id} collecting ${Dep.target.id}`);
    9. this.subscribers.add(Dep.target);
    10. }
    11. }
    12. }
  2. 更新验证:添加更新计数器

    1. class Watcher {
    2. constructor(...) {
    3. this.updateCount = 0;
    4. }
    5. update() {
    6. this.updateCount++;
    7. console.log(`Watcher ${this.id} updated ${this.updateCount} times`);
    8. // ...原有逻辑
    9. }
    10. }

六、进阶方向建议

  1. Proxy方案:对比Object.defineProperty的实现差异

    1. function reactive(obj) {
    2. return new Proxy(obj, {
    3. get(target, key, receiver) {
    4. const result = Reflect.get(target, key, receiver);
    5. track(target, key); // 依赖收集
    6. return isObject(result) ? reactive(result) : result;
    7. },
    8. set(target, key, value, receiver) {
    9. const oldValue = target[key];
    10. const result = Reflect.set(target, key, value, receiver);
    11. if (oldValue !== value) {
    12. trigger(target, key); // 触发更新
    13. }
    14. return result;
    15. }
    16. });
    17. }
  2. 计算属性实现:添加缓存机制

    1. class ComputedWatcher extends Watcher {
    2. constructor(vm, getter) {
    3. super(vm, getter, () => {});
    4. this.dirty = true;
    5. this.value = undefined;
    6. }
    7. evaluate() {
    8. this.value = this.get();
    9. this.dirty = false;
    10. return this.value;
    11. }
    12. depend() {
    13. this.getters.forEach(getter => getter.depend());
    14. }
    15. }

通过系统化的实现和调试,新手可以深入理解响应式原理。建议从简单对象开始,逐步添加数组支持、性能优化等特性。完整实现约需300行代码,但核心机制在最初的50行中已体现。实际开发中,推荐基于成熟框架二次开发,而非完全从零实现。

相关文章推荐

发表评论