当前位置:首页 > 文章列表 > 文章 > 前端 > 防止原型链扩展的3种方法

防止原型链扩展的3种方法

2025-07-30 14:03:31 0浏览 收藏

文章小白一枚,正在不断学习积累知识,现将学习到的知识记录一下,也是将我的所得分享给大家!而今天这篇文章《防止 JavaScript 原型链被扩展的方法有以下几种,适用于不同场景和需求:1. 使用 Object.defineProperty 设置不可变属性可以通过设置对象的原型属性为只读、不可配置或不可枚举,来防止其被修改。// 防止 Object.prototype 被修改 Object.defineProperty(Object.prototype, 'myMethod', { value: function() { console.log('My method'); }, writable: false, configurable: false, enumerable: false });这样可以防止 myMethod 被重新赋值或删除。2. 使用 Object.seal() 或 Object.freeze()Object.seal(obj):阻止添加新属性,但允许修改现有属性。Object.freeze(obj):阻止添加、删除、修改属性,是最严格的保护方式。const obj = {}; Object.freeze(obj); // 冻结对象 obj.newProp = 'test'; // 无法添加 obj.prop = 'changed'; // 无法修改 delete obj.prop; // 无法删除注意:这仅对对象本身有效,不包括原型链上的属性。3. 使用 __proto__ 的安全访问(现代浏览器)在支持 ES6 的环境中,可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 来》带大家来了解一下##content_title##,希望对大家的知识积累有所帮助,从而弥补自己的不足,助力实战开发!


不能完全阻止原型链扩展,但可通过Object.preventExtensions、Object.seal和Object.freeze限制对象自身及其原型的修改;2. 避免污染全局原型,应使用模块化、不直接修改内置原型,并用Object.prototype.hasOwnProperty.call进行属性检查;3. 运行时可通过检测原型属性、防御性编程和隔离高风险代码来应对原型链被意外修改;4. 安全添加共享方法应使用class语法或构造函数的prototype属性,避免触碰内置对象原型;5. 原型链被修改后应检测、规避影响,而非尝试撤销,因直接修复风险高且不可靠。

js如何阻止原型链的扩展

JavaScript中,我们很难说能够“阻止”原型链的“扩展”,尤其对于那些内置对象(比如Object.prototypeArray.prototype)。这有点像在说,我们能不能阻止一个开放的系统被添加新功能。JS的设计哲学就是高度灵活和可扩展的,原型链正是这种灵活性的核心。所以,我们更准确的说法应该是“如何限制或规避原型链被不当修改带来的风险”,或者“如何保护我们自己定义的对象的原型不被随意篡改”。这其实更多是关于代码健壮性、安全性和可维护性的问题。

js如何阻止原型链的扩展

解决方案

要限制或规避原型链被不当修改,我们可以从几个层面入手:

1. 保护特定对象本身及其直接原型链接

js如何阻止原型链的扩展

这是最直接的手段,通过JS内置的Object方法来限制一个对象的属性操作,这间接也影响了其原型链的“扩展性”,因为它限制了对象自身作为原型时能被修改的程度。

  • Object.preventExtensions(obj): 这个方法一旦调用,就不能再向obj添加新的属性了。但现有属性可以被删除或修改。如果你把一个对象作为另一个对象的原型,那么这个原型对象本身就不能再被“扩展”了。

    js如何阻止原型链的扩展
    const protoObj = {
      methodA: function() { console.log('A'); }
    };
    Object.preventExtensions(protoObj);
    
    // 尝试添加新属性,会失败(严格模式下抛错)
    // protoObj.newMethod = function() {}; // TypeError: Cannot add property newMethod, object is not extensible
  • Object.seal(obj): 比preventExtensions更进一步。除了不能添加新属性,也不能删除现有属性。但现有属性的值仍然可以被修改。

    const sealedProto = {
      value: 10,
      action: function() { console.log(this.value); }
    };
    Object.seal(sealedProto);
    
    // sealedProto.newValue = 20; // TypeError
    // delete sealedProto.value; // TypeError
    sealedProto.value = 100; // OK
  • Object.freeze(obj): 这是最严格的。一旦冻结,对象就完全不可变了:不能添加新属性,不能删除现有属性,也不能修改现有属性的值。如果一个对象被冻结,它作为原型时,其自身就无法被修改。

    const frozenProto = {
      constant: 'immutable',
      greet: function() { console.log(this.constant); }
    };
    Object.freeze(frozenProto);
    
    // frozenProto.newProp = 'test'; // TypeError
    // delete frozenProto.constant; // TypeError
    // frozenProto.constant = 'mutable'; // TypeError

    需要注意的是,这些方法都是“浅”操作,它们只作用于目标对象本身,如果目标对象内部有其他对象(比如嵌套对象或数组),那些内部对象仍然是可变的。

