1、原型链继承
=》SubFun.prototype = new SuperFun()
首先谈谈原型、构造函数和实例以及三者之间的关系
原型:所有对象都有_prop_属性
构造函数:其实就是函数,其特点就是函数名首字母大写和得使用new实例对象调用;
三者关系:创建构造函数同时会自动创建一个property原型属性,每一个property属性都有constructor属性,这个constructor属性指向得就是构造函数
搞清楚上面三者关系,回归正文,原型链继承方法
//构造函数实现通用属性和方法,new实例方法继承构造函数的属性和方法
function SuperFun (name,age) {
// 公共的属性
this.name = name
this.age = age
this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
console.log('都有'+this.has);
console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
let huahua = new SuperFun('花花',23)
huahua.getSuperValue()
huahua.has.push('耳朵')
console.log(...huahua.has); //头发 眼睛 鼻子 耳朵
let ming = new SuperFun('明明',21)
ming.getSuperValue()
ming.has.push('嘴巴')
console.log(...ming.has); //头发 眼睛 鼻子 嘴巴
// 原型链继承,其实就是通过prototype原型属性的方式继承父类
function SubFun (name,age) {
// 公共的属性
this.name = name
this.age = age
}
SubFun.prototype = new SuperFun() //原型链继承
// 实例对象
let zhangsan = new SubFun('张三',11)
zhangsan.getSuperValue() //都有头发,眼睛,鼻子
zhangsan.has.push('大长腿')
console.log(...zhangsan.has); //头发 眼睛 鼻子 大长腿
let lisi = new SubFun('李四',10)
lisi.getSuperValue() //都有头发,眼睛,鼻子,大长腿
lisi.has.push('乌黑头发')
console.log(...lisi.has); //头发 眼睛 鼻子 大长腿 乌黑头发
// 原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。
// 解决方案:使用组合继承,原型链继承结合构造函数继承
// 在子构造函数中使用 通过拷贝父类属性
// SuperFun.call(this,name,age)
原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。
2、构造函数继承
=》 SuperFun.call(this,arguments)
借用父类的构造实例来增强子类实例,其实相当于复制一份父类构造函数中属性和方法
function SuperFun (name,age) {
// 公共的属性
this.name = name
this.age = age
this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
console.log('都有'+this.has);
console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
function SubFun (name,age) {
// 通过拷贝父类
SuperFun.call(this,name,age)
// 所有子类都有父类实例函数的副本
}
// 实例对象
let zhangsan = new SubFun('张三',11)
//zhangsan.getSuperValue() // Uncaught TypeError: zhangsan.getSuperValue is not a function
zhangsan.has.push('大长腿')
console.log(...zhangsan.has); //头发 眼睛 鼻子 大长腿
let lisi = new SubFun('李四',10)
//lisi.getSuperValue() // Uncaught TypeError: zhangsan.getSuperValue is not a function
lisi.has.push('乌黑头发')
console.log(...lisi.has); //头发 眼睛 鼻子 乌黑头发
/*
缺点:
只能继承父类的实例属性和方法,无法继承原型上的属性和方法
无法实现复用,所有子类都有父类实例函数的副本,影响性能
*/
缺点:
只能继承父类的实例属性和方法,无法继承原型上的属性和方法
无法实现复用,所有子类都有父类实例函数的副本,影响性能
3、组合继承
=》结合原型链和构造函数两者方法
//构造函数实现通用属性和方法,new实例方法继承构造函数的属性和方法
function SuperFun (name,age) {
// 公共的属性
this.name = name
this.age = age
this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
console.log('都有'+this.has);
console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
// 原型链继承,其实就是通过prototype原型属性的方式继承父类
function SubFun (name,age,address) {
// 通过拷贝父类属性
SuperFun.call(this,name,age)
this.address = address
}
SubFun.prototype = new SuperFun() //原型链继承
// console.log(SubFun.prototype.constructor); //指向SuperFun
// 重写SubFun.prototype的constructor属性,指向自己的构造函数SubFun
SubFun.prototype.constructor = SubFun
// console.log(SubFun.prototype.constructor); //指向SuperFun
// 实例对象
let zhangsan = new SubFun('张三',11,'江西省上饶市')
zhangsan.getSuperValue() //都有头发,眼睛,鼻子
zhangsan.has.push('大长腿')
console.log(zhangsan.has); //头发 眼睛 鼻子 大长腿
console.log(zhangsan.address);
let lisi = new SubFun('李四',10,'上海市浦东新区')
lisi.getSuperValue() //都有头发,眼睛,鼻子
lisi.has.push('乌黑头发')
console.log(lisi.has); //头发 眼睛 鼻子 乌黑头发
console.log(lisi.address);
缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。
4、原型式继承
/*
原型式继承:
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型
*/
function object (obj) {
function F(){}
F.prototype = obj
return new F()
}
// object()对传入对象进行一次浅复制,将构造函数F的原型直接指向传入的对象,这样子类实例将共享该对象的所有属性和方法
let person = {
name:'person',
friends:['a','b']
}
let firstP = object(person)
firstP.name = 'xiaoxiao'
firstP.friends.push('cc')
console.log(firstP);
let secondP = object(person)
secondP.name = 'minghua'
secondP.friends.push('dd')
console.log(secondP);
console.log(person);
缺点:原型链继承多个实例的引用类型属性指向相同,存在篡改可能;无法传递参数。
ES5存在object.create()方法,能够替代上面的object方法
5、寄生式继承
// 寄生式继承:在原型式继承基础上,增加对象,返回构造函数
function commonP (original) {
let clone = object(original) // 通过调用 object() 函数创建一个新对象
clone.sayFun = function(){ // 以某种方式来增强对象
console.log('hi hello world');
}
return clone // 返回这个对象
}
let children = {
name:'children',
friends:['q1','q2']
}
let anotherP = commonP(children)
anotherP.name = 'xx'
anotherP.friends.push('p1')
console.log(anotherP);
console.log(anotherP.friends);
anotherP.sayFun()
let otherP = commonP(children)
otherP.name = 'gg'
otherP.friends.push('mm')
console.log(otherP);
console.log(otherP.friends);
otherP.sayFun()
console.log(children);
/*
friends: (4) ["q1", "q2", "p1", "mm"]
name: "children"
*/
缺点:(同原型式继承)
原型链继承多个实例的引用属性存在相同,存在篡改可能;无法传递参数
6、寄生组合式继承
/*
寄生组合式继承:结合借用构造函数传递参数和寄生模式实现继承
*/
// 寄生式
function inheritPrototype (subType,superType) {
// 创建对象
let __prototype = Object.create(superType.prototype) //创建父类原型的一个副本
// 指定对象
subType.prototype = __prototype //将新创建的对象赋值给子类的原型
// 增强对象
__prototype.constructor = subType //防止因重写原型而失去默认的constructor属性
}
// 父类初始化实例属性和原型属性
function SuperFun (name,age) {
// 公共的属性
this.name = name
this.age = age
this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
console.log('都有'+this.has);
console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubFun (name,age,sex) {
// 通过拷贝父类
SuperFun.call(this,name,age) // 所有子类都有父类实例函数的副本
this.sex = sex
}
// 将父类原型指向子类
inheritPrototype(SubFun,SuperFun)
// 新增子类原型属性
SubFun.prototype.saySex = function () {
console.log(this.sex);
}
let instance1 = new SubFun('ss',12,'女')
instance1.has.push('aa')
let instance2 = new SubFun('gg',11,'男')
instance2.has.push('bb')
console.log(instance1);
console.log(instance2);
这个例子的高效率体现在它只调用了一次SuperType
构造函数,并且因此避免了在SubType.prototype
上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf()
推荐使用这种继承方法,这也是最成熟的方法,也是库实现的方法
7、混入方式继承多个对象
function SuperCls (name) {
this.name = name
this.friends = ['a']
}
SuperCls.prototype.superMethod = function () {
console.log('SuperCls');
}
function OtherSuperCls (sex) {
this.sex = sex
}
OtherSuperCls.prototype.otherSuperMethod = function () {
console.log('otherSuperMethod');
}
// 拷贝父构造函数实例所有属性
function MyCls (name,sex) {
SuperCls.call(this,name)
OtherSuperCls.call(this,sex)
}
// 继承一个类
MyCls.prototype = Object.create(SuperCls.prototype)
// 重新指定constructor属性
MyCls.prototype.constructor = MyCls
// 混入其他
Object.assign(MyCls.prototype,OtherSuperCls.prototype)
MyCls.prototype.myMethod = function () {
// do something
console.log('do something');
}
let instance1 = new MyCls('ss','女')
instance1.friends.push('aa')
let instance2 = new MyCls('gg','男')
instance2.friends.push('bb')
console.log(instance1);
console.log(instance2);
Object.assign
会把 OtherSuperCls原型上的函数拷贝到 MyCls原型上,使 MyCls的所有实例都可用 OtherSuperCls的方法。
8、ES6类继承extends
extends
关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor
表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError
错误,如果没有显式指定构造方法,则会添加默认的 constructor
方法,子类继承父类属性使用super;
一个构造函数可以使用 super
关键字来调用一个父类的构造函数。
class Rectangle {
constructor(w,h){
this.height = h
this.width = w
}
get area(){
return this.width*this.height
}
}
const reactangle = new Rectangle(10,20)
console.log(reactangle.area); //200
console.log(reactangle);
// ------------------------继承--------------------------------
class Square extends Rectangle {
constructor(length){
super(length,length)
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name='square'
}
get area(){
return this.width*this.height
}
}
const square = new Square(10)
console.log(square.area); //100
console.log(square);
extends
继承的核心代码如下,其实现和上述的寄生组合式继承方式一样
function _inherits(subType, superType) {
// 创建对象,创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
总结
1、函数声明和类声明的区别
函数声明会提升,类声明不会。首先需要声明你的类,然后访问它
2、ES5和ES6继承区别
ES5没有类概念,ES5继承实质上是先创建子类的实例对象,再将父类的属性和方法添加到this上(SuperFun.call(this))
ES6继承实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this;
因为子类没有自己的this对象,所以必须先调用父类super()方法,否则新建实例报错
/*
总结
1、函数声明会提升,类声明不会;首先需要声明后才能访问,否则会报错
*/
let instace1 = new Rectangle()
class Rectangle{}
// Uncaught ReferenceError: Cannot access 'Rectangle' before initialization
/*2、ES5继承和ES6继承的区别
ES5没有类概念,ES5继承实质上是先创建子类的实例对象,再将父类的属性和方法添加到this上(SuperFun.call(this))
ES6继承实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this;
因为子类没有自己的this对象,所以必须先调用父类super()方法,否则新建实例报错
*/
有什么不对的地方可以给出您宝贵的评论或意见
参考来源:https://juejin.im/post/5bcb2e295188255c55472db0