本文深入解析 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
以上就是本文的全部内容了,希望大家能够喜欢