目录
Class 继承与 super
重写一个方法
重写构造函数
Super 的实现与 [[HomeObject]]
[[HomeObject]]
静态方法和继承
在内置对象中没有静态继承
原生拓展
首页 web前端 js教程 ES6 Class 继承与 super的介绍

ES6 Class 继承与 super的介绍

Jul 09, 2018 am 10:47 AM
class javascript

这篇文章主要介绍了关于ES6 Class 继承与 super的介绍,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

Class 继承与 super

class 可以 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 Animal
class 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

719959942-5b41c4dcd690b_articlex[1].png

所以现在 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')返回的结果。

对于高级编程模式,当我们使用的类是根据许多条件使用函数来生成时,这就很有用。

重写一个方法

现在让我们进入下一步,重写一个方法。到目前为止,RabbitAnimal 继承了 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!
登录后复制

现在,Rabbitstop 方法通过 super.stop() 调用父类的方法。

箭头函数无 super

正如在 arrow-functions 一章中提到,箭头函数没有 super

它会从外部函数中获取 super。例如:

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
登录后复制

箭头函数中的 superstop() 中的相同,所以它按预期工作。如果我们在这里用普通函数,便会报错:

// Unexpected super
setTimeout(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 fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(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,于是就这么无限循环下去。

情况如图:

1204806494-5b41c4dccebef_articlex[1].png

1.在 longEar.eat() 里面,(**) 行中调用了 rabbit.eat,并且this = longEar

// inside longEar.eat() we have this = longEar
this.__proto__.eat.call(this) // (**)
// becomes
longEar.__proto__.eat.call(this)
// that is
rabbit.eat.call(this);
登录后复制

2.然后在rabbit.eat(*) 行中,我们希望传到原型链的下一层,但是 this = longEar,所以 this .__ proto __.eat又是 rabbit.eat

// inside rabbit.eat() we also have this = longEar
this.__proto__.eat.call(this) // (*)
// becomes
longEar.__proto__.eat.call(this)
// or (again)
rabbit.eat.call(this);
登录后复制
  1. ...因此 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 Animal
class 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]

1418177155-5b41c4dcd62c3_articlex[1].png

所以,Rabbit 函数现在继承 Animal 函数。Animal 自带引用到 Function.prototype[[Prototype]](因为它不 extend 其他类)。

看看这里:

class Animal {}
class Rabbit extends Animal {}

// for static propertites and methods
alert(Rabbit.__proto__ === Animal); // true

// and the next step is Function.prototype
alert(Animal.__proto__ === Function.prototype); // true

// that's in addition to the "normal" prototype chain for object methods
alert(Rabbit.prototype.__proto__ === Animal.prototype);
登录后复制

这样 Rabbit 可以访问 Animal 的所有静态方法。

在内置对象中没有静态继承

请注意,内置类没有静态 [[Prototype]] 引用。例如,Object 具有 Object.definePropertyObject.keys等方法,但 ArrayDate 不会继承它们。

DateObject 的结构:

3237107697-5b41c4dcd967d_articlex[1].png

DateObject 之间毫无关联,他们独立存在,不过 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()); // false

let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
登录后复制

请注意一件非常有趣的事情。像 filtermap 和其他内置方法 - 返回新的继承类型的对象。他们依靠 constructor 属性来做到这一点。

在上面的例子中,

arr.constructor === PowerArray
登录后复制

所以当调用 arr.filter() 时,它自动创建新的结果数组,就像 new PowerArray 一样,于是我们可以继续使用 PowerArray 的方法。

我们甚至可以自定义这种行为。如果存在静态 getter Symbol.species,返回新建对象使用的 constructor。

下面的例子中,由于 Symbol.species 的存在,mapfilter等内置方法将返回普通的数组:

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 constructor
let filteredArr = arr.filter(item => item >= 10);


// filteredArr is not PowerArray, but Array

alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
登录后复制

我们可以在其他 key 使用 Symbol.species,可以用于剥离结果值中的无用方法,或是增加其他方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

用Node编写RESTful API接口

async/await 并行请求和错误处理

以上是ES6 Class 继承与 super的介绍的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何使用WebSocket和JavaScript实现在线语音识别系统 如何使用WebSocket和JavaScript实现在线语音识别系统 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术 WebSocket与JavaScript:实现实时监控系统的关键技术 Dec 17, 2023 pm 05:30 PM

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何利用JavaScript和WebSocket实现实时在线点餐系统 如何利用JavaScript和WebSocket实现实时在线点餐系统 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

如何使用WebSocket和JavaScript实现在线预约系统 如何使用WebSocket和JavaScript实现在线预约系统 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

使用jQuery替换元素的class名称 使用jQuery替换元素的class名称 Feb 24, 2024 pm 11:03 PM

jQuery是一种经典的JavaScript库,被广泛应用于网页开发中,它简化了在网页上处理事件、操作DOM元素和执行动画等操作。在使用jQuery时,经常会遇到需要替换元素的class名的情况,本文将介绍一些实用的方法,以及具体的代码示例。1.使用removeClass()和addClass()方法jQuery提供了removeClass()方法用于删除

JavaScript和WebSocket:打造高效的实时天气预报系统 JavaScript和WebSocket:打造高效的实时天气预报系统 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

简易JavaScript教程:获取HTTP状态码的方法 简易JavaScript教程:获取HTTP状态码的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

javascript中如何使用insertBefore javascript中如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用于在DOM树中插入一个新的节点。这个方法需要两个参数:要插入的新节点和参考节点(即新节点将要被插入的位置的节点)。

See all articles