curTain

在 es6 之前,javaScript 不能使用 extends 来显式继承的,所以,继承需要程序员手动完成。

那么,让我们来看一看 js 中的各种继承方式吧。

前置知识

原型链

我们需要了解原型和原型链,需要知道构造函数与原型对象之间的关系,需要知道原型对象是一个对象,它有个属性 constructor 就是构造函数

Object.creact() 方法

传入原型对象,返回原型对象和属性。

1. 原型链继承

原理说明:

就是将已经 new 出来的对象实例的 prototype 变成父对象,从而,把父对象的属性和方法都继承到了当前对象的 prototype 上,当前对象的实例本身没有父对象的属性与方法,当调用这些方法时,他会自动到此对象的 prototype 上依次去找,直到找到 null 为止,这就称为原型链继承。

实现

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 Person( name="tan", age=18 ){
this.name = name
this.age = age
this.run = function(){
console.log(`${ this.name } can run...`)
}
}
Person.prototype.getName = function(){
return this.name
}
function Student( id = 12 ){
this.id = id
this.study = function(){
console.log( `${name} can study...` )
}
}
// 修改原型对象,并改回构造函数
Student.prototype = new Person()
Student.prototype.constuctor = Student

var S = new Student( 12 )
console.log( "实例S:", S )
S.run()
console.log( S instanceof Student )

运行结果:

1.1 总结:

由上面的例子可见,当继承的对象需要传参时,极度不灵活,因为 prototype 是一个父类的实例。

故:此继承方法适合继承方法,而不适合继承属性。

2. 借用构造函数继承

原理说明:

在构造函数中,改变父类构造函数的 this 并执行构造函数。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person( name="tan", age=18 ){
this.name = name
this.age = age
this.run = function(){
console.log(`${ this.name } can run...`)
}
}
Person.prototype.getName = function(){
return this.name
}
function Student( id = 12, name, age ){
this.id = id
this.study = function(){
console.log( `${name} can study...` )
}
// 改变父类构造函数并传参执行 也可以使用 apply 改变 this
Person.call( this, name, age )
}

var S = new Student( 12, "yu", 12 )
console.log( "实例S:", S )
S.run()
console.log( S instanceof Student )

运行结果:

总结:

可以传参给父类构造函数了,但是此方法不能继承父类原型链( prototype )上的方法和参数。

3. 组合继承

原理说明:

将上面两种方法结合起来,目的是继承到父类原型链上的方法。

实现:

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 Person( name="tan", age=18 ){
this.name = name
this.age = age
this.run = function(){
console.log(`${ this.name } can run...`)
}
}
Person.prototype.getName = function(){
return this.name
}
function Student( id = 12, name, age ){
this.id = id
this.study = function(){
console.log( `${name} can study...` )
}
// 改变父类构造函数并传参执行
Person.call( this, name, age )
}
// 继承父类原型链
Student.prototype = new Person()
Student.prototype.constuctor = Student

var S = new Student( 12, "yu", 17 )
console.log( "实例S:", S )
console.log( "getName:", S.getName() )
console.log( S instanceof Student )

运行结果:

总结:

很好的继承了父类,并继承了原型链,但是多了一些属性在原型链上。下面我们将解决这个问题。

4. 组合继承变种——借用原型链继承

原理说明:

将子类的原型链指向父类的原型链,达到继承父类原型链的目的:

实现:

修改组合继承的代码:
1
2
3
4
5
6
7
// 继承父类原型链
Student.prototype = new Person()
Student.prototype.constuctor = Student

// 修改为:
Student.prototype = Object.create( Person.prototype )
Student.prototype.constuctor = Student

运行结果:

总结:

完美的继承了父类,干净利索。

5. ES6 类继承 extends

实现说明:
extends 关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中 constructor 表示构造函数,一个类中只能有一个构造函数,有多个会报出 SyntaxError 错误,如果没有显式指定构造方法,则会添加默认的 constructor 方法,使用 super 方法传参到父类构造函数。

实现:

es6 extends 继承
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
class Person{
constructor( name="tan", age=18 ){
this.name = name
this.age = age
}
run = function() {
console.log(`${ this.name } can run...`)
}
getName = function (params) {
return this.name
}
}

class Student extends Person{
constructor( id, name, age ){
super( name, age )
this.id = id
}
study = function(){
console.log( `${name} can study...` )
}
}
var S = new Student( 12, "yu", 17 )
console.log( "实例S:", S )
console.log( "getName:", S.getName() )
console.log( S instanceof Student )

运行结果:

总结:

简单、完美

疑问:

在第四种继承方式中,为什么不能直接改成 Student.prototype = Person.prototype,而要使用 Object.create( Person.prototype ),

代码:

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
function Person( name="tan", age=18 ){
this.name = name
this.age = age
this.run = function(){
console.log(`${ this.name } can run...`)
}
}
Person.prototype.getName = function(){
return this.name
}
function Student( id = 12, name, age ){
this.id = id
this.study = function(){
console.log( `${name} can study...` )
}
// 改变父类构造函数并传参执行
Person.call( this, name, age )
}
Student.prototype = Person.prototype
Student.prototype.constuctor = Student

var S = new Student( 12, "yu", 17 )
console.log( "实例S:", S )
console.log( "getName:", S.getName() )
console.log( S instanceof Student )

结果是:

如果你知道原因,请在下方评论区给我留言。感谢!

总结:

尽管继承方式五花八门,但是继承的目的却是不变的:

  1. 将父类属性和方法复制到当前类 ( 在构造函数内改变父类构造函数的this并执行 )
  2. 继承父类的原型链 ( 修改构造函数的 prototype 指向父类的 prototype )

参考:

看了都知道继承也就那么回事儿

JavaScript常用八种继承方案


 评论