介绍
- 继承可以使子类得到父类的属性和方法,甚至可以重新定义父类的属性。
- 通过重写或覆盖父类的方法,以取得不同于父类的属性和方法
- 继承是面向对象的,可以更好的复用之前的开发代码,缩短开发周期,提高开发效率。
面向对象
什么是面向对象?
将要处理的物品拟化为一个对象,该对象有属于自己的属性和方法以及一个作用域this,this可以用来获取对象在某个时刻的属性和方法。
面向对象的特征
-
封装:
- 把客观事物封装成抽象的类,内部包含该事物的属性和行为方法
-
继承:
- 对象可以继承,子类对象通过继承可以拥有父类的属性和方法。
- JS中继承通过赋予子类一个父类的实例并专门化子类来实现
-
多态:
- ·不同的对象接收到同一消息后,所表现出的行为是各不相同的。
- 一个方法的不同表现形式
有一个很形象的理解:将每个对象视为一个村子,每个村子内部是独立的存在(封装)。在改革开放的进程中,当一个村子发展良好时,另外的村子可以去吸收一些良好的经验,甚至于全部模仿(继承)。同时各个村子积极响应乡政府的号召发展各自村子的特色(多态),在脱贫攻坚战中取得了很大的成就。
继承
继承和对象是息息相关的。根据不同的情况,可以分为多种方式:
- 不使用Object.create()
- 原型链继承
- 构造函数继承
- 组合继承
- 使用Object.create()
- 原型式继承
- 寄生式继承
- 寄生组合继承
- ES6 extends继承
原型链继承
原型链涉及构造函数、原型和实例对象,三者关系如下:
- 每一个构造函数都有一个原型对象 Xxx.prototype
- 每一个原型对象都有一个constructor属性指向构造函数 Xxx.prototype.constructor => Xxx
- 每一个实例都有一个__proto__属性指向原型对象
原型链继承示例
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
var s1 = new Child1();
var s2 = new Child1();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
由于s1和s2的原型对象是同一个Child1,而对于引用类型数据,其内存空间是共享的,所以s1中改动了也会影响到s2。这就是原型链继承的一个缺点。
PS: 整体输出s1和s2时,仅包含type属性,父类属性并未包含进去。只有单独输出父类属性( [s1.name]) 时,才会原型链上查找并输出显示相应属性的值
构造函数继承
构造函数继承是借助于call来实现的。(apply、bind也可以,都是改变了this指向)
function Parent1(){
this.name = 'parent1';
}
Parent1.prototype.getName = function () {
return this.name;
}
function Child1(){
Parent1.call(this); // Parent1.apply(this) Parent1.bind(this)()
this.type = 'child1'
}
let child = new Child1();
console.log(child); // 没问题 {name: "parent1", type: "child1"}
console.log(child.getName()); // 会报错
可以看出:
- 子类中可以拿到继承的父类中的属性值
- 父类的原型对象中存在自定义方法,则子类无法继承
所以构造函数的优缺点:
- 父类的引用属性空间不会共享(通过call完全复制了一份)
- 只能继承父类的实例属性和方法,无法继承原型属性和方法
组合式继承
组合式继承其实是原型链式继承和构造函数式继承的组合
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}
// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3] 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
解决了父类引用类型数据内存共享和无法继承父类原型属性方法的问题,但是两次调用父类构造函数,性能开销过大