深入理解 JavaScript 原型链与继承机制:从 instanceof 到多种继承模式
在 JavaScript 的面向对象编程(OOP)体系中,原型(prototype)和原型链 是核心机制。不同于 Java、C++ 等基于类的语言,JavaScript 采用基于原型的继承模型 。理解这一机制,不仅能正确使用 instanceof 运算符,还能灵活实现各种继承方式。本文将从原型链本质出发,手写 instanceof,并系统梳理三种经典继承模式。
一、原型与原型链:JavaScript OOP 的基石
每个 JavaScript 对象都有一个内部属性 [[Prototype]](可通过 __proto__ 访问),它指向另一个对象------即该对象的"原型"。当访问一个对象的属性时,若自身没有,引擎会沿着原型链向上查找,直到 null。
ini
const arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(arr.__proto__.__proto__.__proto__ === null); // true
这条从实例 → 构造函数的 prototype → 更上层原型 → null 的链条,就是原型链。
constructor 属性的作用
每个原型对象默认有一个 constructor 属性,指回其构造函数:
ini
arr.constructor === Array; // true
Array.prototype.constructor === Array; // true
二、手写 instanceof:理解其本质
A instanceof B 的语义是:判断构造函数 B 的 prototype 是否出现在 A 的原型链上。
我们可以手动实现:
ini
function isInstanceOf(left, right) {
let proto = left.__proto__;
while (proto) {
if (proto === right.prototype) {
return true;
}
proto = proto.__proto__; // 向上遍历原型链
}
return false;
}
验证示例
javascript
function Animal() {}
function Dog() {}
Dog.prototype = new Animal();
const dog = new Dog();
console.log(isInstanceOf(dog, Dog)); // true
console.log(isInstanceOf(dog, Animal)); // true(因为 Dog.prototype = new Animal())
console.log(isInstanceOf(dog, Object)); // true(所有对象最终继承自 Object)
✅ 这说明
instanceof是基于血缘关系(原型链) 的判断,而非简单的类型匹配。
三、JavaScript 继承的三种经典模式
继承的本质是:子类能访问父类的属性和方法。由于 JS 没有类(ES6 之前),我们通过函数和原型模拟继承。
模式一:构造函数绑定继承(借用构造函数)
通过 call 或 apply 在子类构造函数中调用父类构造函数,实现属性继承。
ini
function Animal() {
this.species = '动物';
}
function Cat(name, color) {
Animal.call(this); // 借用父类构造函数
this.name = name;
this.color = color;
}
const cat = new Cat('小黑', '黑色');
console.log(cat.species); // '动物'
优点:
- 每个实例拥有独立的父类属性(避免引用共享问题)
- 可以向父类构造函数传参
缺点:
- 无法继承父类原型上的方法
- 父类构造函数每次都会执行,浪费性能(若父类有复杂初始化)
❌ 此模式仅实现"属性继承",未实现"方法继承"。
模式二:原型链继承(prototype 模式)
将父类的实例 赋值给子类的 prototype,使子类原型链指向父类实例。
ini
function Animal() {
this.species = '动物';
}
Animal.prototype.say = function() { console.log('I am an animal'); };
function Cat(name, color) {
this.name = name;
this.color = color;
}
// 关键:子类原型 = 父类实例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; // 修复 constructor 指向
const cat = new Cat('小黑', '黑色');
console.log(cat.species); // '动物'
cat.say(); // 'I am an animal'
优点:
- 子类可继承父类属性 + 原型方法
缺点:
- 所有子类实例共享父类实例的属性(若属性是引用类型,会互相影响)
- 无法向父类构造函数传参(
new Animal()无参数)
✅ 这是真正意义上的"原型链继承",但存在共享状态风险。
模式三:组合继承(推荐)
结合前两种模式:构造函数继承属性 + 原型链继承方法。
ini
function Animal(name) {
this.species = '动物';
this.name = name;
}
Animal.prototype.say = function() {
console.log(`I am ${this.name}`);
};
function Cat(name, color) {
Animal.call(this, name); // 继承属性(可传参,独立副本)
this.color = color;
}
// 继承方法
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
const cat1 = new Cat('咪咪', '白色');
const cat2 = new Cat('小黑', '黑色');
优点:
- 属性独立,方法共享
- 支持传参
- 符合 OOP 直觉
缺点:
- 父类构造函数被调用了两次(一次在
new Animal(),一次在Animal.call(this))
💡 尽管有小瑕疵,这是 ES5 时代最常用的继承模式。
模式四:寄生组合继承(优化版)
为避免组合继承中父类构造函数重复调用,引入"空中介对象":
ini
function inheritPrototype(Child, Parent) {
const prototype = Object.create(Parent.prototype); // 创建空对象,原型指向 Parent.prototype
prototype.constructor = Child;
Child.prototype = prototype;
}
function Animal(name) {
this.name = name;
}
Animal.prototype.say = function() { console.log(this.name); };
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
inheritPrototype(Cat, Animal); // 关键优化
const cat = new Cat('小花', '橘色');
cat.say(); // '小花'
优点:
- 只调用一次父类构造函数
- 原型链完整,
instanceof和isPrototypeOf正常工作
✅ 这是 ES5 中最理想的继承方式 ,也是
class extends的底层原理之一。
四、错误示范:直接继承 prototype
ini
Cat.prototype = Animal.prototype; // 危险!
这会导致父子类共享同一个原型对象,修改子类原型会影响父类:
javascript
Cat.prototype.meow = function() {};
console.log(Animal.prototype.meow); // function() {} ← 父类被污染!
❌ 绝对不要这样做!
五、为什么 instanceof 在大型项目中很重要?
在多人协作或复杂框架中,对象来源多样,类型模糊。instanceof 能可靠判断对象"血缘":
javascript
if (obj instanceof Array) { ... }
if (element instanceof HTMLElement) { ... }
相比 typeof(对数组、null 返回 "object")或 constructor(易被覆盖),instanceof 基于不可篡改的原型链,更安全可靠。
六、总结
| 继承模式 | 属性继承 | 方法继承 | 传参 | 属性独立 | 推荐度 |
|---|---|---|---|---|---|
| 构造函数绑定 | ✅ | ❌ | ✅ | ✅ | ⭐⭐ |
| 原型链继承 | ✅ | ✅ | ❌ | ❌ | ⭐ |
| 组合继承 | ✅ | ✅ | ✅ | ✅ | ⭐⭐⭐ |
| 寄生组合继承 | ✅ | ✅ | ✅ | ✅ | ⭐⭐⭐⭐ |
掌握原型链和 instanceof 的本质,是理解 JavaScript OOP 的关键。虽然 ES6 引入了 class 语法糖,但其底层仍是原型机制。深入这些原理,才能写出健壮、可维护的代码。