js中es6之前没有类和继承,但是可以通过各种巧妙的方式来实现继承
继承应该达到的状态:
1.子类可以使用父类中的属性和方法
2.子类不同的实例之间不会互相影响
3.子类实例能够向父类传参
4.能实现多继承(一个子类可继承多个父类)
5.父类的方法能被复用(不会过多的消耗内存),而不是每创建一个子类实例都生成一份父类方法
一.原型链继承
1.具体实现:父类的实例作为子类的原型对象,核心实现代码标注如下:
1 | js复制代码 // 父类(我就不写成父构造函数了,这样比较简洁) |
2.优点:易于实现,一行代码就能实现
3.缺点:
1—>创建子类实例的时候不能向父类传参
2—>父类的引用数据类型属性被子类实例修改后,所有的子类实例上的该属性值也会跟着被修改
二.借用构造函数继承
1.具体实现:借父类的构造函数来增强子类实例(call,apply),相当于把父类的实例属性复制一份到子类实例
1 | js复制代码 // 父类 |
2.优点:
1.解决了子类实例共享父类引用属性的问题
2.创建子类实例时,可以向父类构造函数传参
3.可以实现多继承(call多个)
3.缺点:无法继承父类原型中的方法,除非父类的方法写入构造函数中,但这样就实现不了函数的复用
三.组合式继承(伪经典继承)
1.具体实现:
1 | js复制代码 // 父类 |
2.核心
**把实例方法都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点,通过call(this)继承父类的基本属性和引用属性并保留能传参的优点.
通过Child.prototype = new Father()继承父类函数,实现函数复用**
优点:
- 不存在引用属性共享问题(不同子类实例之间不会互相影响)
- 可传参
- 函数可复用
4.缺点:也有一点小缺点
子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,生成了两份,而子类实例上的那一份屏蔽了子类原型上的,造成内存浪费
四.原型式继承(对象中的继承,更像是拷贝而不是继承)
1.具体实现:
1 | js复制代码 // 创建对象obj |
3.核心:通过空的构造函数作为跳板 ,返回该构造函数的调用(类似于复制一个对象,用函数来包装)
3.优缺点
优点:
- 从已有对象衍生新对象,不需要创建自定义类型(一种新的创建对象方式) es5中内置方法Object.create()用到了这种方式
缺点:
- 父类引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象,所以这个缺陷无可避免
- 无法实现代码复用(新对象是现取的,每次new F()返回的都是一个新的对象,也没用到原型,无法复用)
五.寄生式继承
1.具体实现:
1 | js复制代码 let obj = { |
缺点:
函数还是不能复用,一个实例修改原型上的引用属性,其他实例依然会跟着改变(就是给原型式继承套上一层函数而已,让原型式看起开更像继承,并没有解决根本问题)
到这里的几个分析:
1.以上的方式多少都会有点缺陷,要达到完美继承就需要在组合式继承(伪经典继承)身上进行改造,但是要达到能传参,并且还要实现多继承的目的,那么在子类内部调用父类构造函数Father.call()这一步就不能动,
所以只能考虑Child.prototype = new Father 这一步,
2.最终需要达到目的:Child.Prototype.__ proto __ = Father.prototype(子类的原型指向父类的原型, 解决父类调用两次的缺陷) —–>(也就是一个对象继承另一个对象,上面的寄生式和原型式继承方式),
另外最后还是需要将子类实例的constructor指回子类
六.寄生式组合式继承(完美继承)
1.具体实现:
1 | js复制代码 //1. 父类 实例属性放在构造函数中 |
优点:
完美实现了函数复用,传参,实例之间不会相互影响,多继承
寄生组合式优化(Object.create()):
1 | js复制代码 //1. 父类 实例属性放在构造函数中 |
本文转载自: 掘金