一、原型链的核心概念
JavaScript 是基于原型的语言,每个对象都有一个原型对象([[Prototype]]
)。当访问一个对象的属性时,JavaScript 会先在对象本身查找,若未找到则沿着原型链向上查找,直到原型链终点(Object.prototype
)或找到该属性。
关键术语:
- 原型对象(Prototype):每个对象都有一个内部属性
[[Prototype]]
,指向其原型对象。 - 构造函数(Constructor):创建对象的函数,其
prototype
属性指向实例的原型对象。 - 原型链(Prototype Chain):由多个
[[Prototype]]
连接形成的层级结构。
二、原型链的底层机制
1. 构造函数与原型的关系
javascript
function Person(name) {
this.name = name;
}
// Person 构造函数的 prototype 属性指向原型对象
Person.prototype.sayHello = function() {
console.log(`Hello, ${this.name}`);
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
// person1 和 person2 的 [[Prototype]] 都指向 Person.prototype
console.log(person1.__proto__ === Person.prototype); // true
console.log(person2.__proto__ === Person.prototype); // true
2. 原型链的层级结构
plaintext
person1 → Person.prototype → Object.prototype → null
person1
实例的属性和方法。Person.prototype
上的属性和方法(如sayHello
)。Object.prototype
上的通用方法(如toString()
、hasOwnProperty()
)。- 原型链终点为
null
(Object.prototype.__proto__ === null
)。
三、原型链的实战应用
1. 继承属性和方法
javascript
function Animal(type) {
this.type = type;
}
Animal.prototype.move = function() {
console.log(`${this.type} is moving`);
};
function Dog(name) {
this.name = name;
Animal.call(this, 'Dog'); // 继承实例属性
}
// 设置 Dog 的原型为 Animal.prototype,实现方法继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog = new Dog('Buddy');
dog.move(); // 继承自 Animal.prototype → "Dog is moving"
dog.bark(); // 定义在 Dog.prototype → "Woof!"
2. 实现混合(Mixin)模式
javascript
const Logger = {
log(message) {
console.log(`[LOG] ${message}`);
}
};
const Calculator = {
add(a, b) {
return a + b;
}
};
// 将 Logger 和 Calculator 的方法混入到 obj
function mixin(obj, ...mixins) {
Object.assign(obj.prototype, ...mixins);
}
function MyClass() {}
mixin(MyClass, Logger, Calculator);
const instance = new MyClass();
instance.log('Calculating...'); // "[LOG] Calculating..."
console.log(instance.add(1, 2)); // 3
四、原型链的性能与陷阱
1. 性能问题
- 属性查找效率:原型链越长,查找属性的性能开销越大。
- 避免过度嵌套:复杂的原型层级会降低代码可读性和维护性。
2. 常见陷阱
-
原型共享问题:
javascript
function Car() {} Car.prototype.features = []; // 引用类型属性 const car1 = new Car(); const car2 = new Car(); car1.features.push('GPS'); console.log(car2.features); // ['GPS'](共享同一个数组)
解决方案:在构造函数中初始化引用类型属性:
javascript
function Car() { this.features = []; // 每个实例独立拥有 }
-
修改原型的副作用:
修改内置对象(如Array.prototype
)的原型可能导致全局影响,引发难以调试的问题。
五、ES6 类与原型链的关系
ES6 的 class
语法是原型链的语法糖,本质仍基于原型实现:
javascript
class Animal {
constructor(type) {
this.type = type;
}
move() {
console.log(`${this.type} is moving`);
}
}
class Dog extends Animal {
constructor(name) {
super('Dog');
this.name = name;
}
bark() {
console.log('Woof!');
}
}
const dog = new Dog('Buddy');
// 原型链结构与之前相同:
// dog → Dog.prototype → Animal.prototype → Object.prototype → null
六、面试高频问题
-
如何判断一个属性是对象自身的还是原型链上的?
javascript
obj.hasOwnProperty('prop'); // true 表示对象自身属性
-
如何实现对象的浅拷贝和深拷贝?
- 浅拷贝:复制对象本身及直接属性,不复制原型链。
javascript
const clone = Object.assign({}, obj);
- 深拷贝:递归复制所有层级的属性(包括原型链)。
javascript
function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; const clone = Array.isArray(obj) ? [] : {}; for (const key in obj) { clone[key] = deepClone(obj[key]); } return clone; }
- 浅拷贝:复制对象本身及直接属性,不复制原型链。
-
Object.create()
与new
的区别?new Constructor()
:创建实例并执行构造函数逻辑。Object.create(proto)
:仅创建一个以proto
为原型的对象,不执行构造函数。
-
JavaScript 中实现继承的方式有哪些?
- 原型链继承
- 构造函数继承(
call
/apply
) - 组合继承(原型链 + 构造函数)
- 寄生组合继承(ES6
class
的底层实现)
七、总结
原型链是 JavaScript 实现继承和代码复用的核心机制,理解其原理有助于:
- 掌握对象属性查找的底层逻辑。
- 避免原型共享导致的意外副作用。
- 理解 ES6 类的本质和继承实现。
- 优化代码性能和可维护性。
在实际开发中,应谨慎使用原型链,避免过度依赖,优先考虑更现代的继承方式(如 ES6 类)和组合模式。