介绍 ( Introduction )
With the ES2015 spec finalized and Node.js shipping with a substantial subset of its functionailty, it's safe to say it: The Future is Upon Us.
随着ES2015规范的最终确定以及Node.js附带的大部分功能失效,可以肯定地说:“未来就在我们身上”。
. . . I've always wanted to say that.
。 。 。 我一直想这么说。
But, it's true. The V8 Engine is swiftly approaching spec-compliance, and Node ships with a good selection of ES2015 features ready for production. It's this latter list of features that I consider the Essentials™, as it represents the set of feature we can use without a transpiler like Babel or Traceur.
但是,这是真的。 V8引擎正在Swift达到规范要求 , Node附带了可供选择的大量ES2015功能供生产使用 。 我考虑的是Essentials™的最新功能列表,因为它代表了无需Babel或Traceur这样的编译器就可以使用的功能集。
This article will cover three of the more popular ES2015 features available in Node:
本文将介绍Node中可用的三种最受欢迎的ES2015功能:
- Block scoping with
let
andconst
; 用let
和const
范围界定; - Arrow functions; and 箭头功能; 和
- Shorthand properties & methods. 速记属性和方法。
Let's get to it.
让我们开始吧。
用let和const阻止作用域 ( Block Scope with let and const )
Scope refers to where in your program your variables are visible. In other words, it's the set of rules that determines where you're allowed to use the variables you've declared.
范围是指变量在程序中的可见位置。 换句话说,这是一组规则,用于确定允许在何处使用已声明的变量。
We've mostly all heard the claim that JavaScript only creates new scopes inside of functions. While a good 98% of the useful scopes you've created were, in fact, function scopes, there are actually three ways to create a new scope in JavaScript. You can:
我们几乎都听到过这样的说法,即JavaScript仅在函数内部创建新范围。 实际上,虽然您创建的有用范围中有98%是函数范围,但实际上有三种方法可以在JavaScript中创建新范围。 您可以:
- Create a function. You probably know this already. 创建一个函数 。 您可能已经知道了。
- Create a
catch
block. I'm not kidding. 创建一个catch
块 。 我不是在开玩笑 。 - Create a code block. If you're writing ES2015. declaring variables with
let
orconst
within a code block restricts their visibility to that block only*. This is called *block scoping. 创建一个代码块 。 如果您正在编写ES2015。 在代码块中用let
或const
声明变量将其可见性限制为仅该块* 。 这称为* block scoping 。
A block is just a section of code wrapped in curly braces. { like this }
. They appear naturally around if
/else
statements and try
/catch
/finally
blocks. You can also wrap arbitrary sections of code in braces to create a code block, if you want to take advantage of block-scoping.
块只是用大括号括起来的一段代码。 { like this }
。 它们自然出现在if
/ else
语句周围if
并try
/ catch
/ finally
块。 如果要利用块作用域,还可以将任意代码段括在大括号中以创建代码块。
Consider this snippet.
考虑一下此片段。
// You have to use strict to try this in Node
"use strict";
var foo = "foo";
function baz() {
if (foo) {
var bar = "bar";
let foobar = foo + bar;
}
// Both foo and bar are visible here
console.log("This situation is " + foo + bar + ". I'm going home.");
try {
console.log("This log statement is " + foobar + "! It threw a ReferenceError at me!");
} catch (err) {
console.log("You got a " + err + "; no dice.");
}
try {
console.log("Just to prove to you that " + err + " doesn't exit outside of the above `catch` block.");
} catch (err) {
console.log("Told you so.");
}
}
baz();
try {
console.log(invisible);
} catch (err) {
console.log("invisible hasn't been declared, yet, so we get a " + err);
}
let invisible = "You can't see me, yet"; // let-declared variables are inaccessible before declaration
A few things to note.
需要注意的几件事。
- Notice that
foobar
isn't visible outside of theif
block, because we declared it withlet
; 注意,foobar
在if
块之外不可见,因为我们用let
声明了它; - We can use
foo
anywhere, because we defined it as avar
in the global scope; and 我们可以在任何地方使用foo
,因为我们在全局范围内将其定义为var
。 和 - We can use
bar
anywhere inside ofbaz
, becausevar
-declared variables are accessible throughout the entirety of the scope they're defined. 我们可以在baz
内的任何地方使用bar
,因为var
声明的变量在定义的整个作用域中都是可访问的。 - We can't use
let
orconst
-declared variables before we've defined them. In other words, they're not hoisted by the compiler, asvar
-declarations are. 在定义变量之前,不能使用let
或const
声明的变量。 换句话说,它们不会像var
-declaration那样被编译器挂起。
The const
keyword behaves similarly to let
, with two differences.
const
关键字的行为与let
相似,但有两个区别。
- You must assign a value to a const-declared variable when you create it. You can't create it first and assign it later. 创建变量时, 必须为const声明的变量分配一个值。 您不能先创建它,然后再分配它。
- You cannot change the vaue of a
const
-declared variable after you create it. If you try, you'll get aTypeError
. 创建变量后,您无法更改const
声明的变量的值。 如果尝试,将得到TypeError
。
let
& const
:谁在乎? (let
& const
: Who Cares?)
Since we've gotten by just fine with var
for a good twenty years, now, you might be wondering if we really need new variables.
既然我们在var
相处已经二十年了,所以现在,您可能想知道我们是否真的需要新的变量。
Good question. The short answer -- no. Not really. But there are a few good reasons to use let
and const
where possible.
好问题。 简短的答案-不。 不是真的 。 但是有一些充分的理由在可能的地方使用let
和const
。
- Neither
let
norconst
-declared variables are hoisted to the top of their scopes, which can make for more readable, less confusing code.let
和const
声明的变量都不会放在其作用域的顶部,这可以使代码更易读,更易混淆。 - They limit your variables' visibility as much as possible, which helps prevent confusing namespace collisions. 它们尽可能地限制了变量的可见性,这有助于防止混淆名称空间冲突。
- It's easier to reason about programs that reassign variables only when absolutely necesary.
const
helps enforce immutable variable references. 仅在绝对必要时才需要对重新分配变量的程序进行推理。const
帮助实施不可变的变量引用。
Another use case is that of let
in for
loops.
另一个用例是的是let
中for
循环。
"use strict";
var languages = ['Danish', 'Norwegian', 'Swedish'];
// Pollutes global namespace. Ew!
for (var i = 0; i < languages.length; i += 1) {
console.log(`${languages[i]} is a Scandinavian language.`);
}
console.log(i); // 4
for (let j = 0; j < languages.length; j += 1) {
console.log(`${languages[j]} is a Scandinavian language.`);
}
try {
console.log(j); // Reference error
} catch (err) {
console.log(`You got a ${err}; no dice.`);
}
Using var
to declare the counter in a for
loop doesn't actually keep the counter local to the loop. Using let
instead does.
使用var
在for
循环中声明计数器实际上并没有使计数器在循环本地。 使用let
代替。
let
also has the major advantage of rebinding the loop variable on every iteration, so each loop gets its own copy, rather than sharing the globally-scoped variable.
let
还具有在每次迭代时重新绑定循环变量的主要优点,因此每个循环都有自己的副本,而不是共享全局范围的变量。
"use strict";
// Simple & Clean
for (let i = 1; i < 6; i += 1) {
setTimeout(function() {
console.log("I've waited " + i + " seconds!");
}, 1000 * i);
}
// Totally dysfunctional
for (var j = 0; j < 6; j += 1) {
setTimeout(function() {
console.log("I've waited " + j + " seconds for this!");
}, 1000 * j);
}
The first loop does what you think it does. The bottom one prints "I've waited 6 seconds!", every second.
第一个循环按照您的想法进行。 底部每秒钟打印一次“我已经等了6秒!”。
Pick your poison.
选择你的毒药。
动态的怪癖 ( The Quirks of Dynamic this )
JavaScript's this
keyword is notorious for doing basically everything except for you want it to.
JavaScript的this
关键字臭名昭著,除了您希望执行的所有操作外,它几乎可以执行所有操作。
The truth is, the rules are really quite simple. Regardless, there are situations where this
can encourage awkward idioms.
事实是, 规则确实非常简单 。 无论如何,在某些情况下this
会鼓励笨拙的成语。
"use strict";
const polyglot = {
name : "Michel Thomas",
languages : ["Spanish", "French", "Italian", "German", "Polish"],
introduce : function () {
// this.name is "Michel Thomas"
const self = this;
this.languages.forEach(function(language) {
// this.name is undefined, so we have to use our saved "self" variable
console.log("My name is " + self.name + ", and I speak " + language + ".");
});
}
}
polyglot.introduce();
Inside of introduce
, this.name
is undefined
. Right outside of the callback, in our forEach
loop, it refers to the polyglot
object. Often, what we want in cases like this is for this
within our inner function to refer to the same object that this
refers to in the outer function.
在introduce
, this.name
是undefined
。 在回调的外部,在我们的forEach
循环中,它指向polyglot
对象。 通常情况下,我们希望在这样的情况下,究竟是什么this
我们内部函数内引用同一个对象, this
是指在外部函数。
The problem is that functions in JavaScript always define their own this
values upon invocation, according to a well-established set of four rules. This mechanim is known as dynamic this
.
问题在于,JavaScript中的函数总是根据一套完善的四个规则来定义自己的this
值。 这mechanim称为动态this
。
Not a single one of these rules involves looking up what this
means "nearby"; there is no conceivable way for the JavaScript engine to define this
based on its meaning within a surrounding scope.
没有这些规则中的单一一个涉及查找什么this
意思是“附近”; JavaScript引擎无法根据其含义在周围范围内定义this
方法。
This all means that, when the engine looks up the value of this
, it will find one, but it will not be the same as the value outside of the callback. There are two traditional workarounds to the problem.
所有这些都意味着,当引擎查找的值this
,它会找到一个,但它不会是相同的回调的价值之外。 有两种传统的解决方法。
- Save
this
in the outer function to a variable, usually calledself
, and use that within the inner function; or 保存this
在外部函数的变量,通常被称为self
,和使用该内部函数内; 要么 - Call
bind
on the inner function to permanently set itsthis
value. 在内部函数上调用bind
以永久设置其this
值。
These methods work, but they can be noisy.
这些方法有效,但可能会产生干扰。
If, on the other hand, inner functions did not set their own this
values, JavaScript would look up the value of this
just as it would look up the value of any other variable: By stepping through parent scopes until it finds one with the same name. That would let us use the value of this
from "nearby" source code, and is known as lexical this
.
如果,另一方面,内部功能没有设置自己的this
值,JavaScript的将查找的价值this
只是因为它会查找任何其他变量的值:由通过家长加强作用域,直到它找到一个具有相同名称。 这将使我们能够使用“附近”源代码中this
的值,并且被称为词汇this
。
Quite a bit of code would be quite a bit cleaner if we had such a feature, don't you think?
如果具有这样的功能,相当多的代码会更简洁一些,您是不是认为呢?
词汇this
与Arrow功能 (Lexical this
with Arrow Functions)
With ES2015, we do. Arrow functions do not bind a this
value, allowing us to take advantage of lexical binding of the this
keyword. We can refactor the broken code from above like this:
使用ES2015,我们可以做到。 箭头函数不绑定this
值,这使我们可以利用this
关键字的词法绑定。 我们可以像这样从上面重构损坏的代码:
"use strict";
let polyglot = {
name : "Michel Thomas",
languages : ["Spanish", "French", "Italian", "German", "Polish"],
introduce : function () {
this.languages.forEach((language) => {
console.log("My name is " + this.name + ", and I speak " + language + ".");
});
}
}
. . . And all would work as expected.
。 。 。 一切都会按预期进行。
Arrow functions have a few types of syntax.
箭头函数具有几种语法。
"use strict";
let languages = ["Spanish", "French", "Italian", "German", "Polish"];
// In a multiline arrow function, you must use curly braces,
// and you must include an explicit return statement.
let languages_lower = languages.map((language) => {
return language.toLowerCase()
});
// In a single-line arrow function, curly braces are optional,
// and the function implicitly returns the value of the last expression.
// You can include a return statement if you'd like, but it's optional.
let languages_lower = languages.map((language) => language.toLowerCase());
// If your arrow function only takes one argument, you don't need to wrap it in
// parentheses.
let languages_lower = languages.map(language => language.toLowerCase());
// If your function takes multiple arguments, you must wrap them in parentheses.
let languages_lower = languages.map((language, unused_param) => language.toLowerCase());
console.log(languages_lower); // ["spanish", "french", "italian", "german", "polish"]
// Finally, if your function takes no arguments, you must include empty parentheses before the arrow.
(() => alert("Hello!"))();
The MDN docs on arrow functions are great for reference.
关于箭头功能的MDN文档非常适合参考。
速记属性和方法 ( Shorthand Properties & Methods )
ES2015 also gives us a few new ways to define properties and methods on objects.
ES2015还为我们提供了一些新的方法来定义对象的属性和方法。
速记方法 (Shorthand Methods)
In JavaScript, a method is a property on an object that has a function value:
在JavaScript中, 方法是具有函数值的对象的属性:
"use strict";
// Kudos to @_finico for catching a type in the first draft.
const myObject = {
foo : function () {
console.log('bar');
},
}
In ES2015, we can simply write:
在ES2015中,我们可以简单地编写:
"use strict";
const myObject = {
foo () {
console.log('bar');
},
* range (from, to) {
while (from < to) {
if (from === to)
return ++from;
else
yield from ++;
}
}
}
Note that you can use generators to define methods, too. All you need to do is prepend the function's name with an asterisk (*).
请注意,您也可以使用生成器来定义方法。 您需要做的就是在函数名称的前面加上星号(*)。
These are called method definitions. They're similar to traditional functions-as-properties, but have a few key differences:
这些称为方法定义 。 它们类似于传统的函数即属性,但有一些主要区别:
- You can only call
super
from a method definition; 您只能从方法定义中调用super
; - You are not allowed to call a method definition with
new
. 你不允许调用一个方法定义new
。
I'll cover classes and the super
keyword in a later article. If you just can't wait, Exploring ES6 has all the goodies.
我将在以后的文章中介绍类和super
关键字。 如果您迫不及待,请探索Exploring ES6 。
速记和计算属性 (Shorthand & Computed Properties)
ES6 also introduces shorthand and computed properties.
ES6还引入了速记和计算属性 。
If the name of your object's keys are identical to the variables naming their values, you can initialize your object literal with just the variable names, rather than defining it as a redundant key-value pair.
如果对象键的名称与命名其值的变量相同,则可以仅使用变量名来初始化对象文字,而不必将其定义为冗余键值对。
"use strict";
const foo = 'foo';
const bar = 'bar';
// Old syntax
const myObject = {
foo : foo,
bar : bar
};
// New syntax
const myObject = { foo, bar }
Both syntaxes create an object with foo
and bar
keys that refer to the values of the foo
and bar
variables. The latter approach is semantically identical; it's just syntactically sweeter.
两种语法都使用指向foo
和bar
变量值的foo
和bar
键创建对象。 后一种方法在语义上是相同的。 在语法上更甜蜜。
I often take advantage of shorthand properties to write succinct definitions of public APIs when using the revealing module pattern.
在使用显示模块模式时,我经常利用速记属性来编写公共API的简洁定义。
"use strict";
function Module () {
function foo () {
return 'foo';
}
function bar () {
return 'bar';
}
// Write this:
const publicAPI = { foo, bar }
/* Not this:
const publicAPI = {
foo : foo,
bar : bar
} */
return publicAPI;
};
Here, we create and return a publicAPI
object, whose key foo
refers to the foo
method, and whose key bar
refers to the bar
method.
在这里,我们创建并返回一个publicAPI
对象,其键foo
指的是foo
方法,其键bar
指的是bar
方法。
计算的属性名称 (Computed Property Names)
This is a bit of a niche case, but ES6 also allows you to use expressions as property names.
这是一个有点小生情况,但也ES6允许您使用表达式作为属性名称。
"use strict";
const myObj = {
// Set property name equal to return value of foo function
[foo ()] () {
return 'foo';
}
};
function foo () {
return 'foo';
}
console.log(myObj.foo() ); // 'foo'
According to Dr. Raushmayer in Exploring ES6, the main use case for this feature is in setting property names equal to Symbol values.
根据Raushmayer博士在“ 探索ES6”中的介绍 ,此功能的主要用例是将属性名称设置为等于Symbol值。
获取和设置方法 (Getter & Setter Methods)
Finally, I'd like to remind you of the get
and set
methods, which have been around since ES5.
最后,我想提醒您一下get
和set
方法,这些方法自ES5以来就存在。
"use strict";
// Example adapted from MDN's page on getters
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
const speakingObj = {
// Track how many times "speak" has been called
words : [],
speak (word) {
this.words.push(word);
console.log('speakingObj says ' + word + '!');
},
get called () {
// Returns latest word
const words = this.words;
if (!words.length)
return 'speakingObj hasn\'t spoken, yet.';
else
return words[words.length - 1];
}
};
console.log(speakingObj.called); // 'speakingObj hasn't spoken, yet.'
speakingObj.speak('blargh'); // 'speakingObj says blargh!'
console.log(speakingObj.called); // 'blargh'
There are a few things to keep in mind when using getters:
使用getter时要牢记以下几点:
- Getters can't take arguments; 吸气剂不能争论。
- You can't have properties with the same names as your getter functions; 您不能拥有与getter函数同名的属性;
- You can create a getter dynamically by using
Object.defineProperty(OBJECT, "property name", { get : function () { . . . } })
您可以使用Object.defineProperty(OBJECT, "property name", { get : function () { . . . } })
动态创建吸气剂
As an example of this last point, we could have defined the above getter this way:
作为最后一点的一个例子,我们可以这样定义上面的getter:
"use strict";
const speakingObj = {
// Track how many times "speak" has been called
words : [],
speak (word) {
this.words.push(word);
console.log('speakingObj says ' + word + '!');
}
};
// This is just to prove a point. I definitely wouldn't write it this way.
function called () {
// Returns latest word
const words = this.words;
if (!words.length)
return 'speakingObj hasn\'t spoken, yet.';
else
return words[words.length - 1];
};
Object.defineProperty(speakingObj, "called", get : getCalled )
In addition to getters, we have setters. Unsurprsingly, they set properties on an object with custom logic.
除了getter,我们还有setter。 毫不奇怪,它们使用自定义逻辑在对象上设置属性。
"use strict";
// Create a new globetrotter!
const globetrotter = {
// Language spoken in the country our globetrotter is currently in
const current_lang = undefined,
// Number of countries our globetrotter has travelled to
let countries = 0,
// See how many countries we've travelled to
get countryCount () {
return this.countries;
},
// Reset current language whenever our globe trotter flies somewhere new
set languages (language) {
// Increment number of coutnries our globetrotter has travelled to
countries += 1;
// Reset current language
this.current_lang = language;
};
};
globetrotter.language = 'Japanese';
globetrotter.countryCount; // 1
globetrotter.language = 'Spanish';
globetrotter.countryCount; // 2
Everything we said about getters above applies to setters as well, with one difference:
我们上面所说的关于getter的所有内容也适用于setter,但有一个区别:
- Unlike getters, which can take no arguments, setters must take exactly one argument 与不带参数的getter不同,setter 必须带一个参数
Breaking either of these rules throws an error.
违反这些规则之一将引发错误。
Now that Angular 2 is bringing TypeScript and the class
keyword to the fore, I expect get
and set
to spike in popularity. . . But I kind of hope they don't.
既然Angular 2将TypeScript和class
关键字放在了首位,我希望get
和set
会大受欢迎。 。 。 但是我希望他们不会。
结论 ( Conclusion )
Tomorrow's JavaScript is happening today, and it's high time to get a grip on what it has to offer. In this article, we've looked at three of the more popular features from ES2015:
明天JavaScript如今正在发生,现在是时候掌握它提供的功能了。 在本文中,我们研究了ES2015最受欢迎的三个功能:
- Block scoping with
let
andconst
; 用let
和const
范围界定; - Lexical scoping of
this
with arrow functions; 的词法作用域this
带箭头的功能; - Shorthand object properties and methods, plus a review of getter and setter functions. 速记对象属性和方法,以及对getter和setter函数的回顾。
For detailed thoughts on let
, const
, and the notion of block scoping, read Kyle Simpson's take on block scoping. If all you need is a quick practical reference, check the MDN pages for let
and const
.
有关let
, const
以及块作用域概念的详细想法,请阅读Kyle Simpson的块作用域概念 。 如果您需要的只是快速的实用参考,请查看MDN页面的let
和const
。
Dr Rauschmayer has a wonderful article on arrow functions and lexical this
. It's great reading if you want a bit more detail than I had room to cover here.
Rauschmayer博士撰写了一篇关于箭头函数的精彩文章,并用词法this
词汇化 。 如果您想要的细节比我在这里可以讨论的更多,这是一本很棒的书。
Finally, for an exhaustive take on all of what we've talked about here -- and a great deal more -- Dr Rauschmayer's book, Exploring ES6, is the best all-in-one reference the web has to offer.
最后,对于我们在这里讨论的所有内容(包括更多内容)进行详尽的研究,Rauschmayer博士的书《 Exploring ES6 》是网络必须提供的最好的多合一参考。
What ES2015 feature are you most excited about? Is there anything you'd like to see covered in a future article? Let me know in the comments below, or hit me on Twitter (@PelekeS) -- I'll do my best to get back to everyone individually.
您最兴奋的ES2015功能是什么? 您想在以后的文章中看到什么吗? 在下面的评论中让我知道,或者在Twitter( @PelekeS )上打我-我会尽力与每个人联系。
Note: This is part 1 of the Better JavaScript series. You can see parts 2 and 3 here:
注意:这是Better JavaScript系列的第1部分。 您可以在此处看到第2部分和第3部分:
- 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部分:酷集合和闪烁字符串