JavaScript 原型链继承机制详解与实现

作者:青柠

时间:2026-10-28

浏览:183

本文深入解析 JavaScript 的继承机制,重点讲解基于原型链(prototype chain)的实现原理。通过对比传统面向对象语言,分析构造函数创建实例的局限性,并详细演示如何利用 prototype 属性、call 方法以及封装 extend 函数来实现子类对父类的属性与方法继承,旨在帮助开发者理解 JS 对象模型并优化代码复用。

文章目录

JavaScript 继承机制概述

本文通过具体实例深入解析 JavaScript 的继承机制,供读者参考学习。

对于初学者而言,理解 JavaScript 的继承机制往往颇具挑战。该语言并未采用传统面向对象语言中“子类”与“父类”或“类”与“实例”的概念,而是依赖一种独特的“原型链”(prototype chain)模式来构建继承关系。

在深入学习这一部分时,我曾投入大量时间并整理了许多笔记。然而,这些知识多属于机械记忆,未能从根本逻辑上实现透彻理解。

类的创建与原型链基础

一、如何创建一个类

假设存在一个名为 Person 的类,定义如下:

代码如下:

var Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name;
}

如上所示,Person 代表地球上的所有人,具备 name 和 age 两个基本属性。若我们要实现一个学生类(Students),已知学生也是人,因此同样拥有 name 和 age 属性。当前的核心问题是如何建立这种关联关系。


首先,我们观察纯面向对象语言(如 ActionScript 3)是如何处理这一问题的:


代码如下:

class Students extend Person {}; // 简洁的一行代码;确切地说是一个关键字 -- extend

二、在 JavaScript 中如何实现

在讲解 JavaScript 继承机制的具体实现之前,有必要先了解其核心机制:原型链。

代码如下:

var person = new Person('Poised-flw', 21);
person.getName(); // 返回 "Poised-flw"

以上述 getName() 方法的执行为例,其查找过程如下:首先在当前 Person 函数内部查找 getName() 方法,若未找到,则转向 Person.prototype 对象中查找。若找到,则调用该方法;若仍未找到,则继续沿着原型链向上查找,直到找到该方法或到达原型链顶端为止。

举例说明,假设有一个名为 DOG 的构造函数,用于表示狗对象的原型:

代码如下:

function DOG(name){
this.name = name;
  }

对该构造函数使用 new 运算符,即可生成一个狗对象的实例。


代码如下:

var dogA = new DOG('大毛');
alert(dogA.name); // 输出 '大毛'

请注意构造函数中的 this 关键字,它代表新创建的实例对象。

构造函数的局限性分析

三、new 运算符的局限性

使用构造函数生成实例对象存在一个显著缺点:无法共享属性和方法。例如,在 DOG 对象的构造函数中设置一个共有属性 species:

代码如下:

function DOG(name){
this.name = name;
this.species = '犬科';
  }

接着,生成两个实例对象:


代码如下:

var dogA = new DOG('大毛');
var dogB = new DOG('二毛');

这两个对象的 species 属性是相互独立的,修改其中一个的值不会影响另一个。


代码如下:

dogA.species = '猫科';
alert(dogB.species); // 显示 '犬科',不受 dogA 修改的影响

每个实例对象都拥有自己属性和方法的副本。这不仅导致数据无法共享,也造成了极大的资源浪费。

因此,继承的核心思想是:利用 JavaScript 特有的原型链机制来实现继承!

基于原型链的继承实现

四、基于原型链的继承实现

1. 直接继承方式

代码如下:

var Students = function(name, age, sid) {
Person.call(this, name, age);
this.sid = sid;
}
Students.prototype = new Person(); // 将 Person 置于 Students 的原型链上以实现继承
Students.prototype.constructor = Students;
Students.prototype.getResults = function() {
// 获取学生成绩
}

务必保留 Students.prototype.constructor = Students 这一行代码!在定义构造函数时,其默认的 prototype 是一个 Object 实例,且 prototype 的 constructor 属性会自动指向该函数本身。若手动将 prototype 设置为另一个对象,新对象将不再具备原对象的 constructor 值,因此需要重新设置其 constructor 属性。例如:


代码如下:

var Test = function() {
this.time = "now";
}
console.log(Test.prototype); // 输出 Object {},一个空对象
console.log(Test.prototype.constructor); // 输出 function() {this.time = "now";}, 即函数本身
// 若手动修改 Test 的 prototype 属性
Test.prototype = {
someFunc: function() {
console.log('hello world!');
    }
};
console.log(Test.prototype.constructor); // 输出 function Object() { [native code] }
// 此时你会发现 constructor 指向错误,因此在手动更改 prototype 属性时,必须同时修正其 constructor 指向。

通过上述测试即可理解为何需要修改 constructor 值。

2. 封装继承函数 extend

代码如下:

function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
}

该函数的功能仅是对上述继承过程的封装,其不同之处在于:


仅继承了 superClass 的 prototype 属性,并未继承 superClass 构造函数内部的属性;


这样做的好处在于:减少了 new 一个构造函数的开销!


当然,随之而来的问题是:仅靠此函数无法让 subClass 继承 superClass 的所有属性。


改进方案:


代码如下:

// 在 Students 构造函数中继续添加一行代码:
Person.call(this, name, age);

五、小结

利用 JavaScript 的原型链原理,我们可以轻松实现继承机制。尽管这种实现方式并非绝对严格,但达到了我的目的:尽量让重复代码只出现一次!

希望本文所述内容能对大家的 JavaScript 程序设计有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章