一、原型(Prototype):对象的"基因库"
定义 :原型是JavaScript对象内置的隐藏属性([[Prototype]]
),用于实现属性和方法的继承与共享。
核心作用:让多个对象能够共享同一套属性/方法,避免重复定义,优化内存占用。
1. 原型的两种访问方式
- 实例对象属性 :
__proto__
(ES6标准化,浏览器实现,不推荐直接操作) - 构造函数属性 :
prototype
(仅函数拥有,指向实例的原型对象) - 推荐API :
Object.getPrototypeOf(obj)
和Object.setPrototypeOf(obj, proto)
javascript
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true(实例.__proto__指向原型)
console.log(Object.prototype.constructor === Object); // true(原型.constructor指向构造函数)
2. 原型对象的特性
- 包含所有实例共享的属性/方法(如
Array.prototype.push
) - 自带
constructor
属性,指向关联的构造函数 - 本身也是对象,因此也有自己的原型(形成原型链)
二、原型链(Prototype Chain):属性查找的"族谱"
定义 :多个对象通过__proto__
属性串联形成的链式结构,是JavaScript实现继承的核心机制。
1. 工作原理
当访问对象的属性/方法时:
- 先检查对象自身是否存在该属性
- 若不存在,沿
__proto__
向上查找原型对象 - 依次类推,直至找到属性或到达原型链终点(
null
)
javascript
const arr = [1, 2, 3];
arr.toString(); // 实际调用 Object.prototype.toString
// 原型链路径:arr → Array.prototype → Object.prototype → null
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null(终点)
2. 原型链的典型结构
javascript
实例对象 → 构造函数.prototype → 父构造函数.prototype → ... → Object.prototype → null
arr → Array.prototype → Object.prototype → ... → null
func → Function.prototype → Object.prototype → ... → null
三、构造函数、原型与实例的"三角关系"
javascript
// 1. 定义构造函数
function Person(name) {
this.name = name; // 实例属性(每个实例独立拥有)
}
// 2. 在原型上定义共享方法
Person.prototype.sayHello = function() {
console.log(`Hello, ${this.name}`); // this指向调用方法的实例
};
// 3. 创建实例
const person1 = new Person("Alice");
const person2 = new Person("Bob");
// 4. 关系验证
console.log(person1.__proto__ === Person.prototype); // true(实例→原型)
console.log(Person.prototype.constructor === Person); // true(原型→构造函数)
console.log(person1.constructor === Person); // true(实例继承constructor)
// 5. 共享方法调用
person1.sayHello(); // "Hello, Alice"(共享原型方法)
person2.sayHello(); // "Hello, Bob"
四、原型链的核心应用场景
1. 实现继承
组合继承(最经典的继承模式):
javascript
function Student(name, major) {
Person.call(this, name); // 继承实例属性
this.major = major;
}
// 继承原型方法(关键步骤)
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student; // 修复constructor指向
// 添加子类独有方法
Student.prototype.study = function() {
console.log(`${this.name} is studying ${this.major}`);
};
ES6 class语法糖(本质仍是原型继承):
scala
class Student extends Person {
constructor(name, major) {
super(name); // 等价于 Person.call(this, name)
this.major = major;
}
study() { // 定义在Student.prototype上
console.log(`${this.name} is studying ${this.major}`);
}
}
2. 扩展内置对象功能
javascript
// 为数组添加去重方法(谨慎使用,可能引发冲突)
Array.prototype.unique = function() {
return [...new Set(this)];
};
[1, 2, 2, 3].unique(); // [1, 2, 3]
3. 创建纯净字典对象
ini
// 无原型链的对象,避免原型属性干扰
const safeMap = Object.create(null);
safeMap.toString = "自定义toString";
console.log(safeMap.toString); // "自定义toString"(不会访问Object.prototype.toString)
五、常见问题与避坑指南
1. 原型污染
风险:直接修改内置对象原型会影响所有实例
javascript
// 危险行为!
Object.prototype.foo = "bar";
console.log({}.foo); // "bar"(所有对象都会继承foo属性)
解决方案 :使用Object.create(null)
创建隔离对象
2. 属性遮蔽(Shadowing)
实例属性会覆盖原型链上的同名属性:
ini
Person.prototype.age = 18;
const person = new Person("Alice");
person.age = 20; // 实例属性遮蔽原型属性
console.log(person.age); // 20(优先取实例属性)
delete person.age; // 删除实例属性后恢复原型属性
console.log(person.age); // 18
3. this指向问题
原型方法中的this
指向调用该方法的实例:
ini
Person.prototype.getName = function() {
return this.name;
};
const person = new Person("Alice");
const getName = person.getName;
console.log(getName()); // undefined(this指向window/global)
解决方案 :使用箭头函数绑定或Function.prototype.bind()
六、调试与可视化工具
-
控制台查看原型链 :
console.dir(obj)
可展开对象完整原型链结构 -
判断原型关系:
javascript// 方法1:instanceof(检查是否在原型链上) console.log(person instanceof Person); // true console.log(person instanceof Object); // true // 方法2:Object.prototype.isPrototypeOf() console.log(Person.prototype.isPrototypeOf(person)); // true
-
获取原型链路径:
inifunction getPrototypeChain(obj) { const chain = []; while (obj) { chain.push(obj.constructor.name || obj); obj = Object.getPrototypeOf(obj); } return chain; } getPrototypeChain([1,2,3]); // ["Array", "Object", "null"]
七、总结:原型链的本质与价值
-
本质:JavaScript通过原型链实现了对象间的属性继承,是一种不同于传统类继承的"原型继承"模式
-
价值:
- 实现代码复用与扩展
- 动态特性(运行时可修改原型)
- 轻量级对象模型
-
最佳实践:
- 优先使用
class
语法(更清晰的继承结构) - 避免修改内置对象原型
- 复杂继承场景考虑使用组合优于继承的设计模式
- 优先使用
理解原型与原型链是掌握JavaScript面向对象编程的关键,也是深入理解框架源码(如Vue的响应式原理)的基础。