「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」
1 什么是继承
子类通过继承,可以获得父类的方法和属性
2 继承的方法
2.1 原型链继承
核心:让父类实例作为子类实例的原型
Son.prototype = new Father()
于是son.__proto__
➡Son.prototype
特点:
- 实例可继承的属性
- 实例的构造函数的属性
- 父类构造函数属性
- 父类原型的属性
缺点:
- 实例无法向父类构造函数传参
- 只能单继承
- 所有实例会共享原型对象的所有属性
2.2 构造继承
核心:使用call或apply方法将父类构造函数引入子类构造函数
function Son() {
// 相当于把父类构造函数中的代码复制过来执行
Father.call(this, 'Jack')
this.age = 12
}
特点:
- 继承父类构造函数的属性,不继承父类原型的属性
- 解决了原型链继承的缺点,即可传参、多继承、实例不会共享属性
缺点:
- 只能继承父类构造函数的属性
- 无法实现构造函数的复用(每次用每次都要重新调用)
- 每个新实例都有父类构造函数的副本,臃肿,影响子类的性能
2.3 组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
特点:
- 可以继承父类实例的属性和方法,也可以继承父类原型的属性和方法
- 可以不存在属性共享问题,如果不使用原型上的属性
- 可传参,函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
2.4 实例继承(❌)
核心:为父类实例添加新特性,作为子类实例返回
function Cat(name) {
var instance = new Animal()
instance.name = name || 'Tom'
return instance
}
特点:
- 不限制调用方式,不管是
new Cat()
还是Cat()
,返回的对象具有相同的效果
缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
2.5 原型式继承
核心:定义一个构造函数,让构造函数的原型对象指向已有对象o
function object(o) {
var G = function() {}
G.prototype = o
return new G()
}
var obj = {
name : 'ghostwu',
age : 22,
show : function(){
return this.name + ',' + this.age;
}
};
var obj2 = object( obj );
console.log( obj2.name, obj.age, obj.show() );
特点:
- 类似于复制一个对象,用函数来包装
- 本质是浅拷贝,以一个对象为模板复制出新的对象
缺点:
- 所有实例会共享原型上的属性
- 不能传递参数
在es5
中,新增了一个函数Object.create()实现了原型式继承
var obj = {
skills : [ 'php', 'javascript' ]
};
var obj2 = Object.create( obj );
obj2.skills.push( 'python' );
var obj3 = Object.create( obj );
console.log( obj3.skills ); //php,javascript,python
2.6 寄生式继承
核心:把原型式继承再次封装,然后在对象上扩展新的方法
function object(o) {
var G = function() {}
G.prototype = o
return new G()
}
function CreateObj(srcObj) {
var dstObj = object(srcObj)
dstObj.sayName = function() {
return this.userName
}
return dstObj
}
var obj = {
userName: 'Jack'
}
var obj2 = CreateObj(obj)
console.log(obj2.sayName())
缺点:和原型式继承一样,存在被篡改的可能
2.7 寄生组合继承
核心:结合借用构造函数传递参数和寄生模式实现继承
function Cat(name) {
Animal.call(this)
this.name = name || 'Tom'
}
(function() {
// 创建一个没有实例方法的类
var Super = function() {}
Super.prototype = Animal.prototype
// 将实例作为子类的原型
Cat.prototype = new Super()
})()
function inheritProtorype(subType, superType) {
var prototype = Object.create(superType.prototype) // 创建对象,创建父类原型的一个副本
prototype.constructor = subType // 增强对象,弥补因重写原型而失去的默认constructor属性
subType.prototype = prototype // 指定对象,将新创建的对象赋值给子类的原型
}
// 父类初始化实例属性和原型属性
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
alert(this.name)
}
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age) {
SuperType.call(this, name)
this.age = age
}
// 将父类原型指向子类
inheritProtorype(SubType, SuperType)
// 新增子类原型属性
SubType.prototype.sayAge = function() {
alert(this.age)
}
var instance1 = new SubType('xyc', 23)
var instance2 = new SubType('lxh', 23)
instance1.colors.push('2')
instance2.colors.push('3')
这个例子的高效率体现在它只调用了一次SuperType
构造函数,并且因此避免了在SubType.prototype
上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf()
这是最成熟的方法,也是现在库实现的方法
2.8 混入方式继承多个对象
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do something
};
Object.assign
会把OtherSuperClass
原型上的函数拷贝到MyClass
的原型上,使MyClass
的所有实例都可用OtherSuperClass
的方法。
2.9 es6 类继承extends
extends
关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor
表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError
错误,如果没有显式指定构造方法,则会添加默认的 constructor
方法,使用例子如下
// class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
// class中定义的所有方法是不可枚举的
// class中只能定义方法,不能定义对象,变量等
// class和方法内默认都是严格模式
// es5中constructor为隐式属性
class People{
constructor(name='wang',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
// 继承父类
class Woman extends People{
constructor(name = 'ren',age = '27'){
// 继承父类属性
super(name, age);
}
eat(){
// 继承父类方法
super.eat()
}
}
let womanObj=new Woman('xiaoxiami');
womanObj.eat();
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
-----------------------------------------------------------------
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
总结
1、函数声明和类声明的区别
函数声明会提升,类声明不会。首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError。
let p = new Rectangle();
// ReferenceError
class Rectangle {}
复制代码
2、ES5继承和ES6继承的区别
- ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).
- ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。