1、ES5中的继承模式
我们先看ES5中的继承。
既然要实现继承,首先我们得要有一个父类。
Animal.prototype.eat = function(food) { console.log(this.name + '正在吃' + food); } function Animal(name) { this.color = ['green','red','blue']; this.name = name || 'animal'; this.sleep = function() { console.log(this.name + "正在睡觉") } }
1.1、原型链继承
原型链继承核心: 将父类的实例作为子类的原型。
function Cat(name) { this.name = name this.color = ['green','red','blue'];//引用类型值,,所有实例会共享这个属性。 } Cat.prototype = new Animal(); var cat = new Cat('cat'); console.log(cat.name); console.log(cat.eat('fish')); console.log(cat instanceof Animal); console.log(cat.sleep());
原型链式继承模式实现了子类对父类的原型的继承。
但是,原型链式继承并没有实现代码的复用,一些共同的属性:如name,在子类中还是得重新写一遍(即同一套代码还是得重新写)。
再者,cat继承了Animal实例的所有属性和方法,这些方法并不都是我们需要的,也就是过多的继承了没有用的属性。且如果原型中包含引用类型值,那么所有的实例会共享这个属性。
1.2、构造函数模式
构造函数模式核心: 在子类型构造函数的内部调用超类型构造函数。
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } function Student(name,age,sex){ Person.call(this,name,age,sex); this.grade = grade; } let student = new Student;
优点:
- 构造函数模式继承实现了代码的复用
缺点:
- 不能继承借用的构造函数的原型,只能借用构造函数本身的属性和方法
- 每次构造函数都要多走一个函数
1.3、组合继承
实现核心:组合继承结合了上面两种方式的继承模式,即实现了代码复用,也实现呢了原型继承。
function SuperType(name) { this.name = name; this.colors = ['red','blue','pink']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name,age) { //继承属性 SuperType.call(this,name);//在创建实例时第二次调用SuperType this.age = age; } //继承方法 SubType.prototype = new SuperType();//第一次调用SuperType SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age) }
缺点:
- 父类构造函数被调用2次,子类实例的属性存在两份,一份在原型上,一份在实例属性上。造成内存的浪费。
1.4、寄生组合式继承
寄生组合式继承是对组合继承的进一步优化。我们先看一下为什么要写这个语句。
SubType.prototype = new SuperType();
我们无非是想让SubType继承SuperType的原型。但是我们为什么不直接写成这样呢?
SubType.prototype = SuperType.prototype
这样写确实可以实现子类对象对父类对象原型的继承。但是这样写的话:所有继承该父类的子类对象的原型都指向同一个了。也就是说SubType不能有自己的原型了。这显然不是我们想要的。
既然不能直接继承,那可不可以间接继承SuperType.prototype呢。这就是最终的解决方案:寄生组合式继承。
我们让一个函数去指向SuperType.prototype,然后让SubType.prototype指向这个函数产生的对象不就可以了嘛。
function inherit(Target,Origin) {//实现寄生组合式继承的核心函数 function F() {}; F.prototype = Origin.prototype; //F()的原型指向的是Origin Target.prototype = new F(); //Target的原型指向的是F() Target.prototype.constructor = Target;
SubType.prototype.__proto__ == SuperType.prototype } function SuperType(name) { this.name = name; this.colors = ['red','blue','pink']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name,age) { //继承属性 SuperType.call(this,name);//在创建实例时第二次调用SuperType this.age = age; } inherit(SubType,SuperType);//实现寄生组合式继承
我们再来看一下实现寄生组合式继承的核心函数。F函数其实是通用的,我们没必要每次进入inherit函数时都声明一遍。所以我们可以用闭包的形式来写:
var inherit = (function () { var F = function () {}; return function (Target , Origin) { F.prototype = Origin.prototype;//F()的原型指向的是Origin Target.prototype = new F();//Target的原型指向的是F() Target.prototype.constructor = Target; Target.prototype.uber = Origin.prototype;
SubType.prototype.__proto__ == SuperType.prototype } })()
2、ES6中的类继承
我们先来看一下ES6里面是如何定义一个类的。
class Parent { constructor(name,age) { this.name = name; this.age = age; } getName() { return this.name; } }
typeof Parent; //function,类的数据类型就是函数,类本身就指向构造函数
上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Parent,对应ES6的Parent类的构造方法。
Parent类除了构造方法,还定义了一个getName方法。
注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去就可以了。另外,方法之间不需要逗号分隔,加了会报错。类中定义的所有方法都是不可枚举的。
此外:类的所有方法都定义在类的prototype属性上面。且class不存在变量提升,如果在class声明之前调用,会报错。
new Parent(); class Parent { constructor(name,age) { this.name = name; this.age = age; } getName() { return this.name; } } //Uncaught ReferenceError: Parent is not defined
2.1、类的constructor方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,
如果没有显式定义,一个空的constructor方法会被默认添加。
2.2、类的继承
class Child extends Parent { constructor(sex) { super(); this.sex = sex; } } var child = new Child('xiaoyu',12,'man');
在子类的构造函数中,如果显示声明了constructor,则必须要显示的调用super函数(这一点和Java有点不一样)。
只有调用super之后,才可以使用this关键字,否则会报错。
2.3、类的prototype属性和__proto__属性
大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。
Class作为构造函数的语法糖,同时有 prototype属性和__proto__属性,因此同时存在两条继承链。
- 子类的__proto__属性,表示类的继承,总是指向父类。
- 子类prototype属性,表示类的实例的继承,类的实例的__proto__属性总是指向类的prototype属性。
这些特点和ES5的寄生组合式继承完全一致,所以类的继承可以看做是寄生组合式继承的语法糖(简单理解)。但实际上两者实现的底层原理是完全不一样的。因为阮一峰大大的书籍《ES6入门基础》里面认为ES6的继承机制完全和ES5的继承机制不同。阮一峰大大的书是这么说的。
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
所以对这方面有深入理解的小伙伴们可以说说自己的理解。共同进步。
先写到这里,后面如果有更多的理解再继续添加。