继承
- 继承,即一个对象上拥有被继承对象的属性和方法
- 在
OOP 面向对象编程
中,通过类的继承来实现代码的复用,通过实例化一个类可以创建许多对象,在 JS 中继承是通过原型实现的。
ES5继承
构造函数、原型和实例的关系
- 每一个构造函数都有一个原型对象
- 每一个原型对象都有一个指向构造函数的指针
- 每一个实例都包含一个指向原型对象的内部指针
1. 原型链继承
-
思路
将父类的实例作为子类的原型 -
优点
1、父类方法可以复用 -
缺点
1、父类的所有引用属性
(如:loves) 会被所有子类共享,当其中一个子类的引用属性被修改后,会影响其他子类
2、子类型实例不能给父类型构造函数传参
function Parent() {
this.name = '无法传参';
this.loves = ['敲代码', '摄影']
}
Parent.prototype.getName = function () {
console.log(this.name);
}
Parent.prototype.getLoves = function () {
console.log(this.loves);
}
function Child(age) {
this.age = age
}
// 子类继承父类的属性
Child.prototype = new Parent()
Child.prototype.getAge = function () {
console.log(this.age);
}
let zs = new Child(23);
zs.getAge();
zs.getName();
zs.loves.push('打游戏');
zs.getLoves();
let ls = new Child(22)
ls.getAge();
ls.getName();
ls.getLoves();
2. 借用构造函数继承
-
基本思想
在子类构造函数中调用父类构造函数,可以在子类构造函数中使用call()
和apply()
方法 -
优势
1、可以在子类构造函数中向父类传参数
2、父类的引用属性不会被共享 -
缺点
1、子类不能访问父类原型User.prototype
上定义的方法,因此所有方法属性都写在构造函数中,每次创建实例都会初始化
function Parent(name) {
this.name = name;
this.loves = ['敲代码', '摄影']
}
Parent.prototype.getName = function () {
console.log(this.name);
}
Parent.prototype.getLoves = function () {
console.log(this.loves);
}
function Child(name, age) {
// 子类继承父类的属性
Parent.call(this, name);
// 实例属性
this.age = age
}
Child.prototype.getAge = function () {
console.log(this.age);
}
let zs = new Child('张三', 23);
console.log(zs);
zs.getAge();
// zs.getName(); // 报错,子类不能访问父类原型上定义的方法
zs.loves.push('打游戏');
// zs.getLoves(); // 报错,子类不能访问父类原型上定义的方法
console.log(zs.name);
console.log(zs.loves);
let ls = new Child('李四', 22)
ls.getAge();
console.log(zs.name);
console.log(zs.loves);
3. 组合继承
-
将原型链和借用构造函数技术组合到一起实现继承
-
优点
1、父类的方法可以复用
2、可以在子类构造函数中向父类传参数
3、父类构造函数中的引用属性不会被共享 -
缺点
1、会调用两次超类型构造函数,一次是在创建子类型原型的时候,一次是在子类型构造函数的内部
function Parent(name) {
this.name = name;
this.loves = ['敲代码', '摄影']
}
Parent.prototype.getName = function () {
console.log(this.name);
}
Parent.prototype.getLoves = function () {
console.log(this.loves);
}
function Child(name, age) {
// 子类继承父类的属性
Parent.call(this, name);
// 实例属性
this.age = age
}
// 子类继承父类的属性
Child.prototype = new Parent();
Child.prototype.getAge = function () {
console.log(this.age);
}
let zs = new Child('张三', 23);
console.log(zs);
zs.getAge();
zs.getName();
zs.loves.push('打游戏');
zs.getLoves();
let ls = new Child('李四', 22)
ls.getAge();
ls.getLoves();
4. 原型式继承
- 浅拷贝参数对象
-
优点
父类方法可复用 -
缺点
1、父类的所有引用属性
(如:loves) 会被所有子类共享,当其中一个子类的引用属性被修改后,会影响其他子类
2、子类实例不能向父类传参
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
let person = {
name: null,
age: null,
loves: ['敲代码', '摄影'],
getName: function () {
console.log(this.name);
},
getAge: function () {
console.log(this.age);
},
getLoves: function () {
console.log(this.loves);
}
}
let zs = objectCopy(person);
// let zs = Object.create(person);
// let zs = Object.assign(person);
console.log(zs);
zs.name = '张三';
zs.age = 18;
zs.loves.push('打游戏');
zs.getAge();
zs.getName();
zs.getLoves();
let ls = objectCopy(person);
// let zs = Object.create(person);
// let zs = Object.assign(person);
ls.name = '李四';
ls.age = 20;
ls.getAge();
ls.getName();
ls.getLoves();
- ES5的
Object.create()
方法在只有第一个参数时,与这里的objectCopy()
方法效果相同 - ES6的
Object.assign
与这里的objectCopy()
方法效果相同
5. 寄生式继承
- 使用原型式继承对目标对象进行浅复制,增强这个浅复制的能力
let info = {
loginType: 'PC',
name: null,
loves: ['敲代码']
}
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun()
}
function createUser(original) {
// 通过调用函数创建一个新对象
let clone = objectCopy(original);
// 增强对象
clone.getInfo = function () {
console.log(this.name);
};
clone.getLove = function () {
console.log(this.loves);
};
return clone;
}
let zs = createUser(info)
zs.name = '张三';
zs.loves.push('摄影');
zs.getInfo()
let ls = createUser(info)
ls.name = '张三';
ls.getInfo()
ls.getLove()
6. 寄生式组合继承
- 寄生式组合继承可以算是引用类型继承的最佳模式
-
优点
子类可以向父类传参
父类方法可以复用
父类的引用属性不会被共享
只调用一次父类构造函数
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function inheritPrototype(child, parent) {
let prototype = objectCopy(parent.prototype); // 创建对象
prototype.constructor = child; // 增强对象
child.prototype = prototype; // 赋值对象
}
function Parent(name) {
this.name = name;
this.loves = ['敲代码', '摄影']
}
Parent.prototype.getName = function () {
console.log(this.name);
}
Parent.prototype.getLoves = function () {
console.log(this.loves);
}
function Child(name, age) {
// 子类继承父类属性
Parent.call(this, name);
this.age = age;
}
// 子类继承父类方法
inheritPrototype(Child, Parent);
Child.prototype.getAge = function () {
console.log(this.age);
}
let zs = new Child('张三', 23);
zs.getAge();
zs.getName();
zs.loves.push('打游戏');
zs.getLoves();
let ls = new Child('李四', 22)
ls.getAge();
ls.getName();
ls.getLoves();
ES6 继承
- ES6 通过
class
之间通过使用extends
关键字来实现继承
class Parent {
constructor(name) {
this.name = name;
this.loves = ['敲代码', '摄影']
}
getName() {
console.log(this.name)
};
getLoves() {
console.log(this.loves)
};
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的构造函数
this.age = age;
};
getAge() {
console.log(this.age)
};
}
let zs = new Child('张三', 23);
zs.getAge();
zs.getName();
zs.loves.push('打游戏');
zs.getLoves();
let ls = new Child('李四', 22)
ls.getAge();
ls.getName();
ls.getLoves();
console.log(zs)
总结
- 声明的区别
函数声明会提升,类声明不会 - 继承的区别
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上
ES6的继承实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。
网友评论