原生Javascript如何实现继承以及其优缺点

577 阅读4分钟

最近在复习javascript的一些基础知识,为开启新的征程做准备。所以开始记录一些自己学习的内容。 那今天的主题是 js的原生继承方式

废话少说,上代码!

首先是我们的父类代码。 在这里我们创建一个Person的类作为父类,它的构造函数需要2个参数nameage。 然后我们在它的原型上添加一个sayHi的方法。

//父类
function Person (name, age) {
    this.name = name || 'no name';
    this.age = age || 0;
}

Person.prototype.sayHi = function () {
    console.log('Hi, I\'m ' + this.name + ' and i\'m ' + this.age + ' years old!');
}

var p = new Person('A',20);
p.sayHi();//Hi, I'm A and i'm 20 years old!


原型继承


//原型继承
function Teacher(){
}

Teacher.prototype=new Person('B',22);
Teacher.prototype.constructor=Teacher;

var t = new Teacher();
t.sayHi();//Hi, I'm B and i'm 22 years old!
console.log(t instanceof Person);//true
console.log(t instanceof Teacher);//true

优点

从上面的代码来看,Teacher 的实例拥有了 Person 的属性和方法。并且实例对象既是 Person的实例也是 Teacher的实例。而且这种继承方式特别的简单。

缺点

我们可以很容易的就发现Teacher类的 nameage是固定的,都是name=B和age=22,换句话说就是我们无法实现按照我们的意愿给父类的构造函数传参。并且一个我们不能给一个 Teacher 指定多个原型,也就是没法 多继承。然后我们看下下面这段代码:

var t1 = new Teacher();
var t2 = new Teacher();
Teacher.prototype.name = "C";
t1.sayHi();//Hi, I'm C and i'm 22 years old!
t2.sayHi();//Hi, I'm C and i'm 22 years old!

上面这段代码中我们可以看到当原型中的属性或者方法被改变时,所有的子类实例的属性和方法也会跟着被改变,也就是原型继承的另一个缺点:所有子类共享同一个原型对象

这里说到了原型,我很早之前也写过一个关于原型的随笔,不过可能也是有些模糊,现在的理解和当时有所不同,我会在后面重新写一篇关于原型的随笔。(写好了我会附上连接)

构造函数继承

//构造函数继承
function Teacher (name, age) {
    Person.call(this, name, age);
}

var t1 = new Teacher('B', 22);
var t2 = new Teacher('C', 30);
console.log(t1.name);//B
console.log(t2.name);//C
console.log(t1 instanceof Person);//false
console.log(t1 instanceof Teacher);//true
t1.sayHi();//TypeError: t1.sayHi is not a function
t2.sayHi();//TypeError: t1.sayHi is not a function

优点

相对于 原型继承构造函数继承解决了所有的子类实例共享统一原型的问题,也可以给父类的构造函数传参,并且我们可以在子类的构造函数中调用多个父类的构造函数,实现所谓的多继承(这里的多继承是指子类通过call,apply等方法去调用父类的构造函数使其拥有父类的属性和方法,但是js中一个函数对象只存在一个 prototype,所以其实我们没法通过原型链的形式去体现出多继承)

缺点

上面的代码中我们可以看出创建的实例只是 子类的实例 并不是 父类的实例 ,不能直观的体现出继承,这种继承方式也无法继承父类的原型上的属性和方法。

组合式继承

//组合式继承
function Teacher (name, age) {
    Person.call(this, name, age);
}

Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;


var t1 = new Teacher('B', 22);
var t2 = new Teacher('C', 30);
Teacher.prototype.name = "D";
console.log(t1.name);//B
console.log(t2.name);//C
t1.sayHi();//Hi, I'm B and i'm 22 years old!
t2.sayHi();//Hi, I'm C and i'm 30 years old!
console.log(t1 instanceof Person);//true
console.log(t1 instanceof Teacher);//true


组合式继承就是结合了原型继承构造函数继承的优点,解决了两种方式存在的一些缺点。但是我们会发现每当我们去创建一个子类实例的时候都会去创建一个父类的实例,尽管父类实例不是同一个实例(内存地址不一样),但是他们其实属性和方法上完全一致,所以我们通过下面这种(寄生式组合继承)方式完善它,以避免不必要的实例构造。


寄生式组合继承


//寄生式组合继承
function Teacher (name, age) {
    Person.call(this, name, age);
}

Teacher.prototype = Object.create(Person.prototype);
Teacher.prototype.constructor = Teacher;


var t1 = new Teacher('B', 22);
var t2 = new Teacher('C', 30);
Teacher.prototype.name = "D";
console.log(t1.name);//B
console.log(t2.name);//C
t1.sayHi();//Hi, I'm B and i'm 22 years old!  
t2.sayHi();//Hi, I'm C and i'm 30 years old!
console.log(t1 instanceof Person);//true
console.log(t1 instanceof Teacher);//true 

上面的方式解决了我们没创建一个子类实例都去创建一个父类实例的问题,这也是最为常用的一种js的继承方式,如果我们通过Babel去把ES6中的class的继承转成ES5的代码,我们会发现就是用的寄生式组合继承。

好的,到这里我把我常见的原生js继承方式及其优缺点就写完,如果你们看到了错误的点,欢迎留言指正~