从原型到原型链:彻底搞懂 JavaScript 的继承体系

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,而 prototypeconstructor 又指回构造函数。

三、什么是原型链?

当我们访问一个对象的属性时,如果对象本身没有这个属性,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.prototypeObject.prototype 上的所有属性和方法,比如 toStringhasOwnProperty 等。

四、几种常见的继承方式

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.prototypesuper 内部调用的也是 Parent.call(this) 的逻辑。所以理解原型链,才能真正理解 class 的工作机制。

六、总结

概念 说明
prototype 函数特有,指向实例的原型对象
__proto__ 所有对象都有,指向构造函数的 prototype
constructor 原型对象上的属性,指回构造函数
原型链 属性查找时沿 __proto__ 逐级向上,终点为 null

一句话总结:JavaScript 的继承,本质就是通过 __proto__ 把对象串联成一条链,实现属性和方法的共享与复用。 掌握了原型链,你就掌握了 JavaScript 面向对象编程的灵魂。

未经允许不得转载:任鹏个人博客 » 从原型到原型链:彻底搞懂 JavaScript 的继承体系

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