JavaScript 中的继承总结

前言

由于 JavaScript 并不是那些“传统意义上”的面向对象的语言, 它在 ES5 中并没有类的概念, 没有真正意义上的继承, 但我们去可以进行模拟, 来实现所谓的继承。
在 JavaScript 中, 实现继承的过程中总感觉很有趣, 至少在 ES5 中是这样的, ES5 中自己亲自要通过原型链去实现继承, 而在 ES6 中则将这些需要自己写的封装成了一个接口, 其本质上还是一样, 都是通过原型链实现继承。


基于原型链的继承

JavaScript 中,原型对象的存在, 大体上有两个作用:一是实现资源共享, 二是实现继承。 说白了就是为了节省内存空间, 可以让其构造函数实例出来的对象实现资源共享。原型链实现继承的基本思路是:让“子类”的原型指向“父类”的实例对象, 这样就可以使用原型链来实现继承属性和方法。不多说,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 父类---人类
function Parson(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
Parson.prototype.eat = function() {
console.log("吃嘛嘛香");
};

// 子类---学生
function Student(score) {
this.score = score;
}

// 改变子类的原型指向,指向父类实例,实现继承
Student.prototype = new Parson("小谭", "男", 18);
// 子类自己的方法
Sutdent.prototype.learning = function() {
console.log("三更灯火五更鸡,正是男儿读书时");
};

var std = new Student(100);
console.log(std.name, std.sex, std.age, std.score); // 小谭 男 18 100
std.eat(); // 吃嘛嘛香
std.learning(); // 三更灯火五更鸡,正是男儿读书时

可以看到,“子类”改变原型的指向为父类的实例对象, 可以实现属性和方法的继承, 但这种有缺点, 就是继承的属性值会重复。


借用构造函数实现继承

借用构造函数实现继承, 就是在“子类”的构造函数中调用“父类”的构造函数, 借用 call() 或 apply() 方法在特定的环境内执行另一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 父类---人类
function Parson(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
Parson.prototype.eat = function() {
console.log("吃嘛嘛香");
};

// 子类---学生
function Student(name, sex, age, score) {
// 借用父类的构造函数
Parson.call(this, name, sex, age);
this.score = score;
}
// 子类自己的方法
Student.prototype.learning = function() {
console.log("三更灯火五更鸡,正是男儿读书时");
};

var std1 = new Student("小谭", "男", 18, 100);
var std2 = new Student("小林", "女", 20, 99);
console.log(std1.name, std1.sex, std1.age, std1.score); // 小谭 男 18 100
console.log(std2.name, std2.sex, std2.age, std2.score); // 小林 女 20 99
std1.learning(); // 三更灯火五更鸡,正是男儿读书时
std2.learning(); // 三更灯火五更鸡,正是男儿读书时

std1.eat(); // 报错!
std2.eat(); // 报错!

借用构造函数来继承, 可以解决原型链继承的属性重复问题, 但又有新的问题, 就是不能继承“父类”的方法。


组合继承

就单单使用原型链继承,和借用构造函数继承, 我们不会单一的去使用, 更多的是将二者结合起来, 各发挥其优点, 也就是组合继承。 原型继承实现方法的继承, 借用构造函数实现对实例属性的继承, 最为经典的一种继承方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 父类---人类
function Parson(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
Parson.prototype.eat = function() {
console.log("吃嘛嘛香");
};

// 子类---学生
function Student(name, sex, age, score) {
// 借用父类的构造函数
Parson.call(this, name, sex, age);
this.score = score;
}
// 改变子类原型指向父类实例
Student.prototype = new Parson();
// 子类自己的方法
Student.prototype.learning = function() {
console.log("三更灯火五更鸡,正是男儿读书时");
};

var std1 = new Student("小谭", "男", 18, 100);
var std2 = new Student("小林", "女", 20, 99);
console.log(std1.name, std1.sex, std1.age, std1.score); // 小谭 男 18 100
console.log(std2.name, std2.sex, std2.age, std2.score); // 小林 女 20 99
std1.learning(); // 三更灯火五更鸡,正是男儿读书时
std2.learning(); // 三更灯火五更鸡,正是男儿读书时

std1.eat(); // 吃嘛嘛香
std2.eat(); // 吃嘛嘛香


拷贝继承

拷贝继承, 则是利用 for in 将父类原型对象中的方法, 依次复制给一个新的对象。 利用循环继承原型方法, 再利用 call() 方法实现对属性的继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 父类---人类
function Parson(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
Parson.prototype.eat = function() {
console.log("吃嘛嘛香");
};

// 子类---学生
function Student(name, sex, age, score) {
// 借用父类的构造函数
Parson.call(this, name, sex, age);
this.score = score;
}
// 遍历父类原型对象, 并拷贝给子类原型对象
for (var key in Parson.prototype) {
Student.prototype[key] = Parson.prototype[key];
}
// 子类自己的方法
Student.prototype.learning = function() {
console.log("三更灯火五更鸡,正是男儿读书时");
};

var std1 = new Student("小谭", "男", 18, 100);
var std2 = new Student("小林", "女", 20, 99);
console.log(std1.name, std1.sex, std1.age, std1.score); // 小谭 男 18 100
console.log(std2.name, std2.sex, std2.age, std2.score); // 小林 女 20 99
std1.learning(); // 三更灯火五更鸡,正是男儿读书时
std2.learning(); // 三更灯火五更鸡,正是男儿读书时

std1.eat(); // 吃嘛嘛香
std2.eat(); // 吃嘛嘛香


结语

JavaScript 是基于原型的编程, 在 JavaScript 继承中, 原型贯穿着整个过程。 也由于原型的存在, 让 JavaScript 及其的灵活, 可以让我们实现一些面向对象的特征。 这几种继承的方式, 组合继承最为经典。

------ 本文结束 ------

版权声明

Tflin's Blog by Tan Feng Lin is licensed under a Creative Commons BY-NC-ND 4.0 International License.
谭丰林创作并维护的Tflin's Blog博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Tflin's Blog 博客( http://tflin.com ),版权所有,侵权必究。