JavaScript 常见 6 种继承方式,看看你会几种

深圳新闻 浏览(1264)

面向对象编程最重要的一个方面是对象的继承。通过继承B对象,A对象可以直接拥有B对象的所有属性和方法。这对于代码重用非常有用。

48e26e59-3423-4ad1-b109-e4266b52da96

大多数面向对象的编程语言通过“类”实现对象继承。传统上,JavaScript语言继承不是由类实现的(ES6引入了类语法),而是由“原型”实现。那么JS中常见的继承方法有多少?

方法1,原型链继承

这种方法的关键是:子类型的原型是父类型的实例。

//父类型

职能人(姓名,年龄){

This.name=name,

This.age=年龄,

This.play=[1,2,3]

this.setName=function(){}

}

Person.prototype.setAge=function(){}

//亚型

职能学生(价格){

This.price=价格

this.setScore=function(){}

}

Student.prototype=new Person()//子类型的原型是父类型的实例

Var s1=新学生(15000)

Var s2=新学生(14000)

CONSOLE.LOG(S1,S2)

f2fbbae5293846d598618cce718b43cf

但是这个实现的本质是通过将子类的原型子类化到父类的实例,可以通过proto__访问子类的实例到Student.prototype的实例,该实例是Person的实例,所以您可以访问父类。私有方法,然后__proto指向父类的原型,以获取父类原型的方法。父类的私有,公共方法和属性也是子类的公共属性。

子类继承父类的属性和方法是将父类的私有属性和公共方法作为自己的公共属性和方法,我们都知道操作时基本数据类型是操作的值,当操作引用时数据类型操作是地址。如果父类的私有属性中存在引用类型属性,则当子类继承它时,它将被视为公共属性,因此子类1在对此属性进行操作时将影响子类2。

S1.play.push(4)

Console.log(s1.play,s2.play)

Console.log(s1 .__ proto__===s2 .__ proto __)//true

Console.log(s1 .__ proto __.__ proto__===s2 .__ proto __.__ proto __)//true

f8ae8eccf7d74b2c891df363b16ecd98

s1中的play属性发生变化,同时s2中的play属性也会发生变化。

需要注意的另一点是我们需要向子类添加一个新方法或重写父类的方法,记得把它放在替换原型的语句之后

职能人(姓名,年龄){

This.name=name,

This.age=年龄

}

Person.prototype.setAge=function(){

CONSOLE.LOG( '111')

}

职能学生(价格){

This.price=价格

this.setScore=function(){}

}

//Student.prototype.sayHello=function(){} //这里编写子类的原型方法和属性无效,

//因为它会改变原型的方向,所以应该在重新分配后放置

Student.prototype=new Person()

Student.prototype.sayHello=function(){}

Var s1=新学生(15000)

CONSOLE.LOG(S1)

特性:

父类添加了一个新的prototype方法/prototype属性,并且可以访问子类,使其简单易用

缺点:

无法实现多重继承。原型对象的所有属性都由所有实例共享。创建子类实例时,不能将参数传递给父类构造函数。要向子类添加属性和方法,必须在Student.prototype=new Person()执行后,不能放在构造函数中

方法2:借用构造函数继承

这种方法的关键是:调用子类型构造函数call()中的父类型构造函数。

职能人(姓名,年龄){

This.name=name,

This.age=年龄,

this.setName=function(){}

}

Person.prototype.setAge=function(){}

职能学生(姓名,年龄,价格){

Person.call(this,name,age)//相当于: this.Person(姓名,年龄)

/*this.name=name

This.age=年龄*/

This.price=价格

}

Var s1=新学生('Tom',20,15000)

08ba121d30e2460db97d4fe7a82f3e7c

此方法只是部分继承。如果父类的原型具有方法和属性,则子类无法获取这些方法和属性。

Console.log(s1.setAge())//未捕获TypeError: s1.setAge不是函数

特性:

解决了子类类在原型链继承中共享共享父类引用属性的问题在创建子类实例时,可以将参数传递给父类来实现多重继承(调用多个父类对象)

缺点:

实例不是父类的实例。只有子类的实例才能继承父类的实例属性和方法。您不能继承原型属性和方法。您无法实现函数重用。每个子类都有父类实例函数的副本,这会影响性能。

模式3:原型链+借用构造函数组合继承

这种方法的关键是:继承父类的属性并通过调用父类构造保留参数的优点,然后通过将父类实例子类化为子类来实现函数重用。

职能人(姓名,年龄){

This.name=name,

This.age=年龄,

this.setAge=function(){}

}