2. 避免污染全局原型

很多时候,问题不是出在“阻止”某个具体原型被扩展,而是避免我们自己或者第三方库“不小心”或“恶意”地扩展了像Object.prototypeArray.prototype这样的全局内置对象。

  • 模块化 (ESM/CommonJS):这是现代JS开发的基础。通过模块化,每个文件(模块)都有自己的作用域,避免了全局变量和原型被随意修改。你的代码只影响你自己的模块,而不是全局环境。
  • 避免直接修改内置原型: 这是一个约定俗成的最佳实践。除非你真的知道自己在做什么,并且有充分的理由(通常是polyfill),否则不要直接给Object.prototypeArray.prototype添加方法。
  • 使用Object.prototype.hasOwnProperty.call(): 在遍历对象属性时,始终使用这个方法来检查属性是否是对象自身的,而不是来自原型链。这能有效防止原型链上的可枚举属性(比如一些旧库添加的)干扰你的逻辑。
    for (let key in someObject) {
      if (Object.prototype.hasOwnProperty.call(someObject, key)) {
        // 这是对象自身的属性
        console.log(key, someObject[key]);
      }
    }

3. 运行时检测与防御

如果你怀疑原型链被不当修改,或者想在运行时进行防御,可以做一些检查。

  • 审查第三方库: 在引入第三方库时,留意它们的文档,看是否有修改内置原型的说明。
  • 快照测试: 在自动化测试中,可以对关键的原型对象(如Object.prototype)在加载前后进行快照,对比是否有意外的属性增加。

为什么我们不应该随意扩展JS内置对象的原型链?

这是一个老生常谈但又极其重要的问题。在我看来,随意扩展内置对象的原型链,简直就是给自己挖坑,而且这个坑可能深不见底。

首先,命名冲突和覆盖。你给Array.prototype加了个myCustomMethod,万一将来JS标准也出了个同名方法,或者另一个第三方库也加了个同名但行为不同的方法,那你的代码就会出现难以预料的行为。是你的方法被覆盖了?还是你覆盖了别人的?这简直就是噩梦。

其次,非标准行为和可维护性。你的代码在你的环境里跑得好好的,因为你改了原型。但换个环境,或者JS引擎更新了,可能就出问题了。这让代码变得难以移植,也让后来的维护者摸不着头脑:“这个Array怎么会有个foo方法?!”这大大降低了代码的可读性和可维护性。

再者,性能影响。虽然现代JS引擎对原型链的优化已经很好了,但在某些极端情况下,过长或被频繁修改的原型链仍然可能带来轻微的性能开销,尤其是在进行属性查找时。这有点像你找东西,如果东西被放在一个特别乱、层层叠叠的柜子里,肯定比放在整齐划一的抽屉里慢。

最后,也是最关键的,安全隐患——原型链污染攻击 (Prototype Pollution)。这是一种非常危险的漏洞。如果攻击者能够通过某种方式(比如JSON解析、数据合并操作等)向Object.prototype注入属性,那么理论上,所有继承自Object.prototype的对象都会“继承”这个被注入的属性。这可能导致远程代码执行、拒绝服务等严重的安全问题。想象一下,如果攻击者能给Object.prototype添加一个isAdmin: true的属性,那你的所有用户对象可能都会被误判为管理员!

所以,出于稳定性、可维护性、兼容性和安全性的考虑,我们应该极力避免直接修改内置对象的原型链。

