继承 inherit
class 是对原型继承的一种语法糖的包装。那相对于原型继承,它有什么优点呢?
我们来先看一个典型的基于原型链继承的例子。部分内容来自“Javascript高级程序设计”
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
问题,当包含引用类型的值。
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 原型链继承
(欠图一张)extends
从es5来说,实现对象的继承,还是相当麻烦的。而extends 关键字的出现,使继承变得简单,原型会自动进行调整,super()/super关键字可以访问父类的构造方法和属性。
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类没有构造函数,这样合理吗?
// 等价于上个类定义 class Dog extends Animal { constructor(name) { super(name) } speak() { console.log(this.name + ' barks.'); } }
super()方法调用注意:
- 只可在以extends 实现的派生类中的constructor方法中调用,在非派生类或方法中直接调用,会报错。
- 在constructor中访问this之前,一定要先调用super(),因为它负责初始化this,如果在super()调用之前尝试访问this,会报错。
- 如果不想调用super(),则唯一的方法是让类的constructor()返回一个对象。
类方法遮蔽
说明:派生类中的方法总会覆盖基类中的同名方法。
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特殊的地方。
Symbol.species
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的构造函数。
constructor中new.target
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();}}
这个就是类的了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。