JS中的继承

JS中的继承

1.ES5继承

①原型链继承

原型链继承的核心思想,就是将父亲的实例作为子类的原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义父类
function Parent(){
this.name = 'parent'
}
// 在父类的原型上定义方法
Parent.prototype.getName = function(){
return this.name;
}
// 定义子类
function Child(){
this.name = "child"
}
// 子类的原型=父亲的实例
Child.prototype = new Parent();

// 实例化子类
var Child1 = new Child()
console.log(Child1.getName()); // child

缺点:

在使用原型链继承时,父类的引用数据类型(对象、数组)会被子类共享,子类实例数据更改,其他子类也会受到变化。并且子类实例不能给父类构造函数传参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Parent(){
this.name = [1,2,3]
}
// 在父类的原型上定义方法
Parent.prototype.getName = function(){
return this.name;
}
// 定义子类
function Child(){
}
// 子类的原型=父亲的实例
Child.prototype = new Parent();

// 实例化子类
var Child1 = new Child()
var Child2 = new Child()
Child1.name.push(4)
console.log(Child1.name); // [1, 2, 3, 4]
console.log(Child2.name); // [1, 2, 3, 4] child2.name受到改变

tips:引用类型在JavaScript中是按引用传递的,也就是说当我们将一个引用类型赋值给另一个变量时,实际上是将引用地址传递给了新的变量,而不是将实际的对象复制一份。因此,通过一个变量修改引用类型的属性时,其他引用该对象的变量也会反映出这个修改。

当使用原型链继承时,子对象会通过原型链继承父对象的属性和方法,这些属性和方法在子对象中只是指向同一个引用。所以,如果我们修改了子对象中的引用类型属性,其实是修改了原型链上的那个引用,因此其他通过原型链继承的对象也会受到影响。

②构造函数继承

构造函数继承的思想:在子类构造函数中调用父类构造函数,使用apply()或者call()方法创建新的对象为上下文执行构造函数。等于是复制父类的实例属性给子类(没用到原型)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 构造函数继承
function Parent(arg){
this.name = arg
this.arr = [1,2,3]
}
// 在父亲原型上定义方法
Parent.prototype.getName = function(){
return this.name;
}
// 定义子类
function Child(){
// 使用call方法
Parent.call(this,'参数')
}

// 实例化子类
var Child1 = new Child()
var Child2 = new Child()
console.log(Child1.name); //-->'参数'
Child2.arr.push(4)
console.log(Child1.arr); // [1, 2, 3, 4]
console.log(Child2.arr); // [1, 2, 3]
console.log(Child1.getName()); // 报错

使用构造函数继承:父类的引用类型的数据不会被子类共享,并且可以传递参数。

缺点:子类不能访问父类原型属性上的方法和参数。

③组合继承

组合继承综合了原型链和构造函数,基本思路:使用原型链来继承父类原型上的属性和方法,使用构造函数继承实例属性。

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 Parent(arg){
this.name = arg
this.arr = [1,2,3]
}
// 在父亲原型上定义方法
Parent.prototype.getName = function(){
console.log('父亲原型上的方法');
}
// 定义子类 通过构造函数继承实例
function Child(){
Parent.call(this,'参数')
}
// 通过原型链继承父类原型上的属性方法(子类的原型等于父类的实例)
Child.prototype = new Parent()
// 实例化子类
var Child1 = new Child()
var Child2 = new Child()

console.log(Child1);
console.log(Child1.name);
Child2.arr.push(4)
console.log(Child1.arr); // [1, 2, 3, 4]
console.log(Child2.arr); // [1, 2, 3]
console.log(Child1.getName() ); //-->打印'父亲原型上的方法'

使用组合继承,综合了原型链继承和构造函数继承的优点,父类中的引用类型数据不会被子类共享,子类可以传参,也可以获取父类原型上的属性和方法。

