从零开始撸一个「响应式」框架

lxf2023-03-11 11:50:02

背景

一切的一切都要从一场惨无人道的面试开始说起。年少的小李怀揣着对职场的憧憬,悻然的来到一家高大上的互联网公司面试求职,他所面试的岗位是「前端开发工程师」。面试前小李已经熟读前端八股文、react 和 vue 的各种大法,然而他不知道的是,一个阴谋的老油条面试官「老余」将要摧残这朵即将踏上职场的小花骨朵。

初入江湖

面试官「老余」看了看小李的简历说道:『看你的项目简历,你对 react 和 vue 比较熟悉,那这样吧,你手写一个响应式,满足以下条件』

var obj = { a: 1 };

var fn = () => {
  console.log(obj.a);
}

// 实现目标:当 obj.a 改变时 fn 函数自动执行

看到这个面试题,小李内心十分开心,这不就是网上经常可以搜到的面试题吗。核心点就是 Object.defineProperty这个 API,它能够拦截对象get、set操作

Object.defineProperty 可以拦截对象的 set 和 get 操作,支持三个参数

  • target 源对象
  • key 需要操作的 key
  • config
    • get 、 set 拦截器
    • value 属性值
    • enumerable 是否可枚举
    • writeable 是否可修改属性的值
    • configurable 是否可删除、可修改属性特性

于是小李三两下就完成了以下的 coding

let value;
const reacitve = (obj) => {
  value = obj.a;

  Object.defineProperty(obj, "a", {
    get: () => {
      return value;
    },
    set: (val) => {
      value = val;
      fn();
    }
  });

  return obj;
};

// 测试
var obj = reacitve({ a: 1 });
var fn = () => {
  console.log(obj.a);
};

fn();

obj.a = 2;

问题百出

面试官「老余」看着小李写完的代码,不经眉头一皱,十分生气的说道:『你写的什么乱七八糟的东西,为什么 set 拦截器里写死了执行 fn,而且我需要支持所有属性都能响应式。你令我有点失望,重新写吧』。小李重新审视了下题目,的确发现问题百出。针对面试官的第二个问题(支持所有属性都能响应式),直接遍历就可以了。但是第一个问题(如何何知道哪个函数依赖当前对象)该如何解决呢?
这时小李灵光一闪 JS 是单线程的,也就是说同一时间只可能有一个函数在运行,所以我只要给函数封装下就可以了,于是便有了下面的代码