Person.prototype.setAge=function(){

CONSOLE.LOG( '111')

}

职能学生(姓名,年龄,价格){

Person.call(this,name,age)

This.price=价格

this.setScore=function(){}

}

Student.prototype=new Person()

Student.prototype.constructor=Student //复合继承还需要修复指向

的构造函数

Student.prototype.sayHello=function(){}

Var s1=新学生('Tom',20,15000)

Var s2=新学生('Jack',22,14000)

CONSOLE.LOG(S1)

Console.log(s1.constructor)//学生

Console.log(p1.constructor)//人员

f9b8258711234b7682268d55139fae43

这种方法结合了原型链继承和构造函数的优点,是JavaScript中最常用的继承模式。但是,还有一个缺点是在任何情况下都会调用构造函数两次:一次创建子类型原型时,一旦在子类型构造函数内部,子类型最终将包含父类型对象的所有实例。属性,但我们必须在调用子类构造函数时覆盖这些属性。

优点:

您可以继承实例属性/方法,也可以继承原型属性/方法。没有参考属性共享问题。该参数可以重复使用

缺点:调用两个父类构造函数,并生成两个实例

方法4,组合继承优化1

这样父类原型和子类原型指向同一个对象,子类可以从父类的公共方法继承为自己的公共方法,并且不会初始化实例方法/属性两次,避免了组合继承的缺点。

职能人(姓名,年龄){

This.name=name,

This.age=年龄,

this.setAge=function(){}

}

Person.prototype.setAge=function(){

CONSOLE.LOG( '111')

}

职能学生(姓名,年龄,价格){

Person.call(this,name,age)

This.price=价格

this.setScore=function(){}

}

Student.prototype=Person.prototype

Student.prototype.sayHello=function(){}

Var s1=新学生('Tom',20,15000)

CONSOLE.LOG(S1)

1b43e2a47dae46bba7168a6e98533dce

但是这种方式无法判断对象是子类还是父类实例化。

Console.log(s1 instanceof Student,s1 instanceof Person)//true true

CONSOLE.LOG(s1.constructor)//人

优点:不会初始化实例方法/属性两次,避免组合继承的缺点

缺点:无法判断实例是由子类还是父类创建的。子类的构造函数和父类指向同一个。

方法5:组合继承优化2

原型可用于基于现有对象创建对象。 var B=Object.create(A)将A对象作为原型并生成B对象。 B继承了A的所有属性和方法。

职能人(姓名,年龄){

This.name=name,

This.age=年龄

}

Person.prototype.setAge=function(){

CONSOLE.LOG( '111')

}

职能学生(姓名,年龄,价格){

Person.call(this,name,age)

This.price=价格

this.setScore=function(){}

}

Student.prototype=Object.create(Person.prototype)//核心代码

Student.prototype.constructor=学生//核心代码

Var s1=新学生('Tom',20,15000)

Console.log(s1 instanceof Student,s1 instanceof Person)//true true

Console.log(s1.constructor)//学生

CONSOLE.LOG(S1)

同样,Student继承了Person原型对象的所有属性和方法。就目前而言,最完美的继承方式!

75df895da1fb4d2c93d6dff24e226f2d

方法6,ES6中类的继承

class关键字在ES6中引入。类可以通过extends关键字实现继承。它还可以通过static关键字定义类的静态方法。这比ES5继承原型链的实现更加清晰和方便。

ES5的继承本质上是先创建这个子类的实例对象,然后将父类的方法添加到此(Parent.apply(this))。 ES6的继承机制完全不同。本质上是首先将父实例对象的属性和方法添加到此(因此必须首先调用super方法),然后使用子类的构造函数对其进行修改。

应该注意的是,class关键字只是原型的语法糖,JavaScript继承仍然基于原型。

班级人员{

//调用类的构造函数

构造函数(名称,年龄){

This.name=name

This.age=年龄

}

//定义一般方法

showName(){

Console.log('调用父类的方法')

Console.log(this.name,this.age);

}

}

让p1=新人('kobe',39)

CONSOLE.LOG(P1)

//定义子类

班级学生延伸人{

构造函数(姓名,年龄,薪水){

超级(名称,年龄)//通过超级

调用父类的构造函数

This.salary=薪水

}

showName(){//在子类中自定义方法

Console.log('调用子类的方法')

Console.log(this.name,this.age,this.salary);

}

}

设s1=新学生('wade',38,1000000000)

CONSOLE.LOG(S1)

s1.showName()

78f11245ca274c37a50bfa412fa00954

优点:易于理解语法,更易于操作

缺点:并非所有浏览器都支持class关键字