阅读目标
- 掌握JS6大继承方式及其优缺点
- 了解各种继承方式的演变
类式继承
// 父类
function Parent(name, houses) {
this.name = name;
this.houses = houses;
}
Parent.prototype.getName = function() {
console.log(this.name);
}
Parent.prototype.getHouses = function() {
console.log(this.houses);
}
// 子类
function Son() {
}
/*==== 核心代码 ====*/
Son.prototype = new Parent('Mark', ['北京']);
const son1 = new Son();
son1.houses.push('上海');
son1.name = '斯特里';
son1.getName() // 斯特里
son1.getHouses(); // ['北京','上海']
const son2 = new Son();
son2.getName() // Mark
son2.getHouses(); // ['北京','上海']
优点
- 可复用父类属性
缺点
- 父类引用属性会被共享
- 子类构建实例时不可向父类传递参数
- 子类的constructor属性被修改(不影响,可忽略)
备注
该方式的本质就是修改子类的原型对象,没什么可聊的
构造函数继承
// 父类
function Parent(name, houses) {
this.name = name;
this.houses = houses;
}
Parent.prototype.getName = function() {
console.log(this.name);
}
Parent.prototype.getHouses = function() {
console.log(this.houses);
}
// 子类
function Son(name, houses) {
/*==== 核心代码 ====*/
Parent.call(this, name, houses);
}
const son1 = new Son('斯特里', ['北京']);
son1.houses.push('上海');
console.log(son1.name); // 斯特里
console.log(son1.houses); // [ '北京', '上海' ]
son1.getHouses || console.error('getHouses不存在'); // getHouses不存在
const son2 = new Son('Mark', ['北京']);
console.log(son2.name); // Mark
console.log(son2.houses); // ['北京']
son2.getHouses || console.error('getHouses不存在'); // getHouses不存在
优点
- 父类引用属性不会被共享
- 子类构建时可向父类传递参数
缺点
- 无法继承父类原型链上的属性
备注
- 该方式简单粗暴的理解如下:每次创建子类对象的同时会创建一个父类对象,并将父类对象上的属性绑定到子类对象的上下文(this)上。
- 再认真观察之前两种继承方式的优缺点,发现彼此的优点正好可以弥补对方的缺点,由此衍生出第三种继承方式,组合继承。
组合继承
function Parent(name, houses) {
this.name = name;
this.houses = houses;
}
Parent.prototype.getName = function() {
console.log(this.name);
}
Parent.prototype.getHouses = function() {
console.log(this.houses);
}
function Son(name, houses) {
/*==== 构造函数继承核心代码 ====*/
Parent.call(this, name, houses);
}
/*==== 类式继承核心代码 ====*/
Son.prototype = new Parent();
const son1 = new Son('斯特里', ['北京']);
son1.houses.push('上海');
son1.getName(); // 斯特里
son1.getHouses(); // [ '北京', '上海' ]
const son2 = new Son('富贵', ['北京']);
son2.getName(); // 富贵
son2.getHouses(); // [ '北京' ]
优点
- 可复用父类属性
- 父类引用属性不会被共享
- 子类构建实例时可以向父类传递参数
缺点
- 会调用两次父类的构造函数
备注
怎么还有缺点,不着急后续还有解决方案
原型继承
function Book() {
this.name = 'Js book';
this.alikeBook = ['css book'];
}
Book.prototype.getName = function() {
console.log(‘getName执行了’);
}
/*==== 核心函数 ====*/
function inheritObject(SubClass) {
function F() {} // 子类;
F.prototype = SubClass.prototype;
return new F();
}
let newBook = inheritObject(Book);
newBook.getName(); // getName执行了
console.log(newBook.name); // undefined
特点
- 见名知意,该方式可以仅仅继承父类上的原型属性,不继承父类本身的属性
备注
- 该继承其实就是类式继承的一次改造
- 乍一看,特别容易得出一种结论:这不就是封装了一下原型模式么。但当认真分析函数后会发现,这种继承方式其实是在原型继承基础上有选择的继承了父类原型上的内容
寄生继承
/*==== 原型继承核心 ===*/
function inheritObject(o) {
function F() {};
F.prototype = o;
return new F();
}
/*==== 寄生继承核心 ====*/
function createBook(obj) {
const o = inheritObject(obj);
o.getName = function() {
console.log(this.name);
}
return o;
}
let book = {
name: 'Js book',
alikeBook: ['css book'],
}
let newBook = createBook(book);
newBook.name = 'Ajax book';
newBook.alikeBook.push('xml book');
newBook.getName(); // Ajax book
console.log(newBook.alikeBook); // [ 'css book', 'xml book' ]
let otherBook = createBook(book);
otherBook.name = 'flash book';
otherBook.getName(); // flash book
console.log(otherBook.alikeBook); // [ 'css book', 'xml book' ]
特点
- 对原型继承产生的对象进行扩展
备注
- 该继承其实就是原型继承的一次改造, 寄生继承是的寄生对象是原型继承的产物
- 在此我们观察
inheritObject
函数,每次使用寄生继承都会创建一个空函数F,这样有些浪费内存,此时可以将该函数做成一个单例函数F以避免频繁创建,当完成这一步,我们便实现了ES6中Object.crate()方法
寄生组合继承
/*==== 原型继承核心 ====*/
function inheritObject(o) {
function F() {}
F.prototype = o;
return new F();
}
/*==== 寄生继承核心 ====*/
function inheritPrototype(subClass, superClass) {
let p = inheritObject(superClass.prototype);
p.constructor = subClass; // 纠正子类constructor的指向
subClass.prototype = p;
}
function SuperClass(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperClass.prototype.getName = function() {
console.log(this.name);
}
function SubClass(name, time) {
/*==== 构造函数继承核心 ====*/
SuperClass.call(this, name);
this.time = time;
}
inheritPrototype(SubClass, SuperClass);
let instance1 = new SubClass('Js book', 2014);
let instance2 = new SubClass('css book', 2013);
console.log(instance1.colors);
console.log(instance2.colors);
instance2.getName();
特点
- 解决了组合继承中父类构造函数会调用两次的问题
备注
- ES6中的
class
继承就是该继承方式的一种语法糖 - 该继承是寄生寄生和组合继承的的融合,通过寄生继承中只操作原型的方式实现原型继承,而不是类式继承中创建对象的方式,从而避免了父类构造函数调用两次的问题
总结
其实分析前五种继承方式的过程就是寄生组合继承的推导过程,推导过程如下: