100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 使用ES6 Pt更好JavaScript。 II:深入学习课堂

使用ES6 Pt更好JavaScript。 II:深入学习课堂

时间:2021-05-02 21:16:13

相关推荐

使用ES6 Pt更好JavaScript。 II:深入学习课堂

与旧共进,与新共进 ( Out with the Old, In with the new )

Let's be clear about one thing from the start:

让我们从一开始就清楚一件事:

Under the hood, ES6 classes are not something that is radically new: They mainly provide more convenient syntax to create old-school constructor functions. ~ Axel Rauschmayer

在幕后,ES6类并不是一个全新的东西:它们主要提供更方便的语法来创建老式的构造函数。 〜Axel Rauschmayer

Functionally,classis little more than syntactic sugar over the prototype-based behavior delegation capabilities we've had all along. This article will take a close look at the basic use of ES'sclasskeyword, from the perspective of its relation to prototypes. We'll cover:

从功能上讲,与我们一直以来基于原型的行为委托功能相比,class仅是语法糖。 本文将从ES的class关键字与原型的关系的角度仔细研究其基本用法。 我们将介绍:

Defining and instantiating classes;定义和实例化类; Creating subclasses withextends;用extends创建子类;supercalls from subclasses; and 子类的super调用; 和 Examples of important symbol methods. 重要符号方法的示例。

Along the way, we'll pay special attention to howclassmaps to prototype-based code under the hood.

在此过程中,我们将特别注意class在后台如何映射到基于原型的代码。

Let's take it from the top.

让我们从顶部开始。

Note: This is part 2 of the Better JavaScript series. Be sure to check out part 1:

注意:这是Better JavaScript系列的第2部分。请务必查看第1部分:

Better JavaScript with ES6, Part 1: Popular Features使用ES6更好JavaScript,第1部分:流行功能

