JavaScript 类的继承

《JavaScript 设计模式》总结

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
// 类式继承

// 声明父类
function SuperClass() {
this.superValue = true
}

// 为父类添加共有方法
SuperClass.prototype.getSuperVlaue = function() {
return this.superValue
}

// 声明子类
function SubClass() {
this.subValue = false
}

// 继承父类
SubClass.prototype = new SuperClass()

//为子类添加共有方法
SubClass.prototype.getSubValue = function() {
return this.subValue
}

新创建的对象复制了父类的构造函数内的属性与方法并且将原型 __proto__ 指向了父类的原型对象,这样就拥有了父类的原型对象上的属性和方法,并且这个新创建的对象可直接访问到父类原型对象上的属性与方法。

弊端

  • 父类的共有属性要是引用类型,就会在子类中被所有实例共有,因此一个子类的实例更改子类型从父类构造函数中继承来的共有属性就会直接影响到其他子类
  • 子类的实现的继承是靠其原型 prototype 对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因此在实例化父类的时候也无法对父类构造函数内的属性进行初始化

构造函数式继承(创建即继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 声明父类
function SuperClass(id) {
// 引用类型共有属性
this.books = ["JavaScript", "html", "css"]
// 值类型共有属性
this.id = id
}

// 父类声明原型方法
SuperClass.prototype.showBooks = function() {
console.log(this.books)
}

function SubClass(id) {
//继承父类
SuperClass.call(this, id)
}

// 创建第一个子类的实例
var instance1 = newSubClass(10)

// 创建第二个子类的实例
var instance2 = newSubClass(10)

分析
SuperClass.call(this, id) 这是构造函数式继承的精华,由于 call 这个方法可以改变函数的作用域,因此在子类中,对 SuperClass 调用这个方法就是将子类的变量在父类中执行一遍,由于父类是给 this 绑定属性的,那么子类自然也就继承了父类的共有属性。

弊端
这种继承方式没有涉及原型 prototype, 所以父类的原型方法自然不会被子类继承,而如果想要被子类继承就必须放在构造函数中,这样的创建方式的每个实例都会单独的拥有一份而不能共用,也就违背了代码复用的原则。

组合继承

接上文,类式继承是通过子类原型的 prototype 对父类实例化来实现的,构造继承是通过在子类的构造函数作用环境中执行了一次父类的构造函数来实现的,组合继承整合这两点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function SuperClass(name) {
this.name = name
//引用类型的共有属性
this.books = ["JavaScript", "html", "css"]
}
// 父类原型共有方法
SuperClass.prototype.getName = function() {
console.log(this.name)
}
// 声明子类
function SubClass(name, time) {
SuperClass.call(this, name)
this.time = time
}
// 类式继承 子类原型继承父类
SubClass.prototype = new SuperClass()
// 子类原型方法
SubClass.prototype.getTime = function() {
console.log(this.name)
}

测试代码

弊端
在使用构造函数继承是执行了一遍父类的构造函数,而在实现子类原型的类式继承又调用了一遍父类的构造函数,因此父类的构造函数调用了两遍。造成不必要的资源浪费。

原型式继承

借助原型 prototype 可以根据已有的对象创建一个新的对象,同时不必创建新的自定义对象类型。 – 道格拉斯·克罗克福德

1
2
3
4
5
6
7
8
9
10
// 原型式继承
function inheritObject(o) {
//声明一个过渡函数对象
function F () {
// 过渡对象的原型继承父对象
F.prototype = o;
//返回过渡对象的一个实例,该实例的原型继承了父对象
reutrn new F()
}
}

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//声明基对象
var book = {
name : 'js book',
alikeBook = ['css book', 'html book']
}

function createBook(obj){
// 通过原型继承方式创建对象
var o = new inheritObject(obj);
o.getName = function () {
console.log(name);
}

//返回扩展后的对象
return o;
}

寄生式继承其实就是对原型继承的二次封装,并且在第二次封装过程中对继承的对象进行了扩展,这样新创建的对象不仅仅有父类中的属性和方法而且添加新的属性和方法。

寄生组合式继承(目前最好的继承实现)

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 解决2.4中组合继承问题: 在使用构造函数继承时执行了一遍父类的够赞方法,而在实例化子类的时候又执行了一遍父类的方法
*
* 《JavaScript中原型式继承》:借助原型 prototype 已有的对象创建一个新的对象,同时不必创建新的自定义对象类型
*/

/**
* 寄生组合继承
*/

/**
* 上面的函数得到的对象F,拥有了对象o的全部属性(在原型链上),而修改F的属性,不会影响到o,相当于把o复制了一份。
* F 对象拥有父对象的所有方法(在原型链上)
*/
function inheritObject(o) {
//生命一个过渡函数对象
function F() {}
//过渡对象的原型式父对象
F.prototype = o
//返回过渡对象的一个实例,该实例的原型继承了父对象
return new F()
}

function inheritPrototype(subClass, superClass) {
// 复制一份父类的原型副本保存在变量中, 复制了父类原型的所有属性和方法
var p = inheritObject(SuperClass.prototype)
//修正因为重写子类原型导致子类的 constructor 属性被修改
p.constructor = subClass
//设置子类原型
subClass.prototype = p
}

// 定义父类
function SuperClass(name) {
this.name = name
this.colors = ["red", "black"]
}

//定义父类原型方法
SuperClass.prototype.getName = function() {
console.log(this.name)
}

// 定义子类
function SubClass(name, time) {
//构造函数式继承
SuperClass.call(this, name)
//子类新增属性
this.time = time
}

//寄生式继承父类原型
inheritPrototype(SubClass, SuperClass)
//子类新增原型方法
SubClass.prototype.getTime = function() {
console.log(this.name)
}

// 创建两个测试方法
var instance1 = new SubClass("js book", 2014)
var instance2 = new SubClass("css book", 2013)

多继承

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
/**
* JavaScript 实现多继承
*/
Object.prototype.mix = function() {
var i = 1,
len = arguments.length,
arg

//遍历被继承的对象
for (; i < len; i++) {
//缓存当前对象
arg = arguments[i]
for (var property in arg) {
this[property] = arg[property]
}
}
}

var book = {
name: "JavaScript",
alike: ["css", "js"]
}

var anotherBook = {
color: "red"
}

var newBook = {}

newBook.mix(newBook, anotherBook, book)

多态

ES6 class 实现继承

class 声明创建一个基于原型继承的具有给定名称的新类。

你也可以使用类表达式定义类。但是不同于类表达式,类声明不允许再次声明已经存在的类,否则将会抛出一个类型错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SuperClass {
constructor(width, height) {
this.width = width
this.height = height
this.area = width * height
}
}

class SubClass extends SuperClass {
constructor(length) {
super(length, length)
this.title = "hh"
}
}

var subclass = new SubClass(12)

console.log(subclass.area)