Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object.defineProperty 是否可以监听拦截数组变化【热度: 144】 #1066

Open
yanlele opened this issue Nov 22, 2024 · 0 comments
Open
Labels
JavaScript JavaScript 语法部分 小米 公司标签
Milestone

Comments

@yanlele
Copy link
Member

yanlele commented Nov 22, 2024

关键词:Object.defineProperty 监听数组变化

  1. 基本原理与部分可行性

    • Object.defineProperty可以用于监听和拦截数组的某些变化,但不是原生地对所有数组操作都能很好地监听。
    • 数组在 JavaScript 中是特殊的对象,其索引可以看作是对象属性。理论上,我们可以使用Object.defineProperty为数组的每个索引(属性)定义属性描述符,以此来尝试监听数组元素的读取和设置操作。
    • 例如,对于一个简单的数组元素设置操作,可以这样定义:
      let arr = [1, 2, 3];
      Object.defineProperty(arr, "0", {
        get: function () {
          console.log("读取索引为0的元素");
          return arr[0];
        },
        set: function (value) {
          console.log("设置索引为0的元素");
          arr[0] = value;
        },
      });
      • 当通过arr[0]读取或设置元素时,相应的getset函数会被触发,从而实现对这个特定索引元素的变化监听。
  2. 局限性

    • 无法自动监听所有元素:这种方式需要为每个要监听的索引单独使用Object.defineProperty进行定义。如果数组长度是动态变化的,或者要监听整个数组,这种逐个定义的方式就非常繁琐且不实用。例如,对于一个有很多元素的数组或者长度会不断变化的数组,几乎不可能预先为每个可能的索引都定义属性描述符。
    • 无法直接监听数组方法:它不能直接监听数组的方法(如pushpopshiftunshiftsplice等)引起的变化。这些方法会改变数组的状态,但不会触发通过Object.defineProperty为数组元素定义的getset操作。比如,当使用push方法添加元素到数组时,不会自动触发之前为数组元素定义的set操作来监听这个新元素的添加。
  3. 解决方案 - 重写数组方法实现全面监听

以下是使用Object.defineProperty来实现监听数组部分常见操作(如修改元素、添加元素、删除元素等)的基本思路和示例代码:

3.1. 整体思路

要使用Object.defineProperty监听数组,主要思路是对数组的原型方法进行重定义,在这些重定义的方法内部,通过Object.defineProperty来设置属性描述符,使得在执行这些操作时能够触发自定义的监听函数,从而实现对数组变化的监听。

3.2. 具体步骤及示例代码

(1)创建一个继承自原生数组的新类

首先,创建一个新的类,让它继承自原生数组,以便后续可以在这个新类上添加自定义的监听逻辑。

function ObservableArray() {
  // 调用原生数组构造函数,确保可以像正常数组一样使用
  Array.apply(this, arguments);
}
ObservableArray.prototype = Object.create(Array.prototype);
ObservableArray.prototype.constructor = ObservableArray;

(2)重定义数组的部分原型方法

接下来,重定义数组的一些常见操作的原型方法,比如pushpopshiftunshiftsplice等,在这些重定义的方法内部添加监听逻辑。

push方法为例:

ObservableArray.prototype.push = function () {
  // 保存当前数组长度,用于后续判断添加了几个元素
  var previousLength = this.length;

  // 调用原生数组的push方法,执行实际的添加操作
  var result = Array.prototype.push.apply(this, arguments);

  // 遍历新添加的元素,为每个元素设置属性描述符以实现监听
  for (var i = previousLength; i < this.length; i++) {
    (function (index) {
      Object.defineProperty(this, index, {
        enumerable: true,
        configurable: true,
        get: function () {
          console.log("正在读取索引为" + index + "的元素");
          return this[index];
        },
        set: function (value) {
          console.log("正在设置索引为" + index + "的元素为" + value);
          this[index] = value;
        },
      });
    }).call(this, i);
  }

  console.log("执行了push操作,添加了" + (this.length - previousLength) + "个元素");

  return result;
};

在上述push方法的重定义中:

  • 首先调用原生数组的push方法来执行实际的添加元素操作,并保存添加前的数组长度。
  • 然后,通过循环为新添加的每个元素使用Object.defineProperty设置属性描述符。在get方法中,当读取该元素时会打印相应信息;在set方法中,当设置该元素时也会打印相应信息。
  • 最后,打印出执行push操作添加的元素个数。

类似地,可以重定义其他如popshiftunshiftsplice等方法,以下是pop方法的重定义示例:

ObservableArray.prototype.pop = function () {
  var result = Array.prototype.pop.apply(this, arguments);

  if (this.length >= 0) {
    Object.defineProperty(this, this.length, {
      enumerable: true,
      configurable: true,
      get: function () {
        console.log("正在读取最后一个元素");
        return this[this.length];
      },
      set: function (value) {
        console.log("正在设置最后一个元素为" + value);
        this[this.length] = value;
      },
    });
  }

  console.log("执行了pop操作");

  return result;
};

3.3. 使用示例

创建ObservableArray的实例并进行操作来测试监听效果:

var myArray = new ObservableArray(1, 2, 3);

myArray.push(4, 5);
var poppedElement = myArray.pop();
myArray[0] = 10;

在上述示例中:

  • 首先创建了一个ObservableArray实例myArray并初始化为[1, 2, 3]
  • 然后执行myArray.push(4, 5),此时会触发重定义的push方法,添加元素的同时会为新添加的元素设置监听逻辑,并且会打印出相关操作信息。
  • 接着执行myArray.pop(),触发重定义的pop方法,执行弹出操作并设置对最后一个元素的监听逻辑,同时打印出相关操作信息。
  • 最后执行myArray[0] = 10,由于之前为数组元素设置了监听逻辑(在push方法中对新添加元素设置了监听),所以会触发相应的set逻辑,打印出相关信息。
@yanlele yanlele added JavaScript JavaScript 语法部分 小米 公司标签 labels Nov 22, 2024
@yanlele yanlele added this to the milestone Nov 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript JavaScript 语法部分 小米 公司标签
Projects
None yet
Development

No branches or pull requests

1 participant