缺点:出现效率问题,会调用两次父类的构造函数,一次是在创建子类原型时,一次是在子类构造函数时,会有两份一样的属性方法。

我们打印子类实例console.log(Child1);

组合继承缺点

④原型式继承

原型式继承首先了解ES5里Object.create方法,第一个参数是作为新对象原型的对象,第二个可选参数是给新对象定义额外属性对象。本质是对第一个参数对象的浅复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 //  原型式继承
let Parent = {
name:'Pa',
arr: [1,2,3,4],
printArr:function(){
console.log(this.arr);
}
}

let Child = Object.create(Parent,{
name:{
value:'pa2'
}
})
Child.printArr() //[1,2,3,4]
console.log(Child.name); // pa2

缺点:

  • 父类的引用属性会被所有子类实例共享
  • 子类构建实例时不能向父类传递参数

适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。

⑤寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function  createAnother(original){
let clone = Object(original)
clone.sayHi= function(){
console.log("Hi");
}
return clone
}

let person = {
name:'he',
age:18
}
let person1 = createAnother(person)
console.log(person1)

通过寄生式继承给对象添加函数会导致函数难以复用。

⑥寄生式组合继承

寄生式组合继承通过构造函数继承属性,但使用混合式原型链继承方法。基本思路:不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。

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 Father(name){
this.name = name
}

// 父类原型
Father.prototype.sayHi = function(){
console.log(`你好,我叫${this.name}`);
}

function Child(name){
Father.call(this,name)
}

// 基于父类原型创建一个新的原型对象,原型的构造函数指向子类
const prototype = Object.create(Father.prototype,{
construcor:{
value:Child
}
})
// 新的原型对象赋值给子类的原型,
Child.prototype=prototype

let Child1 = new Child('111')

组合式继承:借用构造函数,原型链

寄生:父亲的原型中,有子类的构造函数。

寄生式组合继承

只调用一次父类构造函数,避免Child.prototype上重复的属性和方法,效率高,原型键也保持不变。

2.ES6类的继承

①class

ECMAScript6新引入class关键字具有正式定义类的能力,实际背后使用的仍然是原型和构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
// 公有属性
name
age=18
// 构造函数
constructor(name){
// 构造函数内部的this 就是实例化对象
this.name = name
// 可以动态添加属性,但是不推荐
this.foods = ['西蓝花','鸡蛋']
}
// 公有方法
sayHi(){
console.log('你好');
console.log(this.name);
}
}
// 基于类实例化对象
const p = new Person('he')

私有属性和静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 静态属性, 访问时:类来访问
class Test {
// 定义时:static开头
static stInfo='我是静态属性'
static stMethod(){
console.log('我是静态方法')
}
// 私有属性方法
#prInfo = '我是私有属性'
#prMethod(){
console.log('我是私有方法')
}
test(){
console.log(this.#prInfo)
this.#prMethod()
}
}
// 类调用
Test.stMethod()
console.log(Test.stInfo);
const t=new Test()
t.testPr()
t.#prMethod() // 访问私有属性,方法会报错

②class继承

extends:使用extends可以继承任何拥有[[Construct]]和原型的对象。

super:一般用来调用父类的构造函数。

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
class Person {
name
constructor(name){
this.name = name
}
sayHi(){
console.log('父亲的sayHi');
}
}

// 子类
// extends:继承父类
// super:一般用来调用父亲的构造函数
class Son extends Person {
age
constructor(name,age){
// 用super调用父类构造函数,不要在super()之前引用this,否则报错
super(name)
this.age = age
}
// 给子类添加方法
sayHello(){
console.log('子类的方法');
}
// 同名方法就近
sayHi(){
console.log('子类的sayHi');
}
}
let son1 = new Son('son1',18)
let son2 = new Son('son2')

JS中的继承
http://example.com/2023/09/11/JS中的继承/
作者
AlongSunsea
发布于
2023年9月11日
许可协议