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'
核心逻辑:
构造函数 Animal
的 prototype
,是实例 cat1
、cat2
的原型对象。原型上的 color
属性被所有实例共享。
4. 原型属性的动态特性
原型对象的属性不属于实例自身,而是通过原型链关联。因此:
修改原型对象,所有实例会立即体现变动。
示例(接上面代码):
Animal.prototype.color = 'black'; // 修改原型属性
cat1.color // 'black'(实例自动同步变化)
cat2.color // 'black'
综上,prototype
的核心作用是 为构造函数的实例提供共享属性 / 方法的 “公共容器”,实现内存优化和继承关系,是 JavaScript 原型继承机制的基石。
可以理解为:他(prototype)是实例的父类
5 对比
object
res.name调用的是name属性,所以.prototype也是object属性
原型链
JavaScript 规定:所有对象都有自己的原型对象(prototype)。
一、原型链的形成逻辑
- 双向特性:
- 任何对象都可以充当其他对象的原型;
- 原型对象本身也是对象,因此也有自己的原型。
- 链式推导:
对象 → 原型 → 原型的原型 → … → 终点,形成 “原型链”(prototype chain)。
二、原型链的示例与终点
1. 原型链的层级示例(以 cat1
为例):
cat1.color --> Animal.prototype --> ...(中间原型)... --> Object.prototype --> null
2. 所有对象的最终原型:Object.prototype
- 所有对象的原型链最终都会上溯到
Object.prototype
(Object
构造函数的prototype
属性)。 - 这就是 “所有对象都有
valueOf()
、toString()
方法” 的原因 —— 这些方法继承自Object.prototype
。
3. 原型链的终点:null
Object.prototype
也有自己的原型,即null
(null
没有属性、方法,也没有自身的原型)。- 验证代码:
Object.getPrototypeOf(Object.prototype) // 返回 null
三、原型链的属性查找规则
当读取对象的某个属性时,JavaScript 引擎遵循以下逻辑:
- 优先查自身:先在对象自身查找属性;
- 向上遍历原型链:若自身没有,就到原型对象中查找;若仍没有,继续到原型的原型中查找…… 直到原型链顶端(
Object.prototype
); - 最终结果:
- 若找到,返回属性值;
- 若遍历到
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.prototype
即Father
的实例继承)。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(修复后正常)
核心总结
- 身份标识:
constructor
帮助实例 “溯源” 自己的构造函数; - 动态创建:允许从实例反向调用构造函数,灵活生成新实例;
- 原型维护:修改原型时,务必修复
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 ) | 新原型是全新对象,默认继承 Object 的 constructor ,需手动修正关联。 |
若忽略修复,实例的 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__
可辅助理解原型链的层级关系,但实际开发中更注重 “原型继承” 的逻辑,而非直接操作该属性