如何安全地为自定义对象添加共享方法?

当我们谈到为自定义对象添加共享方法时,现代JavaScript提供了非常优雅且安全的方式,告别了过去直接操作prototype的繁琐和潜在风险。

1. 使用ES6的class语法

这是最推荐的方式。class语法是ES6引入的语法糖,它背后依然是原型和继承的机制,但它提供了一种更清晰、更面向对象的写法来定义构造函数和原型方法。

class MyCustomObject {
  constructor(name, value) {
    this.name = name;
    this.value = value;
  }

  // 这就是添加到 MyCustomObject.prototype 上的方法
  displayInfo() {
    console.log(`Name: ${this.name}, Value: ${this.value}`);
  }

  // 也可以添加静态方法,不属于实例,属于类本身
  static createDefault() {
    return new MyCustomObject('Default', 0);
  }
}

const obj1 = new MyCustomObject('Item A', 100);
obj1.displayInfo(); // Name: Item A, Value: 100

const defaultObj = MyCustomObject.createDefault();
defaultObj.displayInfo(); // Name: Default, Value: 0

使用class,你清晰地定义了哪些方法是实例共享的(通过原型链),哪些是类自身的(静态方法),这极大地提升了代码的可读性和可维护性,也避免了直接操作prototype可能带来的误解或错误。

2. 传统的构造函数与prototype属性

虽然class更推荐,但理解传统的构造函数和prototype属性仍然很重要,因为class底层就是它。如果你不使用class,也可以直接操作构造函数的prototype属性来添加共享方法。

function OldSchoolObject(id, data) {
  this.id = id;
  this.data = data;
}

// 直接添加到构造函数的原型上
OldSchoolObject.prototype.printData = function() {
  console.log(`ID: ${this.id}, Data: ${this.data}`);
};

OldSchoolObject.prototype.updateData = function(newData) {
  this.data = newData;
};

const oldObj = new OldSchoolObject('ABC', { status: 'active' });
oldObj.printData(); // ID: ABC, Data: { status: 'active' }
oldObj.updateData({ status: 'inactive' });
oldObj.printData(); // ID: ABC, Data: { status: 'inactive' }

这种方式也完全安全,因为它只影响你自己的OldSchoolObject构造函数的原型,不会触及全局的内置对象。

无论是使用class还是传统的构造函数,核心思想都是把共享方法放在自定义构造函数的prototype对象上,而不是去动Object.prototypeArray.prototype。这样,你的代码既能享受原型链带来的内存效率和继承特性,又能保持代码的隔离性和安全性。

当原型链被意外修改时,我们能做些什么?

虽然我们努力避免,但现实世界里,原型链,尤其是内置对象的原型链,偶尔还是会被一些不那么规范的第三方库或遗留代码“污染”。当这种情况发生时,我们能做的通常是“检测”和“规避”,而不是“撤销”或“阻止”已经发生的修改,因为后者几乎是不可能或极其危险的。

1. 运行时检测和排查

这是第一步。你需要确认到底哪个原型被修改了,以及被添加了什么。

  • 检查Object.prototypeArray.prototype:

    // 看看 Object.prototype 上是不是多了什么不该有的东西
    console.log(Object.getOwnPropertyNames(Object.prototype));
    // 或者只看可枚举的
    console.log(Object.keys(Object.prototype));
    
    // 同样检查 Array.prototype
    console.log(Object.getOwnPropertyNames(Array.prototype));

    通过这些输出,你可以大致判断哪些“陌生”的方法或属性被添加了。

  • 隔离问题代码: 如果你怀疑是某个特定的第三方库导致的问题,尝试在没有该库的情况下运行你的应用,看问题是否消失。这有点像“二分法”调试,逐步排除可疑模块。

2. 防御性编程策略

