以下是由 Ryan Christiani 撰写的客座文章。Ryan 是 HackerYou 的老师,并且一直在制作一个名为 让我们学习 ES6 的视频系列。他提出将其中一些内容整理成教程格式,我认为这非常完美,因为我们之前在 CSS-Tricks 上没有太多关于 ES2015 的内容。
名称的含义
截至 2015 年 6 月,JavaScript 语言中最大的新增内容已完成。其官方名称为 ECMAScript 2015,有时也称为“ES6”,或者现在更常称为“ES2105”。它是多年工作和功能的结晶。
展望未来,将会有 ECMAScript 2016,它可能会被称为“ES7”或“ES2016”。计划是每年发布增量版本。
大多数浏览器已开始实现 ES2015 功能,但不同浏览器之间的支持程度各不相同。您可以使用此 表格 查看此实现的当前浏览器兼容性。
像 Babel 这样的工具允许我们今天编写新的 ES2015 代码,并执行称为转译(类似于预处理)的任务,将代码转换为具有更广泛浏览器支持的早期版本的 JavaScript。这类似于 Sass 的工作方式;最初使用 Sass 语法编写代码,然后预处理器编译为标准 CSS。
概述
在本文中,我们将了解一些现在可供开发人员使用的功能。
我们将了解新的关键字,如 let
和 const
,如何创建模板字面量以简化连接,新的箭头函数语法、扩展运算符和剩余参数!以下是目录
这些新增功能可以帮助我们更愉快地编写 JavaScript!
let
和 const
let
和 const
是 ES2015 中提供的两个新关键字。它们用于声明变量,但是这些变量共享一个关键特性,这使它们有别于 var
:**它们创建块级作用域变量。**
当您使用 var
关键字创建变量时,它是函数作用域的,并且仅在该函数中是局部的。这意味着它在创建它的函数以及该函数内部的任何嵌套函数中都是可用的。但它在外部**不可用**。如果您使用 var
在任何函数之外定义变量,它将是全局可用的。
我们将在函数作用域变量中遇到的一个常见问题是 for
循环。
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // Will print out 10;
通常会在 for
循环内声明一个变量,目的是将其绑定到该 for
循环,但这并非如此。如果您运行上述代码,您将看到 i
变量在 for
循环之外可用。
如果您想使用 let
或 const
,则必须首先为 JavaScript 文件启用严格模式。通过在文档顶部添加 'use strict'
,您可以启用 JavaScript 的受限变体。
'use strict';
严格模式是一种选择加入 JavaScript 版本的方式,该版本修复了语言中的一些错误,并将它们转换为错误。它还禁止将来可能会定义的语法!例如,在严格模式下,您不能使用 let
作为变量名。有关严格模式的更多信息,请查看 MDN 上的相关页面。
(**编辑注**:如果我们使用 Babel,则不必担心“use strict”,因为它会自动将其添加到我们的代码中,但了解这一点肯定是有价值的。)
JavaScript 中的“块”是指 { }
之间的所有内容。因此,当我们谈论块作用域时,这意味着在花括号中定义的任何变量都只存在于该块中!
var
是函数作用域的,因此在使用 var
的块内创建变量也将使其在块外部可用。
{
var user = "Ryan";
}
console.log(user); // Ryan
当您使用 let
关键字定义变量时,它将仅在 { }
或块内创建一个新变量。
{
let user = "Ryan";
}
console.log(user); // Uncaught ReferenceError: user is not defined
这将定义并绑定一个变量到其所在的块!如果我们再次查看 for
循环示例,并将 var
替换为 let
for (let i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // Uncaught ReferenceError: i is not defined
现在它按预期工作了。const
关键字的行为完全相同,但有一个例外。一旦定义了基本值,就永远无法重新定义它。它是一个只读值。
const person = 'Ryan';
person = 'Kristen'; // Uncaught TypeError: Assignment to constant variable.
console.log(person);
如果您尝试将值重新分配给使用 const
定义的变量,浏览器将抛出错误。也就是说,您可以执行以下操作。
const person = {
name: 'Ryan'
};
person.name = 'Kristen';
console.log(person); // {name: 'Kristen'}
使用 const
不会创建不可变的值,存储在 person 变量上的值仍然是对象,但是我们只是更改了其中的一个属性。如果您想要锁定对象,请查看 Object.freeze()
。
let
以及何时使用 const
何时使用 目前关于何时使用 let
与 const
存在一些争论。一般的经验法则是,如果您知道值在整个程序中不会被重新定义,则使用 const
,如果您需要可能更改的值,则使用 let
。让浏览器知道变量在整个程序中将保持不变,将允许它进行某些调整,这可能会提高性能!
模板字面量
在 ES2015 中,有一种定义字符串的新方法,它带来了一些额外的好处。目前,如果您想定义字符串,可以使用 ''
或 ""
。
let name = "Ryan";
let job = 'Instructor';
如果您想将字符串连接在一起,可以使用 +
运算符。
let name = "Ryan";
let job = "Instructor";
let sentence = name + " works at HackerYou as an " + job;
console.log(sentence); // "Ryan works at HackerYou as an Instructor"
随着需要连接的内容增多,这种模式变得非常乏味且难以管理。这时就该引入模板字面量了!
要创建模板字面量字符串,我们使用反引号 `
代替引号。
let name = `Ryan`;
let job = `Instructor`;
它们的行为与常规字符串字面量完全相同,但有一点不同。使用模板字面量,连接变得容易得多。
let name = `Ryan`;
let job = `Instructor`;
let sentence = `${name} works at HackerYou as an ${job}`;
console.log(sentence); // "Ryan works at HackerYou as an Instructor"
注意字符串内部的 ${}
语法?这是一个模板占位符。它允许我们对字符串进行模板化,浏览器将在运行时用正确的值替换 ${}
表达式。这使得连接大型字符串变得更加愉快。
这些新的占位符还允许您在内部执行表达式!
const price = 9.99;
const shipping = 3.99;
const message = `Your total with shipping will be ${price + shipping}.`;
console.log(message); // Your total with shipping will be 13.98.
多行
关于模板字面量的最后一件事是它们如何处理多行字符串。对于常规字符串,如果您希望它跨越多行,则必须执行以下操作。
const multi = "This is a \n multiline string";
console.log(multi);
包含 \n
或换行符将强制文本换行。如果您尝试将文本简单地放在两行上,如下所示
const multi = "This is a
multiline string";
console.log(multi);
它将抛出错误 Uncaught SyntaxError: Unexpected token ILLEGAL
。但是,使用模板字面量,我们可以做到这一点,并在任何我们想要的地方添加换行符!
const multi = `This is a
multiline string`;
console.log(multi);
这使我们能够以更简洁的方式组织标记!
const person = {
name: 'Ryan',
job: 'Developer/Instructor'
};
const markup = `
<div>
<h2>${person.name}</h2>
<h3>${person.job}</h3>
</div>
`;
箭头函数
箭头函数是 ES2015 中创建函数的新语法。这并没有取代我们熟悉和喜爱的 function() {}
语法,但随着时间的推移,我们将越来越多地看到它成为首选的函数语法。
const add = (a, b) => {
return a + b;
};
语法的核心部分是在定义新函数时缺少 function
关键字。相反,我们使用 =>
或胖箭头。您可以像调用任何其他函数一样调用该函数。
add(2, 3); // 5
实际上,您可以通过多种方式定义箭头函数。例如,如果函数只是返回一个值并且函数体中没有其他内容,我们可以删除 {}
和 return
关键字。
const add = (a, b) => a + b;
此处的 return
是隐式的,这意味着它是隐含的,而不是我们必须显式地将 return
添加到我们的块中。如果函数只有一个参数,您实际上可以省略函数定义中的 ()
。
const add5 = a => a + 5;
如果函数没有参数,则使用空括号作为占位符。
const eight = () => 3 + 5;
或者有一种新的模式正在出现,人们会使用 `_` 作为占位符来代替空括号。
const eight = _ => 3 + 5;
箭头函数和函数式编程
由于箭头函数的语法非常简洁,并且函数式编程中的大多数操作在函数体中只需要很少的操作。这种语法非常适合这种编程风格!
// Without Arrow functions
const numbers = [3,4,5,6,7,8];
const doubleNumbers = numbers.map(function(n) {
return n * 2;
});
// With arrow functions
const numbers = [3,4,5,6,7,8];
const doubleNumbers = numbers.map( n => n * 2 );
语法允许你将这个简洁的操作变成一行代码!
this
关键字
在使用箭头函数时需要注意的一点是如何处理 `this` 关键字。考虑对象上的一个方法。
const person = {
firstName: "Ryan",
sayName: function() {
return this.firstName;
}
}
console.log(person.sayName()); // "Ryan"
在 `sayName` 方法内部,`this` 关键字绑定到 `person` 对象。因此运行该方法将输出 `Ryan`。对于箭头函数,`this` 关键字是词法作用域的。这意味着函数的作用域将根据其定义的位置进行绑定。然后 `this` 的值引用父作用域。
const person = {
firstName: "Ryan",
sayName: () => {
return this.firstName;
}
}
console.log(person.sayName()); // undefined
在这个例子中,如果我们将 `sayName` 方法从匿名函数更改为箭头函数,它将返回 `undefined`!`this` 将按词法绑定,在这种情况下它将是 `window` 对象,该对象上没有 `firstName` 属性。在某些情况下,你可能希望获得正确的结果!请查看此示例。
const person = {
firstName: 'Ryan',
hobbies: ['Robots', 'Games', 'Internet'],
showHobbies: function() {
this.hobbies.forEach(function(hobby) {
console.log(`${this.firstName} likes ${hobby}`);
});
}
};
person.showHobbies();
运行此代码将产生 `Uncaught TypeError: Cannot read property 'firstName' of undefined`。我们 `.forEach()` 方法的回调函数中的 `this` 没有绑定到任何东西(在严格模式下,在非严格模式下它将是 `window`)。但是,如果我们将回调更改为箭头函数,我们可以使用词法绑定的 `this` 来获取我们想要的值!
const person = {
firstName: 'Ryan',
hobbies: ['Robots', 'Games', 'Internet'],
showHobbies: function() {
this.hobbies.forEach(hobby => {
console.log(`${this.firstName} likes ${hobby}`);
});
}
};
person.showHobbies();
我们 `forEach` 中的 `this` 将绑定到 `person` 对象!
扩展运算符
有时我们想对数组做一些我们做不到的事情!例如,假设我们有一个数字数组,我们想找到其中的最大值。`Math.max` 似乎是正确的方法。
const numbers = [39, 25, 90, 123];
const max = Math.max(numbers);
console.log(max); // NaN
`Math.max` 是一个方法,它接受以逗号分隔的值列表并返回最高值!遗憾的是,我们不能将数组传递给它。不过,有一种方法可以解决这个问题,我们可以使用一个名为 `.apply` 的方法,它接受一个数组并将函数调用视为我们已将其作为列表传递。
const numbers = [39, 25, 90, 123];
const max = Math.max.apply(null, numbers);
console.log(max); // 123
` .apply` 中的第一个参数是我们希望在调用 `Math.max` 时设置 `this` 值的值,在此示例中,我们提供 `null`。第二个参数是我们想要应用于函数的数组。这可能有点令人困惑,如果有一种更简单的方法来做到这一点呢?
扩展运算符登场
在 ES2015 中,有扩展运算符。语法如下所示
...numbers
此工具的作用是扩展或分散数组中的元素!它将在适当的位置展开它们。我们现在可以将上面的 `.apply` 方法调用更改为如下所示。
const numbers = [39, 25, 90, 123];
const max = Math.max(...numbers);
console.log(max); // 123
扩展运算符将在适当的位置展开数组,并将元素作为逗号分隔的列表传递。
使用扩展运算符连接数组
你还可以使用扩展运算符将数组连接在一起!由于扩展运算符会展开数组,因此我们可以展开数组中的数组!
const numbersArray1 = [3, 4, 5, 7, 8];
const numbersArray2 = [9, 6, 10, 11];
const concatArray = [...numbersArray1, ...numbersArray2];
console.log(concatArray); // [3, 4, 5, 7, 8, 9, 6, 10, 11]
剩余参数
扩展运算符允许我们将参数数组传递给函数。另一方面,剩余参数允许我们收集传递给函数的参数!与扩展运算符一样,剩余参数语法也涉及变量名前面的 `...` 。
让我们看一个例子。假设我们有一个函数,它接受任意数量的参数并返回它们的和,`add(2, 3, 4, 5, 6, 7)` 将返回 27。
const add = function() {
const numbers = Array.prototype.slice.call(arguments);
return numbers.reduce((a,b) => a + b);
};
add(2, 3, 4, 5, 6, 7);
如果没有剩余参数,我们将不得不使用 `arguments` 关键字,并调用 `Array.prototype.slice.call(arguments)`。`Array.prototype.slice.call(arguments)` 到底是什么意思?!`arguments` 是一个类数组对象,这意味着它不是真正的数组,而是一组传递给函数的参数。但是,如果我们想在 `arguments` 上使用 ` .reduce()` 之类的数组方法,则需要进行一些调整。
JavaScript 由许多对象构建而成。所有这些对象都有一个父对象,它们从中继承方法和属性。它们通过 `.prototype` 属性进行此操作。数组具有 `.slice` 方法,我们可以使用它从 `arguments` 值创建真正的数组。使用 `.call`,我们可以使用 `arguments` 作为上下文从原型调用 `.slice` 方法来创建数组……哇,这太多了。
剩余参数登场!
const add = function(...numbers) {
return numbers.reduce((a, b) => a + b);
};
add(2, 3, 4, 5, 6, 7);
哇!这容易多了。剩余参数从传递给函数的参数创建一个真正的数组,因此我们可以在其上使用 ` .reduce` 之类的方法。这使我们能够更轻松地执行类似的任务!
需要指出的是,你可以将剩余参数和扩展运算符混合使用。考虑一个函数,它将乘数作为第一个参数,然后将之后的任何值乘以该数字。
const multi = (multiplier, ...numbers) => {
return numbers.map(n => n * multiplier);
}
我们为函数定义一个乘数参数,并使用剩余参数收集传递给此函数的任意多个参数!
JavaScript 的未来
ES2015 中还有许多我们没有在这里介绍的功能,但希望这能为你提供一些有用的新语法和语言新增内容的基础!如果你想了解更多信息,请查看我在 YouTube 上的视频系列 让我们学习 ES6,以及 letslearnes6.com,在那里你可以了解我正在撰写的关于 ES6 的书籍。
我已经在附加组件开发中使用 ES6 一段时间了,最大的问题当然是何时可以在 Web 上“安全”地使用它?我们是否必须等到所有主要浏览器都加入?对于非 Mozilla 浏览器,是否有可用的垫片库(特别是对于 task.jsm)?
像这样的新技术,向后兼容性始终是最令人担忧的问题。当我发布也应该在使用过时版本的 Gecko 平台的 POstbox 中运行的附加组件时,我遇到了同样的问题。对于此平台,某些更新的功能可能根本不支持。
在大多数情况下,如果你正在进行转译,现在就可以使用了!如果我们等到每个环境都支持这些功能,那么我们可能要等很多年才能使用它们。
话虽如此,请确保你即将运行转译代码的环境将支持该代码。Babel 将获取你的 ES2015(ES6)代码并将其转换为 ES5,但如果你需要的浏览器不支持该代码。你将不得不寻找其他解决方案。
就我个人而言,我最喜欢的 ES6 功能是默认参数。目前,你必须使用 if 语句来模拟行为,即
但在 ES6 中,你可以使用以下方法
简单多了 :P
您可以在此处阅读有关默认参数的更多信息:https://mdn.org.cn/en/docs/Web/JavaScript/Reference/Functions/default_parameters
是的!默认参数很棒。我很快就会制作一个关于它们的视频。
您在关于模板字面量的那一部分有一个小错误。
peron.job
应为person.job
感谢您的文章!
感谢您指出这一点,我会尽快修复它!
感谢您撰写这篇很棒的文章。
我理解正确吗,从现在开始我们可以停止使用 var 和“旧的”函数语法,还是在某些情况下我们必须使用其中之一?
谢谢 Manuel
附言:您还有另一个小错误。“ES2105”在第一段。
您仍然需要在某些地方使用旧语法。在新箭头语法中,this 关键字的绑定方式不同,因此您需要小心处理它。我关于箭头函数的视频可能会更好地解释这一点!
一个 Chris 制作的关于如何为像我这样仍然只在 script.js 文件中使用旧 JS 的人设置 Babel 等工具的屏幕录制会很棒。
那会很棒!
我确实有一个关于开始使用Gulp 和 Babel的视频,它可能在目前对您有所帮助。
希望对您有所帮助!
不错的总结。一个建议,为任何可迭代对象(如数组)包含一个新的 for…of 循环构造的简单示例。