# ES6 以前

在 es6 以前 js 并没有专门的关键字可以实现继承特性,但是那时候我们又需要使用继承。于是就产生了一些方法可以实现继承的效果

# 构造函数继承

call 调用实现继承父属性。我们都知道 call 可以改变函数内 this 的指向,利用这个特性我们可以在子构造函数里面调用父构造函数,并利用 call 让父构造函数内的 this 指向子构造函数。这样就完成了继承操作

function Father(uname,age){
	this.name = uname;
	this.age = age;
};
function Son(uname,age){
    Father.call(this,uname,age); // 调用父构造函数,利用 call 传入自身的 this 实现继承
}

重点:让新实例的原型等于父类的实例。
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

# 原型链继承

我们还可以通过让子构造函数的 prototype 指向一个父构造函数的实例的形式实现继承,不过需要注意的是在赋值完成后我们需要让 constructor 属性重新指回子构造函数

function Father(uname,age){
	this.name = uname;
	this.age = age;
};
Father.prototype.say = ()=> console.log('hello!');
function Son(uname,age){
    this.name = uname;
    this.age = age;
}
// Son.prototype = Father.prototype 若我们直接赋值,会导致 Son 的原型对象和 Father 的原型对象是同一个,在 Son 中修改原型对象同时也会修改 Father 的。所以这样赋值是错误的
Son.prototype = new Father();  // 通过一个 Father 实例化对象可以间接访问到 Father 的 prototype
Son.prototype.constructor = Son; // 让 constructor 指回 Son 构造函数,不然其会指向 Father 构造函数
let son = new Son('Asuka',16);
son.say(); //hello!

# 组合继承

function Father(uname,age){
	this.name = uname;
	this.age = age;
};
function Son(uname,age){
	Father.call(this,uname,age); // 借用构造函数继承
}
Son.prototype = new Father(); // 原型链继承
const s11 = new Son('zhangsan',18)

# ES6 以后

es6 中引入了 class 关键字同时也引入了 extends 关键字,利用这些关键字我们可以很轻易的实现继承。同时若我们想要调用父类的方法和属性只需要使用 super 关键字。

class Father{
	constructor(uname,age){
		this.name = uname;
		this.age = age;
	}
	say(){
		console.log('hello!');
	}
}
class Son extends Father{
    constructor(uname,age,sex){
        super(uname,age); // 直接使用,调用父类 constructor. 一定要在 this 之前,不然 SyntaxError
        this.sex = sex;
    }
    say(){
        super.say();
        console.log('hi!');
    }
}
let son = new Son('Asuhe',16,'male');
console.log(son); //Son { name: 'Asuhe', age: 16, sex: 'male' }
son.say();  // hello! hi!