JS中的常用继承方式
继承是面对对象(OO)语言的非常重要的一个特性,Javascript中的继承和的C++,java等传统面向对象语言的继承是有些区别的。在ES6(class,extends)标准出来之前,Javascript没有官方的标准来实现继承,一般采用原型链等方式实现继承。下面将分别讨论ES6前后继承方式的不同。
ES6之前的几种继承方式
ES6之前,提到继承,首先想到的肯定是原型链,我们首先讨论的就是利用原型链实现继承。
假设我们现在有如下代码(java/C++中成为超类或父类),有一个“人”的类,有一个实例属性name,一个实例方法sayName();一个原型属性age,一个原型方法sayAge();
function People(){
this.name = 'xxx';
this.sayName = function(){
console.log(`我的名字是:${this.name}`);
}
}
People.prototype.age = 18;
People.prototype.sayAge = function () {
console.log(`我的年龄是:${this.age}`);
}
//使用new生成一个对象
let p = new People();
p.sayName();//我的名字是:xxx
p.sayAge();//我的年龄是:18
1、原型链(prototype)
现在有一个学生类Student有自己的属性和方法,并且要继承People类中的属性和方法,采用原型链继承方式如下:
//学生类
function Student(){
this.job = 'student';
this.sayJob = function () {
console.log(`我的工作是:${this.job}`);
}
}
Student.prototype = new People();
let s = new Student();
s.sayName();
s.sayAge();
s.sayJob();
输出为:
我的名字是:xxx
我的年龄是:18
我的工作是:student
解释:把Student的原型(prototype)指向People的实例,People的实例和原型属性和方法都在Student的原型中了,因此实现了继承,这种继承方法有两个问题,一是People中的实例属性也被继承到Student的原型中,二是重写了原型,Student的prototype.constructor 指向了People(可手动修改构造器的指向Student.prototype.constructor = Student)。
2、借用构造函数
function Student(){
People.call(this);
this.job = 'student';
this.sayJob = function () {
console.log(`我的工作是:${this.job}`);
}
}
//仍然输出三个值
let s = new Student();
s.sayName();
//s.sayAge();//报错
s.sayJob();
解释:在Student中利用People.call(this)(修改People构造器中this的指向),实现实例属性和方法的继承;假如我们仍然输出三个属性,会发现s.sayAge()报错,这是因为Student并没有继承People的原型方法,这也是这种方法的缺陷。
3、组合继承
这种继承方式结合前两种继承的长处。代码如下:
function Student(){
People.call(this);
this.job = 'student';
this.sayJob = function () {
console.log(`我的工作是:${this.job}`);
}
}
Student.prototype = new People();
Student.prototype.constructor = Student;
let s = new Student();
s.sayName();
s.sayAge();
s.sayJob();
解释:People的实例属性和方法在Student的实例和方法中,People的原型属性和方法在Student的原型和方法中,这种继承方式的缺陷在于,Student中的实例属性的方法生成了两次,一份在Student的实例中,一份在原型中。
4、实例继承
function Worker(){
let instance = new People();
instance.job = 'worker';
instance.sayJob = function () {
console.log(`我的工作是:${this.job}`);
}
return instance;
}
let worker = new Worker();
worker.sayName();//我的名字是:xxx
worker.sayAge();//我的年龄是:18
worker.sayJob();//我的工作是:worker
解释:这种方法实际是在People实例上的基础上添加属性。不够灵活。
5、寄生组合式继承
观察上面几种继承方法,第三种方法相对比较其他几种方法,要更有优势,如果把其缺点修复,将是一种比较完美的方法,这种方法就是寄生组合式继承。代码如下:
//学生类
function Student(){
People.call(this);
this.job = 'student';
this.sayJob = function () {
console.log(`我的工作是:${this.job}`);
}
}
//继承原型
function inheritPrototype(subClass , superClass) {
let prototype = Object.create(superClass.prototype);
prototype.constructor = subClass;
subClass.prototype = prototype;
}
inheritPrototype(Student,People);
let s = new Student();
s.sayName();
s.sayAge();
s.sayJob();
这种方式比较修正方法3中的缺陷,应该是比较完善的方法。
ES6中的继承
ES6对于继承给出了官方的定义和方法。
代码如下:
//类的定义
class People{
constructor(name,age){
this.name = name;
this.age = age;
}
sayName(){
console.log(`我的名字是:${this.name}`);
}
sayAge(){
console.log(`我的年龄是:${this.age}`);
}
}
//继承
class Student extends People{
constructor(name,age,job){
super(name,age);
this.job = job;
}
sayJob(){
console.log(`我的工作是:${this.job}`);
}
}
//使用
let s = new Student('xxx',18,'student');
s.sayName();
s.sayAge();
s.sayJob();
解释:对传统面向对象语言熟悉的同学对这种类的定义和继承的语法肯定很熟悉。ES6标准提供了标准的方法,一般建议按照这种方法来实现继承,可以保证工程中代码的一致性。如果要兼容早期版本的浏览器,可以使用babel编译代码。