让我们学习 ES2015

Avatar of Ryan Christiani
Ryan Christiani

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下是由 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。

概述

在本文中,我们将了解一些现在可供开发人员使用的功能。

我们将了解新的关键字,如 letconst,如何创建模板字面量以简化连接,新的箭头函数语法、扩展运算符和剩余参数!以下是目录

  1. letconst
  2. 模板字面量
  3. 箭头函数
  4. 扩展运算符
  5. 剩余参数

这些新增功能可以帮助我们更愉快地编写 JavaScript!

letconst

letconst 是 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 循环之外可用。

如果您想使用 letconst,则必须首先为 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

目前关于何时使用 letconst 存在一些争论。一般的经验法则是,如果您知道值在整个程序中不会被重新定义,则使用 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 的书籍。