class 是对原型继承的一种语法糖的包装。那相对于原型继承,它有什么优势呢?
咱们来先看一个典型的基于原型链继承的例子。部份内容来自“Javascript高级程序设计”es6
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } function SubType() { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subProperty; } var instance = new SubType(); console.log(instance.getSuperValue()); // true console.log(instance instanceof Object); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof SubType); // true
问题,当包含引用类型的值。app
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { } SubType.prototype = new SuperType(); var instance = new SubType(); instance.colors.push("black"); var instance1 = new SubType(); instance1.colors.push("white"); console.log(instance.colors); // [ 'red', 'blue', 'green', 'black', 'white' ] console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black', 'white' ]
解决方案:函数
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { SuperType.call(this); } SubType.prototype = new SuperType(); var instance = new SubType(); instance.colors.push("black"); var instance1 = new SubType(); instance1.colors.push("white"); console.log(instance.colors); console.log(instance1.colors);
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { console.log(this.age); }
function object(o) { function F() {} F.prototype = o; return new F(); } function inheritPrototype(subType, superType) { let prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function() { console.log(this.age); } var instance = new SubType("Tom", 70); instance.colors.push("black"); var instance1 = new SubType("Jerry", 69); instance1.colors.push("white"); console.log(instance.colors); console.log(instance.sayName()); console.log(instance.sayAge()); console.log(instance1.colors); console.log(instance1.sayName()); console.log(instance1.sayAge());
MDN 原型链继承
(欠图一张)this
从es5来讲,实现对象的继承,仍是至关麻烦的。而extends 关键字的出现,使继承变得简单,原型会自动进行调整,super()/super关键字能够访问父类的构造方法和属性。es5
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } var d = new Dog('Mitzie'); d.speak();// 'Mitzie barks.'
分析:Dog类没有构造函数,这样合理吗?prototype
// 等价于上个类定义 class Dog extends Animal { constructor(name) { super(name) } speak() { console.log(this.name + ' barks.'); } }
super()方法调用注意:设计
说明:派生类中的方法总会覆盖基类中的同名方法。code
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } // 基类中的speak()方法被覆盖
说明:若是基类有静态成员,那么这些静态成员在派生类中也可用。对象
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } static create(name) { return new Animal(name); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } let a1 = Animal.create("Monkey"); let a2 = Dog.create("BeijinDog"); console.log(a1 instanceof Animal); // true console.log(a2 instanceof Animal); // true console.log(a2 instanceof Dog); // false 这个是否是很意外?
由ES6的class定义能够知道,是function的语法糖,但为实现原型继承,提供了方便的实现。JS的强大的一点就是函数能够返回函数,那若是返回类的定义呢?是否支持继承?返回对象是个函数,而且有[[Constrcutor]]属性和原型,就能知足extends实现。继承
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } function getBase() { return Animal; } class Dog extends getBase() { speak() { console.log(this.name + ' barks.'); } } const dog = new Dog('Tom'); dog.speak();
若是这个例子基于class的实现,有点取巧的意思,那看另外一个例子。
const SerializableMixin = { serialize() { return JSON.stringify(this); } } const AnimalMixin = { speak() { console.log(this.name + ' barks.'); } } function mixin(...mixins) { const base = function() {}; Object.assign(base.prototype, ...mixins); return base; } class Dog extends mixin(AnimalMixin, SerializableMixin) { constructor(name){ super(name); this.name = name; } } const dog = new Dog('Tom'); dog.speak(); // Tom barks.
关于function,class,extends,mixin,是否有新的理解呢?
在ES6以前,内建对象很难实现继承的,更多用has-a思想,实现对内建对象的处理。ES6中,大量内建对象的内部实现得以暴漏,也使得继承内建对象变成了可能。
class ColorsArray extends Array { } const colors = new ColorsArray(); colors[0] = 'red'; console.log(colors.length); // 1 colors.length = 0; console.log(colors[0]); // undefined
分析:基类(Array)建立 this 的值,而后派生类的构造函数(ColorsArray)再修改这个值。因此一开始能够经过this访问基类的全部内建功能,而后再正确地接收全部与之相关的功能。这与Array.apply/call 这种方法实现继承的this处理方式正好相反。这也是extends特殊的地方。
class ColorsArray extends Array { } const colors = new ColorsArray('red', 'green', 'blue'); const subColors = colors.slice(0,1); console.log(colors instanceof ColorsArray); // true console.log(subColors instanceof ColorsArray); // true
一般来说,slice 方法继承自 Array ,返回的应该是Array的实例,但在这个示例中,却返回的是ColorsArray的实例,这是为何呢?这是ES6中Symbol.species的功劳。Symbol.species MDN 详细说明
class MyArray extends Array { // Overwrite species to the parent Array constructor static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); console.log(mapped instanceof MyArray); // false console.log(mapped instanceof Array); // true
注意:重写实现的时候,使用getter+static,能够返回想用的类型,也能够返回 this,是的,你没看错,在static getter中使用了this,它指向的是MyArray的构造函数。
new.target是es6中新添加的元属性,只有经过new操做建立对象的时候,new.target才会被指向类/方法自己,经过call/apply操做,new.target为undefined。能够经过判断new.target,来确实函数是否容许new操做。MDN new.target 说明
惯例,再加个代码示例,偷懒,直接从MDN上拷了。
function Foo() { if (!new.target) throw 'Foo() must be called with new'; console.log('Foo instantiated with new'); } new Foo(); // logs "Foo instantiated with new" Foo(); // throws "Foo() must be called with new"
又是先说function,不是已经升级到ES6,使用class了吗?始终要有一个清楚的认识,class,是function实现原型继承的语法糖,但有本身的特性存在的(否则,也不用引入class了)。
class A { constructor() { console.log(new.target.name); } } class B extends A { constructor() { super(); } } var a = new A(); // logs "A" var b = new B(); // logs "B" class C { constructor() { console.log(new.target); } } class D extends C { constructor() { super(); } } var c = new C(); // logs class C{constructor(){console.log(new.target);}} var d = new D(); // logs class D extends C{constructor(){super();}}
这个就是类的了。