前言
由于 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) 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) console.log(std2.name, std2.sex, std2.age, std2.score) 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) console.log(std2.name, std2.sex, std2.age, std2.score) 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) console.log(std2.name, std2.sex, std2.age, std2.score) std1.learning() std2.learning()
std1.eat() std2.eat()
|
结语
JavaScript 是基于原型的编程, 在 JavaScript 继承中, 原型贯穿着整个过程。 也由于原型的存在, 让 JavaScript 及其的灵活, 可以让我们实现一些面向对象的特征。 这几种继承的方式, 组合继承最为经典。