退一步:什么都不是 ( A Step Back: What Classes Aren't )

JavaScript's "classes" aren't anything like classes in Java, Python, or . . . Really, any other object-oriented language you're likely to have used. Which, by the way, I'll refer to asclass-oriented languages, as that's more accurate.

JavaScript的“类”不同于Java,Python或中的类。 。 。 确实,您可能会使用其他任何面向对象的语言。 顺便说一下,我将其称为面向类的语言,因为这更准确。

In traditional class-oriented languages, you createclasses, which are templates forobjects. When you want a new object, youinstantiatethe class, which tells the language engine tocopythe methods and properties of the class into a new entity, called aninstance. Theinstanceis your object, and, after instantiation, has absolutely no active relation with the parent class.

在传统的面向类的语言中,您创建类,这些类是对象的模板。 当您需要一个新对象时,您可以实例化该类,该类告诉语言引擎将类的方法和属性复制到一个新的实体(称为instance)中。实例是您的对象,实例化后,与父类绝对没有活动关系。

JavaScript doesnothave such copy mechanics. "Instantiating" aclassin JavaScriptdoescreate a new object, butnotone that is independent of its parent class.

JavaScript没有这样的复制机制。 “实例化”一classJavaScript并创建一个新的对象,而不是一个独立于它的父类的。

Rather, it creates an object that is linked to aprototype. Changes to that prototype propagate to the new object,even afterinstantiation.

而是,它创建一个链接到prototype的对象。即使在实例化之后,对该原型的更改也会传播到新对象。

Prototypes are an immensely powerful design pattern in their own right. There are a number of techniques for using them to emulate something like traditional class mechanics, and it's these techniques thatclassprovides compact syntax for.

原型本身就是一种非常强大的设计模式。 有一些使用它们来模拟像传统类机械技术,以及它的这些技术,class提供简洁的语法。

To summarize:

总结一下:

JavaScriptdoes nothave classes, the way that Java and other languages have classes; andJavaScript没有类,Java和其他语言都没有类。 和 JavaScript'sclassis (mostly) just syntactical sugar for prototypes, which areverydifferent from traditional classes. JavaScript的class(大多数)只是原型的语法糖,与传统类有很大不同。

With that out of the way, let's get our feet wet withclass.

有了这些,就让我们开始class

基类:声明和表达式 ( Base Classes: Declarations & Expressions )

You create classes with theclasskeyword, followed by an identifier, and finally, a code block, called theclass body. These are calledclass declarations. Class declarations that don't use theextendskeyword are calledbase classes:

您可以使用class关键字创建类,其后是标识符,最后是一个称为类body的代码块。 这些称为类声明。 不使用extends关键字的类声明称为基类:

"use strict";// Food is a base classclass Food {constructor (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat = fat;}toString () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`}print () {console.log( this.toString() );}}const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);chicken_breast.print(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'console.log(chicken_breast.protein); // 26 (LINE A)

A few things to note.

需要注意的几件事。

Classes canonlycontain method definitions,notdata properties;类只能包含方法定义,不能包含数据属性。 When defining methods, you use shorthand method definitions;在定义方法时,使用速记方法定义 ; Unlike when creating objects, you donotseparate method definitions in class bodies with commas; and创建对象时不同,你在用逗号分隔的类主体不是单独的方法定义; 和 Youcanrefer to properties on instances of the class directly (Line A).您可以直接在类实例上引用属性(A行)。

A distinctive feature of classes is the function calledconstructor. This is where you initialize your object's properties.

类的一个显着特征是称为构造函数的函数。 在这里您可以初始化对象的属性。

You don'thaveto define a constructor function. If you choose not to, the engine will insert an empty one for you:

您没有定义构造函数。 如果您选择不这样做,引擎将为您插入一个空的:

"use strict";class NoConstructor {/* JavaScript inserts something like this:constructor () { }*/}const nemo = new NoConstructor(); // Works, but pretty boring

Assigning a class to a variable is called aclass expression, and is an alternative to the above syntax:

将类分配给变量称为类表达式,它是上述语法的替代方法:

"use strict";// This is an anonymous class expression -- you can't refer to the it by name within the class body.const Food = class {// Class definition is the same as before. . . }// This is a named class expression -- you /can/ refer to this class by name within the class body . . . const Food = class FoodClass {// Class definition is the same as before . . . // Adding new method, to demonstrate we can refer to FoodClass by name// within the class . . . printMacronutrients () {console.log(`${FoodClass.name} | ${FoodClass.protein} g P :: ${FoodClass.carbs} g C :: ${FoodClass.fat} g F`)}}const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);chicken_breast.printMacronutrients(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'// . . . But /not/ outside of ittry {console.log(FoodClass.protein); // ReferenceError } catch (err) {// pass}

This behavior is analogous to that of anonymous and named function expressions.

此行为类似于匿名函数和命名函数表达式的行为 。

使用扩展创建子类并通过超级调用 ( Creating Subclasses with extends & Calling with super )

Classes created withextendsare calledsubclasses, orderived classes. Using them is straightforward. Building on our Food example:

extends创建的类称为子类派生类。 使用它们很简单。 以我们的食物为例:

"use strict";// FatFreeFood is a derived classclass FatFreeFood extends Food {constructor (name, protein, carbs) {super(name, protein, carbs, 0);}print () {super.print(); console.log(`Would you look at that -- ${this.name} has no fat!`);}}const fat_free_yogurt = new FatFreeFood('Greek Yogurt', 16, 12);fat_free_yogurt.print(); // 'Greek Yogurt | 26g P :: 16g C :: 0g F / Would you look at that -- Greek Yogurt has no fat!'

Everything we discussed above regarding base classe holds true for derived classes, but with a few additional points.

上面我们讨论的有关基类的所有内容都适用于派生类,但有几点要注意。

Subclasses are declared with theclasskeyword, followed by an identifier, and then theextendskeyword, followed by anarbitrary expression. This will generally just be an identifier, but could, in theory, be a function.子类先用class关键字声明,然后是标识符,然后是extends关键字,然后是任意表达式。 通常,这只是一个标识符, 但从理论上讲,可以是一个函数 。 If your derived class needs to refer to the class it extends, it can do so with thesuperkeyword.如果您的派生类需要引用其扩展的类,则可以使用super关键字来实现。 A derived class can't contain an empty constructor. Even if all the constructor does is callsuper(), you'll still have to do so explicitly. It can, however, containnoconstructor.派生类不能包含空的构造函数。 即使构造函数所做的只是调用super(),您仍然必须显式地这样做。 它可以,但是,不包含任何构造函数。 Youmustcallsuperin the constructor of a derived class before you usethis.使用this之前,必须在派生类的构造函数中调用super

In JavaScript, there are precisely two use cases for thesuperkeyword.

在JavaScript中,super关键字恰好有两个用例。

Within subclass constructor calls. If initializing your derived class requires you to use the parent class's constructor, you can callsuper(parentConstructorParams[ )within the subclass constructor, passing along any necessary parameters.在子类内进行构造函数调用。 如果初始化派生类要求您使用父类的构造函数,则可以在子类构造函数内调用super(parentConstructorParams[ ),并传递任何必要的参数。To refer to methods in the superclass. Within normal method definitions, derived classes can refer to methods on the parent class with dot notation:super.methodName.引用超类中的方法。 在常规方法定义中,派生类可以使用点符号super.methodName引用父类上的方法。

OurFatFreeFooddemonstrates both use cases:

我们的FatFreeFood演示了两种用例:

In the constructor, we simply callsuper, passing along0as our quantity of fat.在构造函数中,我们仅调用super,将0传递为我们的脂肪量。 In ourprintmethod, we first callsuper.print, and add additional logic after.在我们的print方法中,我们首先调用super.print,然后添加其他逻辑。

Believe it or not, that wraps up the basic syntactical overview ofclass; this is all you need to start experimenting.

信不信由你,它总结了class的基本语法概述; 这就是您开始实验的全部。

原型:深入研究 ( Prototypes: A Deep Dive )

It's time we turn our attention to howclassmaps to JavaScript's underlying prototype mechanisms. We'll look at:

现在是时候将我们的注意力转移到class如何映射到JavaScript的基础原型机制上了。 我们来看一下:

Creating objects with constructor calls;用构造函数调用创建对象; The nature of prototype linkages;原型链接的性质; Property & method delegation; and属性和方法委托; 和 Emulating classes with prototypes用原型模拟类

使用构造函数调用创建对象 (Creating Objects with Constructor Calls)

Constructors are nothing new. Callinganyfunction with thenewkeyword causes it to return an object -- this is called making aconstructor call, and such functions are generally calledconstructors:

构造函数并不是什么新鲜事物。 使用new关键字调用任何函数都会导致它返回一个对象-这称为进行构造函数调用,而此类函数通常称为构造函数:

"use strict";function Food (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}// Calling Food with 'new' is a "constructor call", and results in its returning an object const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);console.log(chicken_breast.protein) // 26// Failing to call Food with 'new' results in its returning 'undefined'const fish = Food('Halibut', 26, 0, 2);console.log(fish); // 'undefined'

When you call a function withnew, four things happen under the hood:

当您使用new调用函数时,在后台发生了四件事:

A new object gets created (let's call itO);创建了一个新对象(我们称其为O);Ogets linked to another object, called itsprototype;O被链接到另一个对象,称为其原型; The function'sthisvalue is set to refer toO;函数的this值设置为引用O; The function implicitly returnsO.该函数隐式返回O。

It's between steps three and four that the engine executes your function's specific logic.

在第三步和第四步之间,引擎执行您功能的特定逻辑。

Knowing this, we can rewrite ourFoodfunction to workwithoutthenewkeyword:

知道了这一点,我们可以重写Food函数,使其无需使用new关键字即可工作:

"use strict";// Eliminating the need for 'new' -- just for demonstrationfunction Food (name, protein, carbs, fat) {// Step One: Create a new Objectconst obj = {}; // Step Two: Link prototypes -- we'll cover this in greater detail shortlyObject.setPrototypeOf(obj, Food.prototype);// Step Three: Set 'this' to point to our new Object// Since we can't reset `this` inside of a running execution context, //we simulate Step Three by using 'obj' instead of 'this'obj.name = name;obj.protein = protein;obj.carbs = carbs;obj.fat = fat;// Step Four: Return the newly created objectreturn obj;}const fish = Food('Halibut', 26, 0, 2);console.log(fish.protein); // 26

Three of these four steps are straightforward. Creating an object, assigning properties, and writing areturnstatement are unlikely to give most developers any conceptual trouble: It's the prototype weirdness that trips people up.

这四个步骤中的三个很简单。 创建对象,分配属性和编写return语句不太可能给大多数开发人员带来任何概念上的麻烦:正是原型怪异性使人们绊倒了。

摸索原型链 (Grokking the Prototype Chain)

Under normal circumstances, all objects in JavaScript -- including Functions -- are linked to another object, called itsprototype.

通常情况下,JavaScript中的所有对象(包括函数)都链接到另一个对象,称为其原型。

If you request a property on an object that the object doesn't have, JavaScript checks the object's prototype for that property. In other words, if you ask for a property on an object that the object doesn't have, it says: "I don't know. Ask my prototype."

如果您请求对象不具有的属性,则JavaScript会检查该属性的对象原型。 换句话说,如果您要在该对象所不具有的对象上请求属性,它会说:“我不知道。请询问我的原型。”

This process -- referring lookups for nonexistent properties to another object -- is calleddelegation.

这个过程-将不存在的属性的查找引用到另一个对象-被称为委托。

"use strict";// joe has no toString property . . . const joe = {name : 'Joe' },sara = {name : 'Sara' };Object.hasOwnProperty(joe, toString); // falseObject.hasOwnProperty(sara, toString); // false// . . . But we can call it anyway!joe.toString(); // '[object Object]', instead of ReferenceError!sara.toString(); // '[object Object]', instead of ReferenceError!

The output from ourtoStringcalls is utterly useless, but note that this snippet doesn't raise a singleReferenceError! That's because, while neitherjoeorsarahas atoStringproperty,their prototype does.

我们toString调用的输出完全没有用,但是请注意,此代码段不会引发单个ReferenceError! 这是因为,尽管joesara都不具有toString属性,但其原型却具有。

When we look forsara.toString(),sarasays, "I don't have atoStringproperty. Ask my prototype." JavaScript, obligingly, does as told, and asksObject.prototypeifithas atoStringproperty. Since it does, it handsObject.prototype'stoStringback to our program, which executes it.

当我们寻找sara.toString()sara说:“我没有toString属性。请询问我的原型。” JavaScript的,乖乖,确实如告诉,并要求Object.prototype,如果有一个toString属性。Object.prototype,它将Object.prototypetoString还给我们的程序,该程序将执行它。

It doesn't matter thatsaradidn't have the property herself --we just delegated the lookup to the prototype.

sara本身没有该属性并不重要-我们只是将查找委托给了原型。

In other words, we can access non-existent properties on an objectas long as that object's prototypedoeshave those properties. We can take advantage of this by assigning properties and methods to an object's prototype, so that we can use them as if they existed on the object itself.

换句话说,我们可以访问对象上不存在的属性,只要该对象的原型确实具有这些属性即可。 我们可以通过为对象的原型分配属性和方法来利用它,以便我们可以像对待对象本身一样存在它们来使用它们。

Even better, if several objects share the same prototype -- as is the case withjoeandsaraabove -- they canallaccess that prototype's properties, immediately after we assign them,withoutour having to copy those properties or methods to each individual object.

更妙的是,如果几个对象共享相同的原型-这是与本案joesara以上-他们都可以访问该原型的属性,之后我们给他们,不需要我们给这些属性或方法复制到每个单独的对象。

This is what people generally refer to asprototypical/prototypal inheritance-- if my object doesn't have it, but my object's prototype does, my objectinheritsthe property.

这就是人们通常所说的原型/原型继承-如果我的对象没有继承,但是我的对象的原型继承了,那么我的对象就会继承该属性。

In reality, there's no "inheritance" going on, here. In class-oriented languages, inheritance implies behavior iscopiedfrom a parent to a child. In JavaScript, no such copying takes place -- which is, in fact, one of the major benefits of prototypes over classes.

实际上,这里没有“继承”。 在面向类的语言中,继承表示行为是从父级复制到子级的。 在JavaScript中,不会进行此类复制-实际上,这是原型相对于类的主要好处之一。

Here's a quick recap before we see precisely where these prototypes come from:

快速回顾一下,然后我们才能准确了解这些原型的来源:

joeandsaradonot"inherit" atoStringproperty;joesara不“继承”toString属性;joeandsara, as a matter of fact, donot"inherit" fromObject.prototypeat all;joesara,因为事实上,不要从“继承”Object.prototype所有;joeandsaraarelinkedtoObject.prototype;joesara链接Object.prototype; Bothjoeandsaraare linked to thesameObject.prototype.joesara都链接到相同的Object.prototype。 To find the prototype of an object -- let's call itO-- you use:Object.getPrototypeOf(O).要找到对象的原型(我们称其为O),请使用:Object.getPrototypeOf(O)

And, just to hammer it home: Objects do not "inherit from" their prototypes. Theydelegateto them.

而且,只是想敲打它:对象并不“继承”其原型。 他们委托给他们。

Period.

期。

Let's dig deeper.

让我们深入研究。

设置对象的原型 ( Setting an Object's Prototype )

We learned above that (almost) every object (O) has aprototype(P), and that, when you look for a property onOthatOdoesn't have, the JavaScript engine will look for that property onPinstead.

我们从上面了解到,(几乎)每个对象(O)都有一个原型(P),并且当您在O上寻找O没有的属性时,JavaScript引擎将改为在P上寻找该属性。

From here, the questions are:

从这里开始,问题是:

How dofunctionsplay into all of this?函数如何发挥所有作用? Where do these prototypes come from, anyway?这些原型从何而来?

函数命名对象 (A Function Named Object)

Before the JavaScript engine executes a program, it builds an environment to run it in, in which it creates a function, called Object, and an associated object, called Object.prototype.

在JavaScript引擎执行程序之前,它会构建一个运行该程序的环境,在其中创建一个名为Object的函数以及一个名为Object.prototype的关联对象。

In other words,ObjectandObject.prototypealwaysexist, inanyexecuting JavaScript program.

换句话说,ObjectObject.prototype在任何正在执行JavaScript程序中始终存在。

Thefunction,Object, is like any other function. In particular, it's aconstructor-- calling it returns a new object:

函数Object就像任何其他函数一样。 特别是,它是一个构造函数-调用它会返回一个新对象:

"use strict";typeof new Object(); // "object"typeof Object(); // A peculiarity of the Object function is that it does /not/ need to be called with new.

Theobject,Object.prototype, is . . . Well, an object. And, like many objects, it has properties.

对象Object.prototype是。 。 。 好吧,一个对象。 并且,与许多对象一样,它具有属性。

Here's what you need to know aboutObjectandObject.prototype:

这是您需要了解的关于ObjectObject.prototype

Thefunction,Object, has a property, called.prototype, which points to an object (Object.prototype);函数Object具有一个名为.prototype的属性,该属性指向一个对象(Object.prototype)。 Theobject,Object.prototype, has a property, called.constructor, which points to a function (Object).对象Object.prototype具有一个名为.constructor的属性,该属性指向一个函数(Object)。

As it turns out, this general scheme is true forallfunctions in JavaScript. When you create a function --someFunction-- it will have a property,.prototype, that points to an object, calledsomeFunction.prototype.

事实证明,这种通用方案适用于JavaScript中的所有函数。 当您创建一个函数someFunction,它将具有属性.prototype,该属性指向一个名为someFunction.prototype的对象。

Conversely, that object --someFunction.prototype-- will have a property, called.constructor, which pointsbackto the functionsomeFunction.

相反,对象-someFunction.prototype-将有一个属性,称为.constructor,这点回到函数someFunction

"use strict";function foo () {console.log('Foo!'); }console.log(foo.prototype); // Points to an object called 'foo'console.log(foo.prototype.constructor); // Points to the function, 'foo'foo.prototype.constructor(); // Prints 'Foo!' -- just proving that 'foo.prototype.constructor' does, in fact, point to our original function

The major points to keep in mind are these:

需要牢记的要点是:

All functions have a property, called.prototype, which points to an object associated with that function.所有函数都有一个名为.prototype的属性,该属性指向与该函数关联的对象。 All function prototypes have a property, called.constructor, which points back to the function.所有函数原型都有一个名为.constructor的属性,该属性指向该函数。 A function prototype's.constructordoes not necessarily point to the function that created the function prototype . . . Confusingly enough. We'll touch on this in greater detail soon.函数原型的.constructor不一定指向创建函数原型的函数。 。 。 令人困惑。 我们将尽快对此进行详细介绍。

These are the rules for setting afunction'sprototype. With that out of the way, we can cover three rules for setting an object's prototype:

这些是设置函数原型的规则。 这样一来,我们就可以涵盖设置对象原型的三个规则:

The "default" rule;“默认”规则; Setting the prototype implicitly, withnew;用new隐式设置原型; Setting the prototype explicitly, withObject.create.使用Object.create显式设置原型。

默认规则 (The Default Rule)

Consider this snippet:

考虑以下代码段:

"use strict";const foo = {status : 'foobar' };

Refreshingly simple. All we've done is create an object, calledfoo, and give it a property, calledstatus.

令人耳目一新的简单。 我们要做的就是创建一个名为foo的对象,并为其提供一个名为status的属性。

Behind the scenes, however, JavaScript does a little extra work. When we create an object literal, JavaScript sets the object's prototype reference toObject.prototype, and sets its.constructorreference toObject:

但是,在后台,JavaScript做了一些额外的工作。 创建对象文字时,JavaScript将对象的原型引用设置为Object.prototype,并将其.constructor引用设置为Object

"use strict";const foo = {status : 'foobar' };Object.getPrototypeOf(foo) === Object.prototype; // truefoo.constructor === Object; // true

new隐式设置原型 (Setting the Prototype Implicitly withnew)

Let's take another look at our modifiedFoodexample.

让我们再看看修改后的Food示例。

"use strict";function Food (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}

By now, we know that thefunctionFoodwill be associated with anobject, calledFood.prototype.

Food.prototype,我们已经知道函数Food将与一个名为Food.prototype的对象相关联。

When we create an object using thenewkeyword, JavaScript:

当我们使用new关键字JavaScript创建对象时:

Sets the object's prototype reference to the.prototypeproperty of the function you called withnew; and将对象的原型引用设置为您用new调用的函数的.prototype属性。 和 Sets the object's.constructorreference to the function you callednewwith.将对象的.constructor引用设置为调用new的函数。

const tootsie_roll = new Food('Tootsie Roll', 0, 26, 0);Object.getPrototypeOf(tootsie_roll) === Food.prototype; // truetootsie_roll.constructor === Food; // true

This is what lets us do slick stuff like this:

这就是让我们做这样的光滑事情的原因:

"use strict";Food.prototype.cook = function cook () {console.log(`${this.name} is cooking!`);};const dinner = new Food('Lamb Chops', 52, 8, 32);dinner.cook(); // 'Lamb Chops are cooking!'

使用Object.create显式设置原型 (Setting the Prototype Explicitly withObject.create)

Finally, we can set an object's prototype referencemanually, using a utility calledObject.create.

最后,我们可以使用名为Object.create的实用程序手动设置对象的原型引用。

"use strict";const foo = {speak () {console.log('Foo!');}};const bar = Object.create(foo);bar.speak(); // 'Foo!'Object.getPrototypeOf(bar) === foo; // true

Remember the four things that JavaScript does under the hood when you call a function withnew?Object.createdoes all but the third step:

还记得当您使用new调用函数时JavaScript在幕后做的四件事吗? 除了第三步,Object.create执行所有操作:

Create a new object;创建一个新对象; Set its prototype reference; and设置其原型参考; 和 Return the new object.返回新对象。

You can see this yourself if you take a look at the polyfill.

如果您看一下polyfill,就可以自己看到 。

模拟class行为 (EmulatingclassBehavior)

Using prototypes directly, emulating class-oriented behavior required a bit of manual acrobatics.

直接使用原型,模拟面向类的行为需要一些手工技巧。

"use strict";function Food (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}Food.prototype.toString = function () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;};function FatFreeFood (name, protein, carbs) {Food.call(this, name, protein, carbs, 0);}// Setting up "subclass" relationships// =====================// LINE A :: Using Object.create to manually set FatFreeFood's "parent".FatFreeFood.prototype = Object.create(Food.prototype);// LINE B :: Manually (re)setting constructor reference (!)Object.defineProperty(FatFreeFood.constructor, "constructor", {enumerable : false,writeable: true,value : FatFreeFood});

At Line A, we have to setFatFreeFood.prototypeequal to a new object, whose prototype reference is toFood.prototype. If we fail to do this, our "child classes" won't have access to "superclass" methods.

在A行,我们必须将FatFreeFood.prototype设置为一个新对象,该对象的原型参考是Food.prototype。 如果我们不这样做,我们的“子类”将无法访问“超类”方法。

Unfortunately, this results in the rather bizarre behavior thatFatFreeFood.constructoris Function . . . NotFatFreeFood. So, to keep everything sane, we have to manually setFatFreeFood.constructorby hand at Line B.

不幸的是,这导致FatFreeFood.constructor是Function的异常FatFreeFood.constructor。 。 。 不是FatFreeFood。 因此,为使一切正常,我们必须在B行手动设置FatFreeFood.constructor

Sparing developers from the noise and unwieldliness of emulating class behavior with prototypes is one of the motives for theclasskeyword. Itdoesprovide a solution to the most common gotchas of prototype syntax.

使开发人员避免使用原型模仿类行为的杂音和笨拙是class关键字的动机之一。 它确实为最常见的原型语法陷阱提供了解决方案。

Now that we've seen so much of JavaScript's prototype mechanics, it should be easier to appreciate justhowmuch easier it can make things!

既然我们已经了解了JavaScript的许多原型机制,那么应该更容易理解它可以使事情变得多么容易!

仔细研究方法 ( A Closer Look at Methods )

Now that we've seen the essentials of JavaScript's prototype system, we'll wrap up by taking a closer look at three kinds of methods classes support, and a special case of the last sort:

现在,我们已经了解了JavaScript原型系统的基本知识,我们将通过仔细研究三种方法类支持以及最后一种特殊情况来结束本文:

Constructors;构造函数; Static methods; 静态方法; Prototype methods; and原型方法; 和 "Symbol methods", a special case ofprototype methods“符号方法”,原型方法的特例

I didn't come up with these groups -- credit goes to Dr Rauschmayer for identifying them in Exploring ES6.

我没有提出这些小组-劳斯迈尔博士在探索ES6中确认了他们。

类构造器 (Class Constructors)

A class'sconstructorfunction is where you'll focus your initialization logic. Theconstructoris special in a few ways:

类的constructor函数是您将重点放在初始化逻辑上的地方。constructor在几种方面很特殊:

It's the only method of a class from which you can make a superconstructor call;这是类中唯一可以调用超构造函数的方法。 It handles all the dirty work of setting up the prototype chain properly; and 它处理正确设置原型链的所有肮脏工作; 和 It acts as the definition of the class.它充当类的定义。

Point 2 is one of the principle benefits to usingclassin JavaScript. To quote heading 15.2.3.1 of Exploring ES6:

第2点是在JavaScript中使用class的主要优点之一。 引用探索ES6的标题15.2.3.1:

The prototype of a subclass is the superclass.

子类的原型是超类。

As we've seen, setting this up manually is tedious and error-prone. That the language takes care of it all behind the scenes if we useclassis a major boon.

如我们所见,手动设置此操作很繁琐且容易出错。 如果我们使用class,那么语言会在幕后处理所有事情,这是一大福音。

Point 3 is interesting. In JavaScript, a class is just a function -- it's equivalent to theconstructormethod in the class.

第三点很有趣。 在JavaScript中,类只是一个函数-等效于类中的constructor方法。

"use strict";class Food {// Class definition is the same as before . . . }typeof Food; // 'function'

Unlike normal-functions-as-constructors, you can't call a class's constructor without thenewkeyword:

与普通函数作为构造函数不同,如果没有new关键字,则无法调用类的构造函数:

const burrito = Food('Heaven', 100, 100, 25); // TypeError

const burrito = Food('Heaven', 100, 100, 25); // TypeError

. . . Which raises another question: What happens when we call a function-as-constructorwithoutnew?

。 。 。 这就引出了另一个问题:当我们调用没有构造函数的构造函数时会发生什么?

The short answer: It returnsundefined, as does any function without an explicit return. You just have to trust your users will constructor-call your function. This is why the community has adopted the convention of only capitalizing constructor names: It's a reminder to call withnew.

简短的答案:它返回undefined,就像没有显式返回的任何函数一样。 您只需要相信用户将构造函数调用您的函数即可。 这就是社区采用仅大写构造函数名称的约定的原因:提醒您使用new

"use strict";function Food (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}const fish = Food('Halibut', 26, 0, 2); // D'oh . . .console.log(fish); // 'undefined'

The long answer: It returnsundefined,unlessyou manually detect that it wasn't called withnew, and then do something about it yourself.

长答案是:它返回undefined,除非您手动检测到未使用new调用它,然后自己对其进行处理。

ES introdues a property that makes this check trivial:[new.target](/en-US/docs/Web/JavaScript/Reference/Operators/new.target).

ES引入了一个使该检查变得微不足道的属性:[new.target]( /en-US/docs/Web/JavaScript/Reference/Operators/new.target )。

new.targetis a property defined on all functions called withnew, including class constructors. When you call a function with thenewkeyword, the value ofnew.targetwithin the function body is the function itself. If the function wasn't called withnew, its value isundefined.

new.target是在用new调用的所有函数(包括类构造函数)上定义的属性。 当您使用new关键字调用函数时,函数体内的new.target值就是函数本身。 如果未使用new调用该函数,则其值是undefined

"use strict";// Enforcing constructor callfunction Food (name, protein, carbs, fat) {// Manually call 'new' if user forgetsif (!new.target)return new Food(name, protein, carbs, fat); this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}const fish = Food('Halibut', 26, 0, 2); // Oops -- but, no problem!fish; // 'Food {name: "Halibut", protein: 20, carbs: 5, fat: 0}'

This wasn't any worse in ES5:

在ES5中,这并不差:

"use strict";function Food (name, protein, carbs, fat) {if (!(this instanceof Food))return new Food(name, protein, carbs, fat); this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}

The MDN documentation has more details onnew.target, and the spec is the definitive reference for the truly curious. The descriptions of [[Construct]] are particularly illuminating.

MDN文档中有更多有关new.target详细信息,该规范是真正好奇的new.target参考 。 [[Construct]]的说明特别具有启发性。

静态方法 (Static Methods)

Static methodsare methods on the constructor function itself, which arenotavailable on instances of the class. You define them by using thestatickeyword.

静态方法是构造函数本身的方法,在类的实例上不可用。 您可以使用static关键字定义它们。

"use strict";class Food {// Class definition is the same as before . . . // Adding a static methodstatic describe () {console.log('"Food" is a data type for storing macronutrient information.');}}Food.describe(); // '"Food" is a data type for storing macronutrient information.'

Static methods are analogous to attaching properties directly to old-school functions-as-constructors:

静态方法类似于将属性直接附加到老式构造函数上:

"use strict";function Food (name, protein, carbs, fat) {Food.count += 1;this.name = name;this.protein = protein;this.carbs = carbs;this.fat= fat;}Food.count = 0;Food.describe = function count () {console.log(`You've created ${Food.count} food(s).`);};const dummy = new Food();Food.describe(); // "You've created 1 food."

原型方法 (Prototype Methods)

Any method that isn't a constructor or a static method isprototype method. The name comes from the fact that we used to achieve this functionality by attaching functions to the.prototypeof functions-as-constructors:

不是构造函数或静态方法的任何方法都是原型方法。 该名称来自以下事实:我们过去通过将功能附加到.prototype-constructors的.prototype实现此功能:

"use strict";// Using ES6:class Food {constructor (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat = fat;}toString () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`; }print () {console.log( this.toString() ); }}// In ES5:function Food (name, protein, carbs, fat) {this.name = name;this.protein = protein;this.carbs = carbs;this.fat = fat;}// The name "prototype methods" presumably comes from the fact that we // used to attach such methods to the '.prototype' old-school functions-as-constructors.Food.prototype.toString = function toString () {return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`; };Food.prototype.print = function print () {console.log( this.toString() ); };

To be clear, it's perfectly fine to use generators in method definitions, as well:

需要明确的是,在方法定义中使用生成器也很好:

"use strict";class Range {constructor(from, to) {this.from = from;this.to = to;}* generate () {let counter = this.from,to= this.to;while (counter < to) {if (counter == to)return counter++;elseyield counter++;}}}const range = new Range(0, 3);const gen = range.generate();for (let val of range.generate()) {console.log(`Generator value is: ${val }. `);// Prints:// Generator value is: 0.// Generator value is: 1.// Generator value is: 2.}

符号方法 (Symbol Methods)

Finally, there are thesymbol methods. These are functions whose names areSymbolvalues, and which the JavaScript engine recognizes and uses when you use certain built-in constructs with your custom objects.

最后,还有符号方法。 这些函数的名称为Symbol值,当您将某些内置构造与自定义对象一起使用时,JavaScript引擎可以识别和使用这些函数。

The MDN docs provide a succinct overview of what Symbols are in general:

MDN文档简要概述了哪些符号:

A symbol is a unique and immutable data type and may be used as an identifier for object properties.

符号是唯一且不可变的数据类型,可以用作对象属性的标识符。

Creating a new symbol provides you with a value that is guaranteed to be unique within your program. This is what makes it useful for naming object properties: You're guaranteed never to accidentally shadow anything. Symbol-valued keys also aren't innumerable, so they're largely invisible to the outside world (but not completely).

创建新符号将为您提供一个值,该值在程序中将保证是唯一的。 这就是使它对命名对象属性有用的原因:保证永远不会意外阴影任何东西。 符号值键也不是无数的,因此它们在外界几乎看不见( 但不是完全 )。

"use strict";const secureObject = {// This key is guaranteed to be unique.[new Symbol("name")] : 'Dr. Secure A. F.'};console.log( Object.getKeys(superSecureObject) ); // [] -- The symbol property is pretty hard to get at . . . console.log( Reflect.ownKeys(secureObject) ); // [Symbol("name")] -- . . . But, not /truly/ hidden.

More interestingly for us, they give us a way to tell the JavaScript engine to use certain functions for special purposes.

对于我们而言,更有趣的是,它们为我们提供了一种方法来告诉JavaScript引擎将某些函数用于特殊目的。

The so-called Well-known Symbols are special object keys that identify functions the JavaScript engine invokes when you use certain built-in constructs with custom objects.

所谓的“众所周知的符号”是特殊的对象键,用于标识将某些内置构造与自定义对象一起使用时JavaScript引擎调用的功能。

This is a bit exotic for JavaScript, so let's see an example:

这对于JavaScript来说有点奇特,所以让我们看一个例子:

"use strict";// Extending Array lets us use 'length' in an intuitive way,// and also gives us access to built-in array methods, like // map, filter, reduce, push, pop, etc.class FoodSet extends Array {// ...foods collects arbitrary number of arguments into an array// /en-US/docs/Web/JavaScript/Reference/Operators/Spread_operatorconstructor(...foods) {super();this.foods = [];foods.forEach((food) => this.foods.push(food))}// Custom iterator behavior. This isn't very *useful* iterator behavior, mind you, but it's a fine example.// The asterisk *must* precede the name of the key.* [Symbol.iterator] () {let position = 0;while (position < this.foods.length) {if (position === this.foods.length) {return "Done!"} else {yield `${this.foods[ position++ ]} is the food item at position ${position}`;}}}// Return an object of type Array, rather than type FoodSet, when users// use built-in array methods. This makes our FoodSet interoperable// with code expecting an array.static get [Symbol.species] () {return Array;}}const foodset = new FoodSet(new Food('Fish', 26, 0, 16), new Food('Hamburger', 26, 48, 24));// When you use for . . . of with a FoodSet, JavaScript will iterate using the function you // assoiated with the key [Symbol.iterator].for (let food of foodset) {// Prints all of our foodsconsole.log( food );}// JavaScript creates and returns a new object when you `filter` on an array, // which it creates using the default constructor of the object you execute `filter` on.//// Since most code would expect filter to return an Array, we can tell JavaScript// to use the Array constructor when implicitly creating a new instance by // overriding [Symbol.species].const healthy_foods = foodset.filter((food) => food.name !== 'Hamburger');console.log( healthy_foods instanceof FoodSet ); // console.log( healthy_foods instanceof Array );

When you use afor...ofloop on an object, JavaScript will try to execute the object'siteratorfunction, which is the function associated with the key,Symbol.iterator. If you provide your own definition, JavaScript will use that. If you don't, it'll use the default implementation, if there is one; or do nothing, if there isn't.

当在对象上使用for...of循环时,JavaScript将尝试执行对象的迭代器函数,该函数是与键Symbol.iterator关联的函数。 如果您提供自己的定义,JavaScript将使用它。 如果没有,它将使用默认实现(如果有);否则,它将使用默认实现。 或什么都不做,如果没有。

Symbol.speciesis a bit more exotic. In a custom class, the defaultSymbol.speciesfunction is the constructor for your class. When you subclass built-in collections, likeArrayorSet, however, you'll often want to be able to use your subclass wherever you could use an instance of the parent class.

Symbol.species更具异国情调。 在自定义类中,默认的Symbol.species函数是您的类的构造函数。 但是,当对内置集合(例如ArraySet子类化时,您经常希望能够在可以使用父类实例的任何地方使用子类。

Returning instances of the parent class from methods on the classinsteadof instances of the derived class gets us closer to ensuring full interoperability of a subclass with more general code. This isSymbol.speciesallows.

从类上的方法而不是派生类的实例返回父类的实例使我们更接近确保子类与更通用的代码的完全互操作性。 这是Symbol.species允许的。

Don't sweat it if this bit doesn't make much sense. Using symbols this way -- or at all -- is still a niche case, and the point of these examples is to demonstrate:

如果这没有太大意义,请不要流汗。 以这种方式(或根本没有)使用符号仍然是一个小众案例,这些示例的目的是演示:

That youcanuse certain built-in JavaScript constructs with custom classes; and您可以将某些内置JavaScript构造与自定义类一起使用; 和Howyou achieve that, in two common cases.在两种常见情况下,您如何实现这一目标。

结论 ( Conclusion )

ES'sclasskeyword doesnotbring us "true classes", a là Java or SmallTalk. Rather, it simply provides a more convenient syntax for creating objects related via prototype linkage. Under the hood, there's nothing new here.

ES的class关键字不会给我们带来“真正的阶级”,点菜Java或Smalltalk的。 相反,它只是为通过原型链接创建相关对象提供了更方便的语法。 在引擎盖下,这里没有新内容。

I covered enough of JavaScript's prototype mechanism for our discussion, but there's quite a bit more to say. Read Kyle Simpson's this & Object Prototypes for fuller coverage on that front. Appendix A is particularly relevant.

在我们的讨论中,我已经介绍了足够多JavaScript原型机制,但是还有很多话要说。 阅读凯尔·辛普森(Kyle Simpson)的this和Object Prototypes,以更全面地了解这一方面。 附录A特别相关。

For the nitty-gritty on ES classes, Dr Rauschmayer's Exploring ES6: Classes should be your go-to resource. It was the inspiration for much of what I've written here.

对于ES课程的实质性内容,Rauschmayer博士的《 探索ES6:课程》应该是您的首选资源。 这是我在这里写过很多东西的灵感。

Finally, if you've got questions, drop a line in the comments, or hit me on Twitter. I'll do my best to get back to everyone directly.

最后,如果您有任何疑问,请在评论中添加一行,或者在Twitter上打我 。 我会尽力直接与大家联系。

What do you think aboutclass? Love it, hate it, no strong feelings? It seems like everyone's got an opinion -- let us know yours below!

您如何看待class? 喜欢它,讨厌它,没有强烈的感情吗? 似乎每个人都有意见-请在下面告诉我们您的意见!

Note: This is part 2 of the Better JavaScript series. You can see parts 1, 2, and 3 here:

注意:这是Better JavaScript系列的第2部分。您可以在此处看到第1、2和3部分:

Better JavaScript with ES6, Part 1: Popular Features使用ES6更好JavaScript,第1部分:流行功能 Better JavaScript with ES6, Part 2: A Deep Dive into Classes使用ES6更好JavaScript,第2部分:深入研究类 Better JavaScript with ES6, Part 3: Cool Collections & Slicker Strings使用ES6更好JavaScript,第3部分:酷集合和闪烁字符串

翻译自: https://scotch.io/tutorials/better-javascript-with-es6-pt-ii-a-deep-dive-into-classes

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。