XSS的原型链污染1--原型链解释

因为js(<=ES6)中没有类的概念,所以衍生出了原型链的概念

面向对象的封装,多态和继承

JavaScript代码创建原型链时因为里面写的方法,每当创建一个实例对象的时候,都会调用其中的方法,都会创建一个新的内存空间,新建一个相同的方法,这很浪费系统的内存资源,但是创建的实例对象都是用的同一个方法,完全应该共享,所以这个问题的解决方法就衍生出了prototype原型对象

prototype 的属性的作用

JavaScript 继承机制的设计思想是:原型对象的所有属性和方法,都能被实例对象共享。若将属性和方法定义在原型上,所有实例对象可共享这些内容,既节省内存,又体现实例间的联系。

1. 为对象指定原型的规则

JavaScript 规定:每个函数都有一个 prototype 属性,指向一个对象

示例:

复制代码
function f() {}  
typeof f.prototype // "object"

函数 f 默认自带 prototype 属性,其值为一个对象。

2. prototype 的作用差异:普通函数 vs 构造函数

  • 普通函数prototype 属性基本无用。
  • 构造函数 :当通过 new 生成实例时,构造函数的 prototype自动成为实例对象的原型

3. 构造函数的 prototype 实践:属性共享

通过构造函数 Animal 演示 prototype 的共享特性:

复制代码
// 定义构造函数
function Animal(name) {  
  this.name = name; // 实例自身属性(每个实例单独拥有)
}  

// 在原型上定义共享属性
Animal.prototype.color = 'white';  

// 创建实例
var cat1 = new Animal('大毛');  
var cat2 = new Animal('二毛');  

// 实例访问原型上的属性
cat1.color // 'white'  
cat2.color // 'white'  

核心逻辑

构造函数 Animalprototype,是实例 cat1cat2原型对象 。原型上的 color 属性被所有实例共享。

4. 原型属性的动态特性

原型对象的属性不属于实例自身 ,而是通过原型链关联。因此:
修改原型对象,所有实例会立即体现变动

示例(接上面代码):

复制代码
Animal.prototype.color = 'black'; // 修改原型属性  
cat1.color // 'black'(实例自动同步变化)  
cat2.color // 'black'  

综上,prototype 的核心作用是 为构造函数的实例提供共享属性 / 方法的 "公共容器",实现内存优化和继承关系,是 JavaScript 原型继承机制的基石。

可以理解为:他(prototype)是实例的父类

5 对比

object

res.name调用的是name属性,所以.prototype也是object属性

原型链

JavaScript 规定:所有对象都有自己的原型对象(prototype)

一、原型链的形成逻辑

  1. 双向特性
    • 任何对象都可以充当其他对象的原型;
    • 原型对象本身也是对象,因此也有自己的原型。
  2. 链式推导
    对象 → 原型 → 原型的原型 → ... → 终点,形成 "原型链"(prototype chain)

二、原型链的示例与终点

1. 原型链的层级示例(以 cat1 为例):
复制代码
cat1.color --> Animal.prototype --> ...(中间原型)... --> Object.prototype --> null  
2. 所有对象的最终原型:Object.prototype
  • 所有对象的原型链最终都会上溯到 Object.prototypeObject 构造函数的 prototype 属性)。
  • 这就是 "所有对象都有 valueOf()toString() 方法" 的原因 ------ 这些方法继承自 Object.prototype
3. 原型链的终点:null
  • Object.prototype 也有自己的原型,即 nullnull 没有属性、方法,也没有自身的原型)。

  • 验证代码:

    复制代码
    Object.getPrototypeOf(Object.prototype)  
    // 返回 null  

三、原型链的属性查找规则

当读取对象的某个属性时,JavaScript 引擎遵循以下逻辑:

  1. 优先查自身 :先在对象自身查找属性;
  2. 向上遍历原型链 :若自身没有,就到原型对象 中查找;若仍没有,继续到原型的原型 中查找...... 直到原型链顶端(Object.prototype);
  3. 最终结果
    • 若找到,返回属性值;
    • 若遍历到 null 仍未找到,返回 undefined
特殊情况:属性 "覆盖"(overriding)

对象自身原型 定义了同名属性 ,则 优先读取对象自身的属性(自身属性覆盖原型链上的同名属性)。

四、原型链的性能影响

  • 属性查找成本 :在原型链上越 "上层" 的属性(如 Object.prototype 上的属性),查找时遍历的层级越多,对性能影响越大;
  • 不存在属性的代价 :若查找不存在的属性 ,引擎会遍历整个原型链 (直到 null),这会带来额外性能损耗。

综上,原型链是 JavaScript 实现属性继承共享 的核心机制,理解其查找规则和边界(终点 null),是掌握对象行为的关键。

复制代码
function Father() {
  this.first_name = 'Donald'
  this.last_name = 'Trump'
}

function Son() {
  this.first_name = 'Melania'
}

Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)

原型链关系解析

1. 构造函数与原型的初始关系
  • Father 是构造函数,其 prototype 属性指向 Father 的原型对象Father.prototype)。
  • Son 是构造函数,其 prototype 属性原本指向 Son 的原型对象Son.prototype,默认继承 Object.prototype)。
