JavaScript 是一门基于原型的语言,而不是像 Java、C++ 那样基于类的语言。尽管 ES6 引入了 class 语法,但它本质上只是原型继承的语法糖。理解原型和原型链,是掌握 JavaScript 面向对象编程的核心。本文将从最基础的概念出发,逐步深入,帮你彻底搞懂 JavaScript 的继承体系。
一、为什么需要原型?
假设我们要创建多个具有相同属性和方法的对象。如果用构造函数的方式:
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
}
const p1 = new Person('Alice', 25);
const p2 = new Person('Bob', 30);
这样每创建一个实例,都会重新创建一遍 sayHello 方法,造成内存浪费。而原型的作用,就是让所有实例共享同一份属性和方法。
二、prototype、proto 与 constructor
这三个概念是理解原型链的基石,先厘清它们的关系:
prototype:函数才拥有的属性,指向一个对象,这个对象就是通过该函数创建的实例的原型。__proto__:每个对象(包括函数)都拥有的属性,指向创建它的构造函数的prototype。constructor:原型对象上的属性,指回构造函数本身。
function Person(name) {
this.name = name;
}
const p = new Person('Alice');
console.log(Person.prototype); // 原型对象
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
三者的关系可以用一句话概括:实例的 __proto__ 指向构造函数的 prototype,而 prototype 的 constructor 又指回构造函数。
三、什么是原型链?
当我们访问一个对象的属性时,如果对象本身没有这个属性,JavaScript 引擎就会沿着 __proto__ 向上查找,直到找到该属性或到达 null 为止。这条查找路径就是原型链。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
const p = new Person('Alice');
p.sayHello(); // 实例本身没有 sayHello,沿原型链找到 Person.prototype 上的方法
完整的原型链终点是 Object.prototype,再往上就是 null:
p.__proto__ === Person.prototype; // true
Person.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
因此,p 可以访问 Person.prototype 和 Object.prototype 上的所有属性和方法,比如 toString、hasOwnProperty 等。
四、几种常见的继承方式
1. 原型链继承
function Parent() {
this.colors = ['red', 'blue'];
}
Parent.prototype.getColors = function() {
return this.colors;
};
function Child() {}
Child.prototype = new Parent();
const c1 = new Child();
const c2 = new Child();
c1.colors.push('green');
console.log(c2.colors); // ['red', 'blue', 'green'] —— 引用类型被共享
缺点:引用类型属性被所有实例共享,且无法向父类构造函数传参。
2. 构造函数继承
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
}
优点:可以传参,属性独立。缺点:无法继承父类原型上的方法。
3. 组合继承
结合前两者,属性用构造函数继承,方法用原型链继承:
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child; // 修复 constructor 指向
const c = new Child('Alice', 25);
c.sayName(); // Alice
缺点:父类构造函数被调用了两次。
4. 寄生组合继承(最优)
function inheritPrototype(Child, Parent) {
const prototype = Object.create(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
这是 ES5 中最理想的继承方式,只调用一次父类构造函数,且原型链保持完整。
五、ES6 class 与原型的关系
ES6 的 class 让继承写法更清晰:
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
}
但本质上,class 依然基于原型:Child.prototype.__proto__ === Parent.prototype,super 内部调用的也是 Parent.call(this) 的逻辑。所以理解原型链,才能真正理解 class 的工作机制。
六、总结
| 概念 | 说明 |
|---|---|
prototype |
函数特有,指向实例的原型对象 |
__proto__ |
所有对象都有,指向构造函数的 prototype |
constructor |
原型对象上的属性,指回构造函数 |
| 原型链 | 属性查找时沿 __proto__ 逐级向上,终点为 null |
一句话总结:JavaScript 的继承,本质就是通过 __proto__ 把对象串联成一条链,实现属性和方法的共享与复用。 掌握了原型链,你就掌握了 JavaScript 面向对象编程的灵魂。
未经允许不得转载:任鹏个人博客 » 从原型到原型链:彻底搞懂 JavaScript 的继承体系

