删库在逃程序员的Blog

浅谈 Vue2 中 computed 的工作原理

author
·
12
0

浅谈 Vue2 中 computed 的工作原理

通过实现一个简化版的 Vue computed,深入理解其响应式原理。

📌 前言

Vue 的 computed 计算属性是日常开发中的常用功能,但它是如何工作的?本文将通过手写一个简化版实现,带你深入理解 computed 的核心原理。

一、理解 Object.defineProperty

首先,我们需要了解 Object.defineProperty 这个核心 API:

const person = {};
Object.defineProperty(person, 'age', {
  get: function() {
    console.log('person get age');
    return 18;
  }
});
console.log('This person is', person.age);
// 输出:person get age → This person is 18

关键点:访问 person.age 时,实际执行的是 get 函数,而非直接读取属性值。这为响应式系统奠定了基础。

二、实现基础响应式系统

Vue 内部通过类似方式将普通对象转化为响应式对象

function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get: function() {
      return val;
    },
    set: function(newValue) {
      val = newValue;
    }
  });
}

const person = {};
defineReactive(person, 'age', 25);
defineReactive(person, 'country', 'Brazil');

// 正常使用
if (person.age < 18) {
  console.log('small');
} else {
  console.log('big');  // 输出:big
}

// 响应式更新
person.country = 'Russia';  // val 被更新

核心思想person.age 并不直接存储值,而是通过闭包中的 val 变量和 getter/setter 函数来管理。

三、实现计算属性

基于响应式系统,我们可以实现一个简化版的 defineComputed

function defineComputed(obj, key, computeFunc, updateCallback) {
  Object.defineProperty(obj, key, {
    get: function() {
      return computeFunc();
    },
    set: function(newValue) {
      // 暂不处理
    }
  });
}

// 使用示例
defineComputed(
  person,
  'status',
  function() {
    if (person.age > 18) {
      return 'big';
    }
    return 'small';
  },
  function(newValue) {
    console.log('status has changed to', newValue);
  }
);

⚠️ 存在的问题

上述实现有两个缺陷:

  1. 重复计算:每次访问计算属性都会执行 computeFunc()
  2. 无法响应依赖变化:当 person.age 更新时,person.status 不会自动更新

四、引入依赖收集机制

为解决上述问题,我们需要一个依赖收集器 Dep

const Dep = {
  target: null  // 当前正在执行的计算属性回调
};

4.1 更新 defineComputed

function defineComputed(obj, key, computeFunc, updateCallback) {
  Object.defineProperty(obj, key, {
    get: function() {
      Dep.target = onDependencyUpdated;
      const value = computeFunc();  // 触发所有依赖属性的 get
      Dep.target = null;
      return value;
    },
    set: function(newValue) {
      updateCallback(newValue);
    }
  });
}

4.2 更新 defineReactive

在响应式属性的 getter 中收集依赖:

function defineReactive(obj, key, val) {
  const deps = [];  // 存储依赖此属性的所有计算属性回调

  Object.defineProperty(obj, key, {
    get: function() {
      if (Dep.target) {
        deps.push(Dep.target);  // 收集依赖
      }
      return val;
    },
    set: function(newValue) {
      val = newValue;
      deps.forEach(computedCallback => computedCallback());  // 通知所有依赖更新
    }
  });
}

4.3 实现依赖更新回调

function onDependencyUpdated() {
  const value = this?.computeFunc();
  this?.updateCallback(value);
}

function defineComputed(obj, key, computeFunc, updateCallback) {
  Object.defineProperty(obj, key, {
    get: function() {
      Dep.target = onDependencyUpdated.bind({
        __key__: key,
        computeFunc,
        updateCallback
      });
      const value = computeFunc();
      Dep.target = null;
      return value;
    },
    set: function(newValue) {
      updateCallback(newValue);
    }
  });
}

五、完整实现代码

const Dep = {
  target: null
};

function defineReactive(obj, key, val) {
  const deps = [];
  Object.defineProperty(obj, key, {
    get: function() {
      if (Dep.target) {
        deps.push(Dep.target);
      }
      return val;
    },
    set: function(newValue) {
      val = newValue;
      deps.forEach(computedCallback => computedCallback());
    }
  });
}

function onDependencyUpdated() {
  const value = this?.computeFunc();
  this?.updateCallback(value);
}

function defineComputed(obj, key, computeFunc, updateCallback) {
  Object.defineProperty(obj, key, {
    get: function() {
      Dep.target = onDependencyUpdated.bind({
        __key__: key,
        computeFunc,
        updateCallback
      });
      const value = computeFunc();
      Dep.target = null;
      return value;
    },
    set: function(newValue) {
      updateCallback(newValue);
    }
  });
}

// ===== 使用示例 =====
const person = {};
defineReactive(person, 'age', 17);
defineReactive(person, 'country', 'Brazil');

defineComputed(
  person,
  'status',
  function() {
    if (person.age > 18) {
      return 'big';
    }
    return 'small';
  },
  function(newValue) {
    console.log('status has changed to', newValue);
  }
);

console.log(person.status);  // 输出:small
person.age = 22;  // 自动触发:status has changed to big

六、核心原理总结

概念 作用
Object.defineProperty 拦截属性的 get/set 操作
Dep.target 全局标记当前正在执行的计算属性回调
deps 数组 存储依赖某响应式属性的所有计算属性回调
依赖收集 在 getter 中将 Dep.target 推入 deps 数组
派发更新 在 setter 中遍历 deps 数组,执行所有回调

参考文档深入解析 Vue 中的 computed 工作原理

评论 (0)