You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript中赋值与defineProperty定义Object原型方法的差异

问题:扩展Object.prototype的两种方式为何引发不同结果?

我编写了一个用于移除对象中undefined值的TypeScript函数:

export const removeUndefinedValues: (obj: Record<PropertyKey, any>) => Record<PropertyKey, any> = (obj: Record<PropertyKey, any>) => {
    if (obj) {
        Object.keys(obj).forEach((key: PropertyKey) => {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                if (obj[key] === undefined) {
                    console.log(`Removing property ${key.toString()} with value ${obj[key]}`)
                    delete obj[key]
                }
            }
        })
    }
    return obj
}

最初我通过直接赋值的方式将该方法添加到Object.prototype:

Object.prototype.removeUndefinedValues = function () {
    return removeUndefinedValues(this)
}

这种方式在简单场景下有效,但在大型代码库中使用时,导致browserslist包中的部分内容变为undefined。改用Object.defineProperty方式后问题解决:

Object.defineProperty(Object.prototype, 'removeUndefinedValues', {
    value: function () {
        return removeUndefinedValues(this);
    },
    enumerable: false
});

想了解这两种方式的差异,以及为何第二种方式可行而第一种不行?


原因分析

1. 核心差异:属性的枚举性

  • 直接赋值方式:给Object.prototype添加的属性,默认是**可枚举(enumerable: true)**的。这意味着这个属性会被for...in循环、依赖原型链遍历的逻辑等枚举操作扫描到。
  • Object.defineProperty方式:显式设置了enumerable: false,这个属性会被标记为不可枚举,不会出现在任何枚举遍历的结果中。

2. 与browserslist库的冲突点

browserslist这类工具库在处理内部配置或数据时,大概率会用到for...in循环或者依赖对象枚举行为的逻辑来遍历属性。当你用第一种方式添加removeUndefinedValues方法时:

  • 这个方法会作为原型链上的可枚举属性,被库的遍历逻辑“误捕获”;
  • 库的代码可能会把这个额外的方法当成业务属性处理,打乱原有数据结构的遍历顺序、长度判断,甚至触发错误的赋值/删除逻辑,最终导致部分内容变为undefined。

而第二种方式中,因为方法被设置为不可枚举,库的遍历逻辑只会处理对象自身的业务属性,完全不会受到你添加的原型方法干扰,因此问题得以解决。

3. 扩展原生原型的最佳实践

扩展Object.prototype这类原生对象原型时,始终推荐使用Object.defineProperty并设置enumerable: false——这是避免枚举冲突、防止干扰第三方库或原生逻辑的标准做法。


内容的提问来源于stack exchange,提问作者Willem Vanhulle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 02:06:26