2. 关键操作:修改 Son.prototype

javascript

复制代码
Son.prototype = new Father()

这行代码将 Son 的原型对象 替换为 Father 的实例 (即执行 new Father() 创建的对象)。此时:

  • Son.prototype 不再是默认的空对象,而是一个包含 first_name: 'Donald'last_name: 'Trump' 的对象(因为 new Father() 会执行 Father 的构造函数,给实例添加这两个属性)。
  • Son 的实例(如 son)的 __proto__ 会指向这个新的 Son.prototype(即 Father 的实例)。
3. son 实例的原型链结构

当创建 let son = new Son() 时,son 的原型链如下:

plaintext

复制代码
son(实例)  
↓(__proto__指向)  
Son.prototype(即 Father 的实例,包含 first_name: 'Donald'、last_name: 'Trump')  
↓(__proto__指向)  
Father.prototype(Father 构造函数的原型对象,默认继承 Object.prototype)  
↓(__proto__指向)  
Object.prototype  
↓(__proto__指向)  
null(原型链终点)  
4. 属性查找过程(解释 console.log 的输出)

执行 console.log(Name: {son.first_name} {son.last_name}) 时,JS 引擎按原型链查找规则解析属性:

  • son.first_name
    • 先查 son 自身 → 发现 this.first_name = 'Melania'(自身属性),直接使用。
  • son.last_name
    • 先查 son 自身 → 没有该属性;
    • 沿着原型链向上找,查 Son.prototype(即 Father 的实例)→ 发现 this.last_name = 'Trump'(继承自 Father 的构造函数),于是使用。
5. 原型链的核心作用:实现属性继承
  • last_name 是通过 原型链继承 获得的(Son 自身没定义,从 Son.prototypeFather 的实例继承)。
  • first_name自身属性覆盖 了原型链上的同名属性(Son 自身定义了,优先使用自身值)。

总结

这段代码通过 "修改构造函数的 prototype 为另一个构造函数的实例" ,手动实现了 原型链继承

  • Son 的实例能访问 Father 定义的属性(如 last_name),体现了 原型链的属性继承能力
  • 自身属性可覆盖原型链上的同名属性(如 first_name),体现了 原型链的查找优先级

这种写法是 ES5 之前实现继承的经典方式,核心依赖 原型链的层级查找机制

constructor 属性

一、核心定义

每个函数的 prototype 对象,都自带一个 constructor 属性,默认指向该 prototype 所属的构造函数

复制代码
function P() {}  
// prototype 的 constructor 指向构造函数 P  
P.prototype.constructor === P; // true  

二、constructor 的继承特性

constructor 定义在 prototype 对象 上,因此会被 所有实例对象通过原型链继承

代码验证:
复制代码
function P() {}  
var p = new P();  

// 1. 实例 p 的 constructor 继承自原型链  
p.constructor === P; // true(因为 p.__proto__ === P.prototype,继承了 constructor)  

// 2. 对比原型上的 constructor  
p.constructor === P.prototype.constructor; // true  

// 3. 检查 p 自身是否有 constructor 属性  
p.hasOwnProperty('constructor'); // false(说明 constructor 是继承来的,非自身属性)  

三、底层逻辑:原型链的继承关系

实例 p 自身没有 constructor 属性,它的 constructor从原型链(P.prototype)上 "借" 来的 (通过 __proto__ 访问原型对象的 constructor)。

四、constructor 的核心作用

判断实例对象的 "构造函数来源" ------ 明确某个实例是由哪个构造函数创建的。

例如:

复制代码
function Dog() {}  
function Cat() {}  

var animal = new Dog();  
console.log(animal.constructor === Dog); // true(确认 animal 由 Dog 构造)  

constructor 是原型链继承中 "身份溯源" 的关键纽带,让实例和构造函数的关联更清晰。但需注意:若手动修改原型对象(如 P.prototype = {}),可能破坏 constructor 的指向,需手动修复(P.prototype.constructor = P)。

constructor 属性的拓展用法与注意事项

一、验证实例的构造函数归属
复制代码
function F() {};  
var f = new F();  

f.constructor === F; // true  
f.constructor === RegExp; // false  

解释constructor 明确实例 f 的构造函数是 F,而非其他函数(如内置的 RegExp)。

二、通过 constructor 从实例新建同类实例
1. 基础场景:从现有实例创建新实例
复制代码
function Constr() {}  
var x = new Constr();  

// 利用 x 的 constructor 间接调用构造函数,创建新实例 y  
var y = new x.constructor();  
y instanceof Constr; // true  
2. 实例方法中复用构造函数(更实用)

javascript

复制代码
Constr.prototype.createCopy = function () {  
  // 通过 this.constructor 调用自身构造函数,新建实例  
  return new this.constructor();  
};  

解释createCopy 方法借助 constructor,让实例能动态创建 "同类型" 新实例(即使构造函数后续被修改,也能自动适配)。

三、修改原型时的 constructor 维护

constructor原型对象与构造函数的关联纽带 。若直接替换原型对象,会破坏 constructor 指向,需 手动修复