既然已经发生了,我们能做的就是尽量减少其影响。

  • 始终使用hasOwnProperty: 再次强调,在遍历对象属性时,特别是在处理来自外部或不确定来源的数据时,务必使用Object.prototype.hasOwnProperty.call(obj, prop)来确保你只处理对象自身的属性。这能有效防止原型链上被注入的属性影响你的逻辑。

  • 避免for...in遍历数组: for...in会遍历对象及其原型链上的所有可枚举属性。如果Array.prototype被扩展了,for...in就会遍历到那些非数组元素。对于数组,总是使用for...offorEachmapfilter等迭代方法。

    const myArray = [1, 2, 3];
    // 如果 Array.prototype 被污染,for...in 会有问题
    // for (let item in myArray) { console.log(item); } // 可能会输出 '0', '1', '2', 'somePollutedMethod'
    
    // 推荐:
    for (let item of myArray) { console.log(item); } // 只输出 1, 2, 3
    myArray.forEach(item => console.log(item)); // 只输出 1, 2, 3

3. 考虑运行时修补(非常规手段)

这通常不是一个推荐的通用解决方案,因为它风险很高,而且可能导致更多问题。但在某些极端情况下,你可能需要考虑:

  • 删除注入的属性: 如果你知道具体是哪个属性被注入了,并且它不是关键的,你可以尝试直接delete Object.prototype.someBadMethod;。但这非常脆弱,因为其他代码可能依赖它,或者它可能在其他地方被重新注入。
  • 使用沙箱环境(Web Workers / Iframes): 对于特别高风险的第三方代码,如果可能,将其运行在独立的Web Worker或iframe中。这样,即使它们污染了Worker或iframe内部的原型,也不会影响到主页面的全局环境。

总的来说,面对原型链被意外修改的情况,最佳策略是预防和检测。一旦发生,则通过防御性编程来规避其影响,并尽可能找出源头并修复(例如,更新或替换有问题的第三方库)。试图“撤销”或“阻止”已发生的修改,往往是得不偿失的。

今天关于《防止原型链扩展的3种方法》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于原型链,Object.defineProperty,Object.freeze,阻止扩展,原型链污染的内容请关注golang学习网公众号!

Java序列化漏洞详解与防护指南Java序列化漏洞详解与防护指南
上一篇
Java序列化漏洞详解与防护指南
Deepseek满血版联动Typinator提升输入效率
下一篇
Deepseek满血版联动Typinator提升输入效率
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    514次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    499次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • 千音漫语:智能声音创作助手,AI配音、音视频翻译一站搞定!
    千音漫语
    千音漫语,北京熠声科技倾力打造的智能声音创作助手,提供AI配音、音视频翻译、语音识别、声音克隆等强大功能,助力有声书制作、视频创作、教育培训等领域,官网:https://qianyin123.com
    1219次使用
  • MiniWork:智能高效AI工具平台,一站式工作学习效率解决方案
    MiniWork
    MiniWork是一款智能高效的AI工具平台,专为提升工作与学习效率而设计。整合文本处理、图像生成、营销策划及运营管理等多元AI工具,提供精准智能解决方案,让复杂工作简单高效。
    1168次使用
  • NoCode (nocode.cn):零代码构建应用、网站、管理系统,降低开发门槛
    NoCode
    NoCode (nocode.cn)是领先的无代码开发平台,通过拖放、AI对话等简单操作,助您快速创建各类应用、网站与管理系统。无需编程知识,轻松实现个人生活、商业经营、企业管理多场景需求,大幅降低开发门槛,高效低成本。
    1201次使用
  • 达医智影:阿里巴巴达摩院医疗AI影像早筛平台,CT一扫多筛癌症急慢病
    达医智影
    达医智影,阿里巴巴达摩院医疗AI创新力作。全球率先利用平扫CT实现“一扫多筛”,仅一次CT扫描即可高效识别多种癌症、急症及慢病,为疾病早期发现提供智能、精准的AI影像早筛解决方案。
    1216次使用
  • 智慧芽Eureka:更懂技术创新的AI Agent平台,助力研发效率飞跃
    智慧芽Eureka
    智慧芽Eureka,专为技术创新打造的AI Agent平台。深度理解专利、研发、生物医药、材料、科创等复杂场景,通过专家级AI Agent精准执行任务,智能化工作流解放70%生产力,让您专注核心创新。
    1201次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码