什么是继承
可以实现一个类创建的实例拥有另一个类的属性和方法
1.原型链继承
思路:子类的原型指向父类实例,通过子类实例的原型链向上查找达到继承的作用
实现
// 父类
function Parent(age) {
this.name = 'bonly';
this.age = age;
this.hobby = ['basketball','pingpong']
}
// 子类
function Child() {
}
let p = new Parent('18');
Child.prototype = p;
let child1 = new Child();
let child2 = new Child();
console.log(child1.name); // bonly
console.log(child2.name); // bonly
/**
* 1. 所有子类实例的属性都指向构造函数的原型也就是父类实例
*
*/
console.log(child1.name === child2.name); // true
/**
* 2. 父类实例修改属性后,所有子类实例的属性都会被修改
*/
p.name = 'bonly1';
console.log(child1.name); // bonly1
console.log(child2.name); // bonly1
/**
* 3. 引用类型的问题
*/
child1.hobby.push('skiing')
console.log(child1.hobby);
console.log(child2.hobby);
/**
* 4. 我们无法为不同的实例初始化继承来的属性,可以看到所有实例的年龄都是18
*/
console.log(child1.age); // 18
console.log(child2.age); // 18
特点
- 父类新增原型方法、原型属性,子类都能访问到
- 简单,易于实现
问题
- 无法实现多继承
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数中传递参数
- 要想为子类新增属性和方法,必须要在Child.prototype = new Parent();之后执行,不能放到构造器中
2. 构造函数(经典继承)
思路:在子类构造函数的内部调用父类行构造函数
实现
// 父类
function Parent(age) {
this.name = 'bonly';
this.age = age;
this.hobby = ['basketball','pingpong']
}
Parent.prototype.say = function(){
console.log(`我今年${this.age}`);
}
// 子类
function Child(...args) {
Parent.call(this,...args)
}
/**
* 只能实现部分继承,会继承父类的属性和方法,不能继承父类原型的属性和方法
*/
let child1 = new Child(18);
let child2 = new Child(20);
console.log(child1); //Child { name: 'bonly', age: 18, hobby: [ 'basketball', 'pingpong' ] }
console.log(child2); // Child { name: 'bonly', age: 20, hobby: [ 'basketball', 'pingpong' ] }
child1.hobby.push('skiing')
console.log(child1.hobby); // [ 'basketball', 'pingpong', 'skiing' ]
console.log(child2.hobby); // [ 'basketball', 'pingpong' ]
特点
- 可以实现多继承
- 可以创建实例的时候给构造函数传递参数
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 可以实现多继承
问题
- 实例并不是父类的实例,只是子类的实例
- 只能实现继承父类的构造函数的属性和方法,不能继承父类原型上的方法
- 无法实现函数复用,每个子类都有父类实例函数的副本(父类实例函数指的是什么?),影响性能
3. 组合继承(构造函数+原型链)
思路:子类调用父类构造,子类原型指向父类实例,然后修复子类构造函数指向
实现
// 父类
function Parent(age) {
this.name = 'bonly';
this.age = age;
this.hobby = ['basketball','pingpong']
}
Parent.prototype.say = function(){
console.log(`我今年${this.age}`);
}
// 子类
function Child(...args) {
Parent.call(this,...args)
}
Child.prototype = Object.create(Person.prototype,{
constructor:{
value:Child,
enumerable:false,
writable:true,
configurable:true
}
});
console.log(Child.prototype.constructor); // [Function: Parent]
Child.prototype.constructor = Child; // 修复构造函数原型的指向
console.log(Child.prototype.constructor); // [Function: Child]
let child1= new Child(18);
let child2 = new Child(20);
child1.hobby.push('skiing');
console.log(child1); //Child { name: 'bonly', age: 18, hobby: [ 'basketball', 'pingpong', 'skiing' ]}
console.log(child2); // Child { name: 'bonly', age: 20, hobby: [ 'basketball', 'pingpong' ] }
child1.say(); // 18
child2.say(); // 20
特点
- 可以继承实例的属性/方法,也可以继承父类原型的方法
- 可传递参数,不存在引用属性共享问题
- 创建实例的时候可传递参数
- 函数可以复用(哪个函数实现的复用,是父类原型的方法吗??)
4. 原型继承
思路:在creat方法的内部先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例
实现
// 也是object.create的实现原理
function create(obj) {
function F() { }
F.prototype = obj;
return new F();
}
let Parent = {
name: 'bonly',
age: '18',
hobby: ['basketball', 'pingpong'],
say(){
console.log(`我今年${this.age}`);
}
}
let child1 = create(Parent);
let child2 = create(Parent);
child1.say();
child2.say();
console.log(child1.name);
console.log(child2.name);
child1.hobby.push('shiing')
console.log(child1.hobby); // [ 'basketball', 'pingpong', 'shiing' ]
console.log(child2.hobby); // [ 'basketball', 'pingpong', 'shiing' ]
let child3 = Object.create(Parent);
console.log(child3.hobby===child1.hobby); // true
child3.say();
特点
- 可以继承实例的属性和方法
- 方法需要写到对象上,子类才能共享
问题
- 无法通过传递参数来创建实例(创建两个不同年龄的子类实例)
- 传入对象的属性有引用类型,所有实例都会共享相应的值
5. 寄生式继承
思路:在原型式的基础上,包装一个函数,用来给创建出的实例增加自身的方法
实现
// 创建对象
function create(obj) {
function F() { }
F.prototype = obj;
return new F();
}
// 扩展对象
function extend(obj) {
let clone = create(obj);
clone.hi = function(){
console.log(`hi,我是${this.name}`);
}
return clone;
}
let Parent = {
name: 'bonly',
age: '18',
hobby: ['basketball', 'pingpong'],
say() {
console.log(`我今年${this.age}`);
}
}
let child1 = extend(Parent);
let child2 = extend(Parent);
child1.say();
child2.say();
child1.hi();
child2.hi();
let child3 = Object.create(Parent);
console.log(child3.hobby === child1.hobby); // true
child3.say();
问题
- 在增强对象内为不同实例增加函数,函数不能复用
6. 寄生组合式继承
思路:在增强对象的时候把子类的构造函数指向子类
实现
// 父类
function Parent(age) {
this.name = 'bonly';
this.age = age;
this.hobby = ['basketball','pingpong']
}
Parent.prototype.say = function(){
console.log(`我今年${this.age}`);
}
// 子类
function Child(age) {
Parent.call(this,age)
}
// 创建对象
function create(obj) {
function F() { }
F.prototype = obj;
return new F();
}
// 扩展对象
function extend(subClass,superClass) {
let clone = create(superClass.prototype); // 创建对象
// 增强对象,达到函数复用
clone.constructor = subClass; // 增强对象
subClass.prototype = clone; // 指定对象
}
extend(Child,Parent);
let child1 = new Child(18);
let child2 = new Child(20);
child1.say();// 我今年18
child2.say();// 我今年20
console.log(child1.name); // bonly
console.log(child2.name); // bonly
7. es6继承
原理:ES5是先创建子类的实例,然后在子类实例的基础上创建父类的属性。而ES6正好是相反的,是先创建父类的实例,然后在父类实例的基础上扩展子类属性。
实现
class Parent {
constructor(name,age) {
this.name = name;
this.age = age;
this.hobby = ['basketball','pingpong']
}
say(){
console.log(`我${this.age}岁了`);
}
}
class Child extends Parent{
constructor(name,age){
super(name,age);
}
}
let child1 = new Child('bonly1',18);
child1.say(); // 我18岁了
child1.hobby.push('shiing');
console.log(child1.hobby); // [ 'basketball', 'pingpong', 'shiing' ]
let child2 = new Child('bonly2',20);
child2.say(); // 我20岁了
console.log(child2.hobby); // [ 'basketball', 'pingpong' ]
问题
- 为什么一定要调用super方法
子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value),是为了给父类传递参数