继承的意义
-
继承是面向对象的,使用这个方式可以让我们更好的复用代码,缩短开发周期,提升开发效率
思考题
-
① JS的继承方式到底有多少种实现方式呢?
-
② ES5的 extends 关键字是用哪种继承方式实现的呢?
一、原型链继承
-
-
让一段代码来感受一下
(1)案例感受
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());
-
用代码测试一下
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play);
-
结果
(2)优缺点分析
-
实现简单,方便快捷
-
虽然父类的方法和属性都能够访问,但其实有一个潜在的问题,比如上面那样,只改变了一个实例,而另一个实例也跟着改变。
-
因为内存空间是共享的
-
当一个发生变化的时候另一个也跟着发生变化
-
二、构造函数继承(call)
(1)案例感受
function Parent1(){
this.name = 'parent1';
}
Parent1.prototype.getName = function () {
return this.name;
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
let child = new Child1();
console.log(child); // 没问题
console.log(child.getName()); // 会报错
-
结果
(2)优缺点分析
-
子类可以拿到父类实例的属性和方法
-
但是子类拿不到父类原型的属性和方法
三、组合继承
-
结合了原型继承和构造函数继承的优缺点
(1)案例感受
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); // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
-
结果
(2)优缺点分析
-
很好解决了上述两个方法的问题
-
但是会调用两次父类,造成多的性能开销
四、原型式继承
-
上面说的更多的是构造函数(类)的继承,那么围绕对象的继承又有哪些呢
-
说起原型式继承不得不提到 ES5 的 Object.create 方法
-
接受两个参数
-
① 用作新对象的原型对象
-
② 为新对象定义额外属性的对象
-
-
(1)案例感受
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");
let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person4.name);
console.log(person4.name === person4.getName());
console.log(person5.name);
console.log(person4.friends);
console.log(person5.friends);
-
结果
(2)优缺点
-
可以实现对象的浅拷贝
-
但是对于多个对象使用该方法的话,会出现共享问题
五、寄生式继承
-
在上一个方法的基础上进行优化的一个继承方法
-
原理:
-
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力在进行增强,添加一些方法
-
(1)案例感受
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName());
console.log(person5.getFriends());
-
结果
(2)优缺点
-
相比原型式继承,可以添加方法,从而使对象在继承的过程中又添加了一个方法
-
缺点跟原型式继承一样,也只能实现浅拷贝
六、寄生组合式继承
-
在前面这几种继承方式的优缺点基础上进行改造,这是所有继承方式里面相对最优的继承方式
(1)案例感受
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child5';
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());
-
结果
(2)优缺点
-
很好的结合以上的优点,较少了构造函数的调用次数,减少了性能开销
-
es6的语法糖extends
(3)总结脑图
七、ES6的extends关键字实现逻辑
-
使用关键词很容易就实现JS的继承,但是巷深入了解extends语法糖怎么实现,就要深入研究extends的底层逻辑
(1)es6中extends使用方法
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
-
在babel编译成es5的代码
function _possibleConstructorReturn (self, call) { // ... return call && (typeof call === 'object' || typeof call === 'function') ? call : self; } function _inherits (subClass, superClass) { // 这里可以看到 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Parent = function Parent () { // 验证是否是 Parent 构造出来的 this _classCallCheck(this, Parent); }; var Child = (function (_Parent) { _inherits(Child, _Parent); function Child () { _classCallCheck(this, Child); return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments)); } return Child; }(Parent));