您好,欢迎访问一九零五行业门户网

ES6 Class 继承与 super的介绍

这篇文章主要介绍了关于es6 class 继承与 super的介绍,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下
class 继承与 superclass 可以 extends 自另一个 class。这是一个不错的语法,技术上基于原型继承。
要继承一个对象,需要在 {..} 前指定 extends 和父对象。
这个 rabbit 继承自 animal:
class animal {  constructor(name) {    this.speed = 0;    this.name = name;  }  run(speed) {    this.speed += speed;    alert(`${this.name} runs with speed ${this.speed}.`);  }  stop() {    this.speed = 0;    alert(`${this.name} stopped.`);  }}// inherit from animalclass rabbit extends animal {  hide() {    alert(`${this.name} hides!`);  }}let rabbit = new rabbit(white rabbit);rabbit.run(5); // white rabbit runs with speed 5.rabbit.hide(); // white rabbit hides!
如你所见,如你所想,extend 关键字实际上是在 rabbit.prototype 添加 [prototype]],引用到 animal.prototype。
所以现在 rabbit 既可以访问它自己的方法,也可以访问 animal 的方法。
extends 后可跟表达式class 语法的 `extends' 后接的不限于指定一个类,更可以是表达式。
例如一个生成父类的函数:
function f(phrase) {  return class {    sayhi() { alert(phrase) }  }}class user extends f(hello) {}new user().sayhi(); // hello
例子中,class user 继承了 f('hello')返回的结果。
对于高级编程模式,当我们使用的类是根据许多条件使用函数来生成时,这就很有用。
重写一个方法现在让我们进入下一步,重写一个方法。到目前为止,rabbit 从 animal 继承了 stop 方法,this.speed = 0。
如果我们在 rabbit 中指定了自己的 stop,那么会被优先使用:
class rabbit extends animal {  stop() {    // ...this will be used for rabbit.stop()  }}
......但通常我们不想完全替代父方法,而是在父方法的基础上调整或扩展其功能。我们进行一些操作,让它之前/之后或在过程中调用父方法。
class 为此提供 super关键字。
使用 super.method(...) 调用父方法。
使用 super(...) 调用父构造函数(仅在 constructor 函数中)。
例如,让兔子在 stop 时自动隐藏:
class animal {  constructor(name) {    this.speed = 0;    this.name = name;  }  run(speed) {    this.speed += speed;    alert(`${this.name} runs with speed ${this.speed}.`);  }  stop() {    this.speed = 0;    alert(`${this.name} stopped.`);  }}class rabbit extends animal {  hide() {    alert(`${this.name} hides!`);  }  stop() {    super.stop(); // call parent stop    this.hide(); // and then hide  }}let rabbit = new rabbit(white rabbit);rabbit.run(5); // white rabbit runs with speed 5.rabbit.stop(); // white rabbit stopped. white rabbit hides!
现在,rabbit 的 stop 方法通过 super.stop() 调用父类的方法。
箭头函数无 super正如在 arrow-functions 一章中提到,箭头函数没有 super。
它会从外部函数中获取 super。例如:
class rabbit extends animal {  stop() {    settimeout(() => super.stop(), 1000); // call parent stop after 1sec  }}
箭头函数中的 super 与 stop() 中的相同,所以它按预期工作。如果我们在这里用普通函数,便会报错:
// unexpected supersettimeout(function() { super.stop() }, 1000);
重写构造函数对于构造函数来说,这有点棘手 tricky。
直到现在,rabbit 都没有自己的 constructor。
till now, rabbit did not have its own constructor.
根据规范,如果一个类扩展了另一个类并且没有 constructor ,那么会自动生成如下 constructor:
class rabbit extends animal {  // generated for extending classes without own constructors  constructor(...args) {    super(...args);  }}
我们可以看到,它调用了父 constructor 传递所有参数。如果我们不自己写构造函数,就会发生这种情况。
现在我们将一个自定义构造函数添加到 rabbit 中。除了name,我们还会设置 earlength:
class animal {  constructor(name) {    this.speed = 0;    this.name = name;  }  // ...}class rabbit extends animal {  constructor(name, earlength) {    this.speed = 0;    this.name = name;    this.earlength = earlength;  }  // ...}// doesn't work!let rabbit = new rabbit(white rabbit, 10); // error: this is not defined.
哎呦出错了!现在我们不能生成兔子了,为什么呢?
简单来说:继承类中的构造函数必须调用 super(...),(!)并且在使用 this 之前执行它。
...但为什么?这是什么情况?嗯...这个要求看起来确实奇怪。
现在我们探讨细节,让你真正理解其中缘由 ——
在javascript中,继承了其他类的构造函数比较特殊。在继承类中,相应的构造函数被标记为特殊的内部属性 [[constructorkind]]:“derived”。
区别在于:
当一个普通的构造函数运行时,它会创建一个空对象作为 this,然后继续运行。
但是当派生的构造函数运行时,与上面说的不同,它指望父构造函数来完成这项工作。
所以如果我们正在构造我们自己的构造函数,那么我们必须调用 super,否则具有 this 的对象将不被创建,并报错。
对于 rabbit 来说,我们需要在使用 this 之前调用 super(),如下所示:
class animal {  constructor(name) {    this.speed = 0;    this.name = name;  }  // ...}class rabbit extends animal {  constructor(name, earlength) {    super(name);    this.earlength = earlength;  }  // ...}// now finelet rabbit = new rabbit(white rabbit, 10);alert(rabbit.name); // white rabbitalert(rabbit.earlength); // 10
super 的实现与 [[homeobject]]让我们再深入理解 super 的底层实现,我们会看到一些有趣的事情。
首先要说的是,以我们迄今为止学到的知识来看,实现 super 是不可能的。
那么思考一下,这是什么原理?当一个对象方法运行时,它将当前对象作为 this。如果我们调用 super.method(),那么如何检索 method?很容易想到,我们需要从当前对象的原型中取出 method。从技术上讲,我们(或javascript引擎)可以做到这一点吗?
也许我们可以从 this 的 [[prototype]] 中获得方法,就像 this .__ proto __.method 一样?不幸的是,这是行不通的。
让我们试一试,简单起见,我们不使用 class 了,直接使用普通对象。
在这里,rabbit.eat() 调用父对象的 animal.eat() 方法:
let animal = {  name: animal,  eat() {    alert(`${this.name} eats.`);  }};let rabbit = {  __proto__: animal,  name: rabbit,  eat() {    // that's how super.eat() could presumably work    this.__proto__.eat.call(this); // (*)  }};rabbit.eat(); // rabbit eats.
在 (*) 这一行,我们从原型(animal)中取出 eat,并以当前对象的上下文中调用它。请注意,.call(this) 在这里很重要,因为只写 this .__ proto __.eat() 的话 eat 的调用对象将会是 animal,而不是当前对象。
以上代码的 alert 是正确的。
但是现在让我们再添加一个对象到原型链中,就要出事了:
let animal = {  name: animal,  eat() {    alert(`${this.name} eats.`);  }};let rabbit = {  __proto__: animal,  eat() {    // ...bounce around rabbit-style and call parent (animal) method    this.__proto__.eat.call(this); // (*)  }};let longear = {  __proto__: rabbit,  eat() {    // ...do something with long ears and call parent (rabbit) method    this.__proto__.eat.call(this); // (**)  }};longear.eat(); // error: maximum call stack size exceeded
噢,完蛋!调用 longear.eat() 报错了!
这原因一眼可能看不透,但如果我们跟踪 longear.eat() 调用,大概就知道为什么了。在 (*) 和 (**) 两行中, this 的值是当前对象(longear)。重点来了:所有方法都将当前对象作为 this,而不是原型或其他东西。
因此,在两行 (*) 和 (**) 中,this.__ proto__ 的值都是 rabbit。他们都调用了 rabbit.eat,于是就这么无限循环下去。
情况如图:
1.在 longear.eat() 里面,(**) 行中调用了 rabbit.eat,并且this = longear。
// inside longear.eat() we have this = longearthis.__proto__.eat.call(this) // (**)// becomeslongear.__proto__.eat.call(this)// that israbbit.eat.call(this);
2.然后在rabbit.eat的 (*) 行中,我们希望传到原型链的下一层,但是 this = longear,所以 this .__ proto __.eat又是 rabbit.eat!
// inside rabbit.eat() we also have this = longearthis.__proto__.eat.call(this) // (*)// becomeslongear.__proto__.eat.call(this)// or (again)rabbit.eat.call(this);
...因此 rabbit.eat 在无尽循环调动,无法进入下一层。
这个问题不能简单使用 this 解决。
[[homeobject]]为了提供解决方案,javascript 为函数添加了一个特殊的内部属性:[[homeobject]]。
当函数被指定为类或对象方法时,其 [[homeobject]] 属性为该对象。
这实际上违反了 unbind 函数的思想,因为方法记住了它们的对象。并且 [[homeobject]] 不能被改变,所以这是永久 bind(绑定)。所以在 javascript 这是一个很大的变化。
但是这种改变是安全的。 [[homeobject]] 仅用于在 super 中获取下一层原型。所以它不会破坏兼容性。
让我们来看看它是如何在 super 中运作的:
let animal = {  name: animal,  eat() {         // [[homeobject]] == animal    alert(`${this.name} eats.`);  }};let rabbit = {  __proto__: animal,  name: rabbit,  eat() {         // [[homeobject]] == rabbit    super.eat();  }};let longear = {  __proto__: rabbit,  name: long ear,  eat() {         // [[homeobject]] == longear    super.eat();  }};longear.eat();  // long ear eats.
每个方法都会在内部 [[homeobject]] 属性中记住它的对象。然后 super 使用它来解析原型。
在类和普通对象中定义的方法中都定义了 [[homeobject]],但是对于对象,必须使用:method() 而不是 method: function()。
在下面的例子中,使用非方法语法(non-method syntax)进行比较。这么做没有设置 [[homeobject]] 属性,继承也不起作用:
let animal = {  eat: function() { // should be the short syntax: eat() {...}    // ...  }};let rabbit = {  __proto__: animal,  eat: function() {    super.eat();  }};rabbit.eat();  // error calling super (because there's no [[homeobject]])
静态方法和继承class 语法也支持静态属性的继承。
例如:
class animal {  constructor(name, speed) {    this.speed = speed;    this.name = name;  }  run(speed = 0) {    this.speed += speed;    alert(`${this.name} runs with speed ${this.speed}.`);  }  static compare(animala, animalb) {    return animala.speed - animalb.speed;  }}// inherit from animalclass rabbit extends animal {  hide() {    alert(`${this.name} hides!`);  }}let rabbits = [  new rabbit(white rabbit, 10),  new rabbit(black rabbit, 5)];rabbits.sort(rabbit.compare);rabbits[0].run(); // black rabbit runs with speed 5.
现在我们可以调用 rabbit.compare,假设继承的 animal.compare 将被调用。
它是如何工作的?再次使用原型。正如你猜到的那样,extends 同样给 rabbit 提供了引用到 animal的 [prototype]。
所以,rabbit 函数现在继承 animal 函数。animal 自带引用到 function.prototype 的 [[prototype]](因为它不 extend 其他类)。
看看这里:
class animal {}class rabbit extends animal {}// for static propertites and methodsalert(rabbit.__proto__ === animal); // true// and the next step is function.prototypealert(animal.__proto__ === function.prototype); // true// that's in addition to the normal prototype chain for object methodsalert(rabbit.prototype.__proto__ === animal.prototype);
这样 rabbit 可以访问 animal 的所有静态方法。
在内置对象中没有静态继承请注意,内置类没有静态 [[prototype]] 引用。例如,object 具有 object.defineproperty,object.keys等方法,但 array,date 不会继承它们。
date 和 object 的结构:
date 和 object 之间毫无关联,他们独立存在,不过 date.prototype 继承于 object.prototype,仅此而已。
造成这个情况是因为 javascript 在设计初期没有考虑使用 class 语法和继承静态方法。
原生拓展array,map 等内置类也可以扩展。
举个例子,powerarray 继承自原生 array:
// add one more method to it (can do more)class powerarray extends array {  isempty() {    return this.length === 0;  }}let arr = new powerarray(1, 2, 5, 10, 50);alert(arr.isempty()); // falselet filteredarr = arr.filter(item => item >= 10);alert(filteredarr); // 10, 50alert(filteredarr.isempty()); // false
请注意一件非常有趣的事情。像 filter,map 和其他内置方法 - 返回新的继承类型的对象。他们依靠 constructor 属性来做到这一点。
在上面的例子中,
arr.constructor === powerarray
所以当调用 arr.filter() 时,它自动创建新的结果数组,就像 new powerarray 一样,于是我们可以继续使用 powerarray 的方法。
我们甚至可以自定义这种行为。如果存在静态 getter symbol.species,返回新建对象使用的 constructor。
下面的例子中,由于 symbol.species 的存在,map,filter等内置方法将返回普通的数组:
class powerarray extends array {  isempty() {    return this.length === 0;  }  // built-in methods will use this as the constructor  static get [symbol.species]() {    return array;  }}let arr = new powerarray(1, 2, 5, 10, 50);alert(arr.isempty()); // false// filter creates new array using arr.constructor[symbol.species] as constructorlet filteredarr = arr.filter(item => item >= 10);// filteredarr is not powerarray, but arrayalert(filteredarr.isempty()); // error: filteredarr.isempty is not a function
我们可以在其他 key 使用 symbol.species,可以用于剥离结果值中的无用方法,或是增加其他方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注!
相关推荐:
用node编写restful api接口
async/await 并行请求和错误处理
以上就是es6 class 继承与 super的介绍的详细内容。
其它类似信息

推荐信息