首页/文章/八股文

Object.defineProperty 是否可以监听拦截数组变化

2024-12-07
12435 分钟
...

关键词: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逻辑,打印出相关信息。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
八股文2026-08-27
定时器按顺序播放多个音频,切后台音频会乱
本身不会补触发,但 它会被系统级冻结 ——iOS Safari 后台完全停止计时,切回前台后 只补执行一次 (不是不补,而是"缺失的中间状态补不上")。间隔短的不会出大问题, 间隔长的会出现"音频流被压缩" ——切回来后本该播 5 分钟的间隙,实际只过了 3 分钟,结果整段对不上。 核心原则 :定时器只能用来"提醒一次", 真正决定"该不该播"的是绝对时间戳 。 setTimeout 在后台会发生什么(按平台) | 平台 | 后台行为...
面试
八股文2026-08-27
线上项目白屏的原因
白屏的本质是 渲染管线某一环断了 ——可能是资源、JS、接口、路由、样式、兼容性任一环节出问题。排查按"控制台 → 网络 → DOM → 环境"四步定位。 本质(前端类比) 把网页想成一栋楼: 白屏 ≠ 一定是同一种原因——这是面试想听的层次。 六大类原因(按出现频率) | 类别 | 典型表现 | 真实案例 | | : | : | : | | ① 资源加载失败 | DOM 是空的 | 入口 JS 404、CDN 挂了、CSS 阻塞 |...
面试
八股文2026-08-27
背景图就是 1MB 大图,怎么优化
1MB 大背景图优化分 三步 :压缩体积(10 30x)、按需加载(按设备/视口/网速)、渲染期优化(GPU 合成)。背景图跟 不同——它是 CSS,不走浏览器的原生懒加载机制,得手动处理。 背景图 vs 的关键区别(先讲清楚这个) 这是面试官想听的"针对性认知"——背景图不是普通图片,不能套通用方案。 三步优化(按优先级) 第 1 步:压缩体积(最重要,立竿见影) 1MB 的来源一般是这几种 ,对应解决方案: | 原始问题 | 体积来...
面试
八股文2026-08-27
项目里很多图片和视频,怎么优化
图片视频优化分 四层 :网络层(CDN/格式)、加载层(懒加载/预加载)、渲染层(解码/缓存)、业务层(按需/降级)。面试要把这四层都讲到位才算有体系。 四层优化模型 第 1 层:网络层(省钱、省时间) 核心目标:让资源体积小、让用户拿到资源快 | 手段 | 作用 | 关键点 | | : | : | : | | 图片格式 | WebP/AVIF 比 JPEG 小 25 50% | 兼容 fallback | | 视频格式 | H.265...
面试
八股文2026-08-20
Embedding 向量模型:从语义表示到相似度计算
前言 大模型「读懂」文字靠的是 token,但 token 之间只有离散的编号关系,模型并不知道「苹果」和「李子」在语义上很近。要让程序能「理解」两段文字的相似程度,必须先把文本映射成一个 高维向量 ,再用几何方法比较。这一步就是 Embedding。 本篇基于我本地 今天的真实代码,从语义表示讲到余弦相似度,并复盘几个踩过的真实坑。 一、为什么需要 Embedding 传统关键词检索是「字面匹配」: Embedding 做的是「语义匹...
面试
八股文2026-08-19
前端面试100题
前端面试 100 题 适用方向:中高级前端 / React / Vue / Next.js / Nuxt / TypeScript / 工程化 / 实时通信 / Electron / Node.js / AI 应用前端 使用方式:优先掌握“标准回答”,再练“面试官追问”,最后把“结合你的简历怎么答”组织成自己的项目故事。 说明 “结合你的简历怎么答”只使用你简历中已经出现的项目与技术事实。 “标准回答 / 追问”属于通用前端知识总结,用...
面试

评论

请登录后发表评论

去登录
加载评论中...