js继承方式
方式1:原型链继承
核心:将父类的实例作为子类的原型
- 优点:
- 1、非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 2、父类新增原型方法/属性,子类都能访问到
- 缺点:
- 1、创建⼦类实例的时候,不能传⽗类的参数(⽐如name)。
- 2、⼦类实例共享了⽗类构造函数的引⽤属性,⽐如arr属性。
- 4、不支持多继承
function Parent(name) {
this.name = name || '父亲' //实例基本属性(该属性强调私有,不共享)
this.arr=[1] //该属性强调私有
}
Parent.prototype.say = function() { //将要复用,共享的方法定义在父类原型上
console.log('hello')
}
function Child(like){
this.like=like
}
Child.prototype=new Parent()
Child.prototype.constructor=Child//修正constructor指向
let boy1=new Child()
let boy2=new Child()
//优点:共享了⽗类构造函数的say⽅法
console.log(boy1.say(),boy2.say(),boy1.say===boy2.say);//hello,hello,true
//缺点1:不能向⽗类构造函数传参
console.log(boy1.name,boy2.name,boy1.name===boy2.name);//⽗亲,⽗亲,true
//缺点2:⼦类实例共享了⽗类构造函数的引⽤属性,⽐如arr属性
boy1.arr.push(2)
//修改了boy1的arr属性,boy2的arr属性,也会变化,
//因为两个实例的原型上(Child.prototype)有了⽗类构造函数的实例属性arr;
//所以只要修改了boy1.arr,boy2.arr的属性也会变化。
console.log(boy2.arr);//[1,2]
//注意1:修改boy1的name属性,是不会影响到boy2.name。因为设置boy1.name相当于在⼦类实例新增了name属性。
//注意2:
console.log(boy1.constructor);//Parent你会发现实例的构造函数居然是Parent。⽽实际上,我们希望⼦类实例的构造函数是Child,所以要记得修复构造函数指向。
//修复如下:Child.prototype.constructor=Child;
方式2:构造继承
- 核⼼:借⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类。
- 优点:实例之间独⽴。
- 创建⼦类实例,可以向⽗类构造函数传参数。
- ⼦类实例不共享⽗类构造函数的引⽤属性。如arr属性
- 可实现多继承(通过多个call或者apply继承多个⽗类)
- 缺点:
- ⽗类的⽅法不能复⽤ 由于⽅法在⽗构造函数中定义,导致⽅法不能复⽤(因为每次创建⼦类实例都要创建⼀遍⽅法)。⽐如say⽅法。(⽅法应该要复⽤、共享)
- ⼦类实例,继承不了⽗类原型上的属性。(因为没有⽤到原型)
function Parent(name) {
this.name = name //实例基本属性(该属性强调私有,不共享)
this.arr=[1] //该属性强调私有
this.say =function(){ //实例引⽤属性(该属性,强调复⽤,需要共享)
console.log('hello')
}
}
function Child(name,like){
Parent.call(this,name) //核⼼ 拷⻉了⽗类的实例属性和⽅法
this.like=like
}
let boy1 = new Child('小A','苹果')
let boy2 = new Child('小B','香蕉')
//优点1:可向⽗类构造函数传参
console.log(boy1.name,boy2.name); //⼩A,⼩B
//优点2:不共享⽗类构造函数的引⽤属性
boy1.arr.push(2)
console.log(boy1.arr,boy2.arr); //[1,2][1]
//缺点1:⽅法不能复⽤
console.log(boy1.say===boy2.say) //false(说明,boy1和boy2的say⽅法是独⽴,不是共享的)
//缺点2:不能继承⽗类原型上的⽅法
Parent.prototype.walk=function(){ //在⽗类的原型对象上定义⼀个walk⽅法。
console.log('我会⾛路')
}
boy1.walk; //undefined(说明实例,不能获得⽗类原型上的⽅法)
方式3:组合继承
- 核⼼:通过调⽤⽗类构造函数,继承⽗类的属性并保留传参的优点;然后通过将⽗类实例作为⼦类原型,实现函数复⽤。
- 优点:
- 保留构造函数的优点:创建⼦类实例,可以向⽗类构造函数传参数。
- 保留原型链的优点:⽗类的⽅法定义在⽗类的原型对象上,可以实现⽅法复⽤。
- 不共享⽗类的引⽤属性。⽐如arr属性
- 缺点:
- 由于调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性。
- 注意:'组合继承'这种⽅式,要记得修复Child.prototype.constructor指向 第⼀次Parent.call(this);从⽗类拷⻉⼀份⽗类实例属性,作为⼦类的实例属性,第⼆次Child.prototype=newParent();创建⽗类实例作为⼦类原型,Child.protype中的⽗类属性和⽅法会被第⼀次拷⻉来的实例属性屏蔽掉,所以多余。
function Parent(name) {
this.name = name //实例基本属性(该属性强调私有,不共享)
this.arr=[1] //该属性强调私有
}
Parent.prototype.say=function(){ //---将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(name,like){
Parent.call(this,name,like) //核⼼ 第⼆次
this.like=like
}
Child.prototype=new Parent() //核⼼ 第⼀次
Child.prototype.constructor=Child //修正constructor指向
let boy1 = new Child('小A','苹果')
let boy2 = new Child('小B','香蕉')
//优点1:可向⽗类构造函数传参
console.log(boy1.name,boy1.like); //⼩A,苹果
//优点2:可复⽤⽗类原型上的⽅法
console.log(boy1.say===boy2.say)//true
//优点3:不共享⽗类的引⽤属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr,boy2.arr);//[1,2][1]可以看出没有共享arr属性。
//缺点1:由于调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性
Child.prototype=new Parent() console.log(Child.prototype.proto===Parent.prototype);//true 因为Child.prototype等于Parent的实例,所以__proto__指向Parent.prototype
- 为什么‘组合继承’这种方式,会执行两次父类构造函数?
- 第一次 Child.prototype=new Parent() new的过程’的第三步,其实就是执⾏了⽗类构造函数。
- 第⼆次:Parent.call(this,name,like) call的作⽤是改变函数执⾏时的上下⽂。⽐如:A.call(B)。其实,最终执⾏的还是A函数,只不过是⽤B来调⽤⽽已。所以,你就懂了Parent.call(this,name,like),也就是执⾏了⽗类构造函数Person。
方式4:寄⽣组合继承
- 优点:完美
function Parent(name) {
this.name = name //实例基本属性(该属性强调私有,不共享)
this.arr=[1] //该属性强调私有
}
Parent.prototype.say=function(){ //---将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(name,like){
Parent.call(this,name,like) //核⼼
this.like=like
}
//核⼼通过创建中间对象,⼦类原型和⽗类原型,就会隔离开。不是同⼀个啦,有效避免了⽅式4的缺点。
Child.prototype=Object.create(Parent.prototype)
Child.prototype.constructor=Child
let boy1 = new Child('小A','苹果')
let boy2 = new Child('小B','香蕉')
- Object.create(object,propertiesObject) Object.create()⽅法创建⼀个新对象,使⽤第⼀个参数来提供新创建对象的__proto__(以第⼀个参数作为新对象的构造函数的原型对象);⽅法还有第⼆个可选参数,是添加到新创建对象的属性,例如:
const a = Object.create(Person.prototype,{
age:{
value:12,
writable:true,
configurable:true
}
})
- new与Object.create()的区别
- new产⽣的实例,优先获取构造函数上的属性;构造函数上没有对应的属性,才会去原型上查找;如果构造函数中以及原型中都没有对应的属性,就会报错。
- Object.create()产⽣的对象,只会在原型上进⾏查找属性,原型上没有对应的属性,就会报错。
方式5:实例继承
- 核心:为父类实例添加新特性,作为子类实例返回
function Parent(name) {
this.name = name //实例基本属性(该属性强调私有,不共享)
this.arr=[1] //该属性强调私有
}
Parent.prototype.say=function(){ //---将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(name){
var instance = new Parent()
instance.name = name
return instance
}
- 缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
方式6:ES6继承
class Person {
constructor(name) {
this.name =name
}
sayName() {
console.log(this.name)
}
}
class Student extends Person {
constructor(name) {
super(name)
}
}
let person = new Person('oo')
let student = new Student('ss')
person.sayName()
student.sayName()