复制代码
function OldConstr() {}  

// 错误操作:直接替换原型,导致 constructor 丢失  
OldConstr.prototype = {  
  // ...新方法...  
};  

var instance = new OldConstr();  
instance.constructor === OldConstr; // false(此时指向 Object)  


// 正确做法:替换原型时,显式恢复 constructor  
OldConstr.prototype = {  
  constructor: OldConstr, // 手动关联构造函数  
  // ...新方法...  
};  

instance.constructor === OldConstr; // true(修复后正常)  

核心总结

  1. 身份标识constructor 帮助实例 "溯源" 自己的构造函数;
  2. 动态创建:允许从实例反向调用构造函数,灵活生成新实例;
  3. 原型维护 :修改原型时,务必修复 constructor,避免关联关系断裂

原型对象替换时的 constructor 维护问题

一、核心背景

constructor原型对象与构造函数的关联纽带 。若直接替换整个原型对象 (而非增量扩展),必须手动修复 constructor,否则关联会断裂。

二、代码演示:原型替换导致 constructor 异常
复制代码
function Person(name) {  
  this.name = name;  
}  

// 初始状态:prototype.constructor 正确指向 Person  
console.log(Person.prototype.constructor === Person); // true  


// 错误操作:直接替换原型对象(覆盖原有 prototype)  
Person.prototype = {  
  method: function () {}  
};  


// 问题暴露:新原型的 constructor 不再指向 Person  
console.log(Person.prototype.constructor === Person); // false  
console.log(Person.prototype.constructor === Object); // true(默认继承自 Object.prototype)  
三、现象分析
  • 当直接赋值 Person.prototype = {} 时,新原型是全新的普通对象 ,其 constructor 会默认继承 Object.prototype.constructor(即 Object)。
  • 这导致后续通过 实例.constructor 判断构造函数时,结果错误(实例会 "误以为" 自己由 Object 创建)。
四、修复方法:替换原型时,显式重置 constructor
复制代码
Person.prototype = {  
  constructor: Person, // 手动关联回原构造函数  
  method: function () {}  
};  

// 验证:恢复正确关联  
console.log(Person.prototype.constructor === Person); // true  

关键总结

原型操作方式 constructor 是否需要修复 原因
扩展原型 (如 Person.prototype.method = ... 不需要 原型对象未被替换,constructor 仍保留原值。
替换原型 (如 Person.prototype = {} 需要(手动设置 constructor 新原型是全新对象,默认继承 Objectconstructor,需手动修正关联。

若忽略修复,实例的 constructor 会错误指向 Object(或其他无关构造函数),导致身份判断、动态创建实例等逻辑失效。

Object.prototype.proto

proto:指向的值就是父类,_proto_属性指向当前对象的原型对象,及就是构造函数的prototype属性

1. 基本定义

实例对象的 __proto__ 属性 (名称前后各有两个下划线),用于 读取或设置该对象的原型(即它所继承的原型对象) ,该属性可读写

2. 代码示例:通过 __proto__ 修改原型
复制代码
var obj = {};     // 创建空对象 obj  
var p = {};      // 创建空对象 p  
obj.__proto__ = p; // 将 p 设为 obj 的原型  

// 验证原型设置:Object.getPrototypeOf 是标准方法,返回对象的原型  
Object.getPrototypeOf(obj) === p; // 输出 true  
3. 标准规范与使用建议
  • 环境限制__proto__浏览器环境的非标准特性(语言标准未强制要求所有 JavaScript 环境支持,如 Node.js 早期版本行为不同)。
  • 设计意图 :双下划线 __ 表明它是 内部属性,初衷是供引擎内部使用,而非直接暴露给开发者。
  • 替代方案 :生产环境中,优先使用标准 API 操作原型:
    • 读取原型Object.getPrototypeOf(obj)(替代 obj.__proto__ 读操作);
    • 设置原型Object.setPrototypeOf(obj, newProto)(替代 obj.__proto__ 写操作)。
4. 用 __proto__ 直观表示原型链

通过 __proto__ 手动构建原型链,实现方法共享(示例)

复制代码
// 1. 定义"原型对象"(存放共享方法)  
var proto = {  
  print: function () {  
    console.log(this.name); // 访问实例的 name 属性  
  }  
};  

// 2. 定义两个实例对象  
var A = { name: '张三' };  
var B = { name: '李四' };  

// 3. 让 A、B 继承 proto 的方法(通过 __proto__ 关联原型)  
A.__proto__ = proto;  
B.__proto__ = proto;  

// 4. 调用共享方法  
A.print(); // 输出:张三(A 自身有 name,调用 proto 的 print)  
B.print(); // 输出:李四(B 自身有 name,调用 proto 的 print)  

核心总结

  • __proto__ 是实例对象访问原型的 "便捷入口",但因非标准、内部属性 的特性,不推荐直接依赖它开发
  • 若需兼容不同环境,优先使用 Object.getPrototypeOf()Object.setPrototypeOf()
  • __proto__ 可辅助理解原型链的层级关系,但实际开发中更注重 "原型继承" 的逻辑,而非直接操作该属性
相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax