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());
|
缺点:
在使用原型链继承时,父类的引用数据类型(对象、数组)会被子类共享,子类实例数据更改,其他子类也会受到变化。并且子类实例不能给父类构造函数传参。
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); console.log(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(){ Parent.call(this,'参数') }
var Child1 = new Child() var Child2 = new Child() console.log(Child1.name); Child2.arr.push(4) console.log(Child1.arr); console.log(Child2.arr); 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); console.log(Child2.arr); 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() console.log(Child.name);
|
缺点:
- 父类的引用属性会被所有子类实例共享
- 子类构建实例时不能向父类传递参数
适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。
⑤寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
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.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 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'); } }
class Son extends Person { age constructor(name,age){ super(name) this.age = age } sayHello(){ console.log('子类的方法'); } sayHi(){ console.log('子类的sayHi'); } } let son1 = new Son('son1',18) let son2 = new Son('son2')
|