JavaScript 原型链与原型链污染全解析
一、JavaScript 对象与原型基础
1. 对象的本质
2. 原型(Prototype)
- 隐藏属性:每个对象都有一个
[[Prototype]]
(通过 __proto__
或 Object.getPrototypeOf()
访问)。
- 默认原型:
- 普通对象的原型默认指向
Object.prototype
。
Object.prototype.__proto__ === null
(原型链终点)。
二、原型链的形成与查找机制
1. 原型链结构
实例对象 → 构造函数原型 → Object.prototype → null
2. 属性查找规则
- 检查对象自身属性。
- 若未找到,沿
__proto__
向上查找原型对象。
- 持续到
Object.prototype
,若仍未找到返回 undefined
。
3. 代码示例
function Animal(type) {
this.type = type;
}
Animal.prototype.eat = function() {
console.log("Eating...");
};
const myDog = new Animal("dog");
myDog.name = "Buddy";
console.log(myDog.eat());
console.log(myDog.toString());
三、原型链的创建与继承
1. 原型产生时机
原型在对象创建时即被确定,具体规则如下:
创建对象的方式 |
原型的指向 |
示例 |
对象字面量 |
默认指向 Object.prototype |
const obj = {}; → obj.__proto__ === Object.prototype |
构造函数 (new ) |
实例的 __proto__ 指向构造函数的 prototype |
new Person() → person.__proto__ === Person.prototype |
Object.create() |
显式指定原型对象 |
const child = Object.create(parent); → child.__proto__ === parent |
类 (class ) |
类语法是语法糖,实例原型指向类的 prototype (底层仍为原型继承) |
class Dog {}; new Dog() → dog.__proto__ === Dog.prototype |
2. 手动构建原型链
const animal = { eats: true };
const dog = Object.create(animal);
dog.name = "Buddy";
console.log(dog.eats);
四、原型链污染漏洞与防御
1. 漏洞原理
- 攻击方式:通过篡改原型对象(如
Object.prototype
),影响所有继承该原型的对象。
- 触发条件:动态合并未过滤的敏感键名(如
__proto__
)。
2. 攻击示例
function unsafeMerge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
unsafeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
unsafeMerge({}, payload);
console.log({}.isAdmin);
3. 防御措施
方法 |
实现方式 |
代码示例 |
过滤敏感键名 |
禁止操作 __proto__ 、constructor 、prototype |
if (key === '__proto__') throw Error('Forbidden key'); |
使用无原型对象 |
Object.create(null) 创建的对象无原型链 |
const safeObj = Object.create(null); |
冻结内置原型 |
禁止修改 Object.prototype |
Object.freeze(Object.prototype); |
优先使用 Map |
Map 不依赖原型链,避免污染 |
const map = new Map(); map.set('isAdmin', true); |
五、与 PHP 类继承的对比
特性 |
JavaScript 原型链 |
PHP 类继承 |
继承机制 |
对象继承对象(原型链) |
类继承类(经典继承) |
动态性 |
可随时修改原型和对象属性 |
类结构静态,属性需预先声明 |
方法共享 |
原型上的方法被所有实例共享 |
每个实例独立存储方法(除非 static ) |
私有字段 |
通过 # 符号支持(ES2022+) |
通过 private 关键字支持 |
六、关键方法与工具
1. 核心方法
方法/属性 |
作用 |
示例 |
Object.getPrototypeOf() |
获取对象的原型 |
Object.getPrototypeOf(dog) === animal |
Object.setPrototypeOf() |
修改对象的原型(慎用) |
Object.setPrototypeOf(dog, newProto); |
obj.hasOwnProperty() |
检查属性是否属于对象自身(不查原型链) |
dog.hasOwnProperty('name') |
in 操作符 |
检查属性是否存在(包括原型链) |
'toString' in dog |
2. 安全开发工具
工具 |
用途 |
链接 |
ppmap |
自动化原型链污染扫描 |
[GitHub - ppmap] |
CodeQL |
静态代码分析 |
[GitHub - CodeQL] |
七、常见问题与练习
1. 如何判断属性来源?
console.log(myDog.hasOwnProperty("name"));
console.log("toString" in myDog);
2. 练习:分析代码输出
function A() {}
A.prototype.x = 1;
const a = new A();
A.prototype = { x: 2 };
const b = new A();
console.log(a.x, b.x);
八、总结
- 原型链本质:通过
__proto__
实现的链式继承机制,实现属性和方法共享。
- 安全核心:永远不要信任用户输入的键名,严格过滤敏感属性。
- 最佳实践:优先使用
Map
、Object.create(null)
等安全结构,避免直接操作原型。