JavaScript 继承机制详解:原型链与构造函数绑定

作者:异步漫步

时间:2026-10-22

浏览:331

本文深入解析 JavaScript 的继承实现方式,涵盖对象定义、构造函数模式及原型链机制。重点讲解如何利用 prototype 实现代码复用,通过 apply 和 call 方法在构造函数内部绑定父类成员,并演示多重继承的实现技巧,帮助开发者理解无类语言下的面向对象模拟。

文章目录

引言

近期在前端开发工作中,考虑到过往主要从事 .NET 和 PHP 开发,前端社区倾向于直接使用现成库。然而多次实践表明,若不明原理,连修改代码都难以胜任。因此,我决定深入研究 JavaScript 的几个核心技术难点。

JavaScript 对象与构造函数基础

0x1.JavaScript的对象和构造函数

在 JavaScript 中,可以通过字面量方式定义对象。


var a = {
	x : 1,
	y : 2,
	add : function () {
		return this.x + this.y;
	},
	mul : function () {
		return this.x * this.y;
	}
}

上述代码定义了一个变量 a,该变量包含 x 和 y 两个公有成员,以及 add 和 mul 两个公有方法。但这种定义方式存在两个主要缺陷:

1. 批量生成对象极为不便。若执行 var b=a;,随后修改 b 的成员,a 的成员也会随之改变,这是由 JavaScript 的引用机制决定的。

2. 若每次生成对象需自定义特定成员,均需编写相应的赋值操作,导致代码行数增加。

因此,在定义 JavaScript 对象前,建议先定义一个构造函数。


function A(x, y) {
	this.x = x;
	this.y = y;
	this.add = function () {
		return this.x + this.y;
	}
	this.mul = function () {
		return this.x * this.y;
	}
}

随后,通过构造函数定义一个对象。


a = new A(1, 2);

上述代码看似简单,但需与 C++ 等面向对象语言区分。A 并非严格意义上的“类”,因为 JavaScript 没有类的概念,此处仅是调用了构造函数。

接下来探讨如何实现继承。C++ 清晰实现了封装、继承和多态三大面向对象特征。而 JavaScript 作为一种较为宽松的语言,缺乏严格的继承机制,通常采用以下几种方式来模拟继承。

基于 Prototype 的继承机制

0x2.JavaScript的prototype

为了讲清后续的 apply 或 call 函数,此处先引入 prototype。prototype 是 Function 类型特有的属性。

要善用继承,首先需理解其设计初衷:无非是将“公共部分”提取出来,以实现代码复用。

因此,在 JavaScript 中,公共部分通常放置在 Function 的 prototype 中。

下面比较两个利用 prototype 实现继承的例子。


function A(x, y) {
	this.x = x;
	this.y = y;
	this.add = function () {
		return this.x + this.y;
	}
	this.mul = function () {
		return this.x * this.y;
	}
}

function B(x,y){
	
}

B.prototype=new A(1,2);

console.log(new B(3,4).add());  //3

在此例中,子类的 prototype 指向一个 A 类对象。

接着实现一个 B 继承 A 的例子:


function A() {
	
}

A.prototype = {
	x : 1,
	y : 2,
	add : function () {
		return this.x + this.y;
	},
	mul : function () {
		return this.x * this.y;
	}
}
A.prototype.constructor=A;

function B(){
	
}

B.prototype=A.prototype;
B.prototype.constructor=B;

B 的 prototype 对象引用了 A 的 prototype 对象。由于是引用关系,修改 B 的 prototype 对象时,A 的 prototype 对象也会随之改变,因为二者本质上指向同一块内存。因此,每次改动 B 类型的 prototype 时,都需手动将 constructor 改回,以防逻辑错乱。相比前例,上一例因未涉及引用,故无此问题。

创建一个 B 类型的对象。


b=new B();

b 对象具备 A 类型的所有成员。


console.log(b.add());  //3

由于每个 prototype 对象均包含两个重要成员:constructor 和 _proto_。constructor 本质上是一个函数指针,执行 B.prototype=A.prototype 后,会覆盖原有的 constructor,因此后续需让 constructor 重新指向 B 类型的构造函数。

构造函数绑定与多重继承

0x3.JavaScript的构造函数绑定

在定义 A 类型的构造函数后,再定义 B 类型,并在 B 类型构造函数内部,“嵌入执行” A 类型的构造函数。


function A(x, y) {
	this.x = x;
	this.y = y;
	this.add = function () {
		return this.x + this.y;
	}
	this.mul = function () {
		return this.x * this.y;
	}
}

function B(x, y, z) {
	A.apply(this, arguments);
	this.z = z;
}

console.log(new B(1,2,3));

apply 函数与 call 函数基本一致,可在 B 类型构造函数内部执行 A 类型构造函数,同时继承 A 的所有成员。

显示结果:

引言

此处提供一个公式:在 B 构造函数中编写 A.apply(this),可使 B 构造出的对象拥有 A 构造函数中的所有成员。

谈及 apply 和 call,还可实现多重继承。


function IA(){
	this.walk=function(){
		console.log("walk");
	}
}

function IB(){
	this.run=function(){
		console.log("run");
	}
}

function Person(){
	IA.apply(this);
	IB.apply(this);
}

var p=new Person();
p.walk();  //walk    
p.run();  //run

以上就是本文的全部内容了,希望大家能够喜欢

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章