6.2 ES6介绍

ECMAScript 是 JavaScript 的标准化版本,它旨在统一语言语言的规范和功能。所有主流的浏览器或者 Javascript 的运行环境都支持这个规范,因此 ECMAScript 和 JavaScript 的术语是通用的。

在2009年以前,freeCodeCamp 的大多数挑战都是遵循的 ECMAScript 5 (ES5) 的规范。但是 JavaScript 是一门不断发展的语言。随着不断的功能添加及修订,ECMAScript 发布了新的版本供开发者使用。

在2015年,ECMAScript 发布了被称为 ECMAScript 6 (ES6) 的最新版本。在这个挑战里,我们会学习新版本添加的许多强大的功能,包括:

  • 箭头函数
  • 模块
  • Promises 对象
  • 异步生成器 Generators
  • let以及const语法

6.2.1 探索 var 和 let 关键字之间的差异

使用var关键字来声明变量,会出现重复声明导致变量被覆盖却不会报错的问题。

当你的代码规模变得更加庞大的时候,就可能会在不经意间覆盖了之前定义的变量。这样的行为不会报错,导致了 debug 非常困难。在 ES6 中引入了新的关键字let来解决var关键字带来的潜在问题。如果你在上面的代码中,使用了let关键字来代替var关键字,结果会报错:

let camper = 'James';
let camper = 'David'; // 报错

与var不同的是,当使用let的时候,同一名字的变量只能被声明一次。

// explore-differences-between-the-var-and-let-keywords
// 请更新这段代码,并且在其中只使用let关键字

let catName;
let quote;
function catTalk() {
  "use strict";

  catName = "Oliver";
  quote = catName + " says Meow!";

}
catTalk();

6.6.2 比较 var 和 let 关键字的作用域

当你使用var关键字来声明一个变量的时候,这个变量会被声明成全局变量,或是函数内的局部变量。

let关键字的作用类似,但会有一些额外的特性。如果你在代码块、语句或表达式中使用关键字let声明变量,这个变量的作用域就被限制在当前的代码块,语句或表达式之中。

举个例子:

var numArray = [];
for (var i = 0; i < 3; i++) {
  numArray.push(i);
}
console.log(numArray);
// 返回 [0, 1, 2]
console.log(i);
// 返回 3

当使用var关键字的时候,i会被声明成全局变量。当i++执行的时候,它会改变全局变量的值。这段代码可以看做下面这样:

var numArray = [];
var i;
for (i = 0; i < 3; i++) {
  numArray.push(i);
}
console.log(numArray);
// returns [0, 1, 2]
console.log(i);
// returns 3

如果你在for循环中创建了使用i变量的函数,那么在后续调用函数的时候,上面提到的这种行为就会导致问题。这是因为函数存储的值会因为全局变量i的变化而不断的改变:

var printNumTwo;
for (var i = 0; i < 3; i++) {
  if(i === 2){
    printNumTwo = function() {
      return i;
    };
  }
}
console.log(printNumTwo());
// 返回 3

可以看到,printNumTwo()打印了 3 而不是 2。这是因为i发生了改变,并且函数printNumTwo()返回的是全局变量i的值,而不是for循环中创建函数时i的值。let关键字就不会有这种现象:

'use strict';
let printNumTwo;
for (let i = 0; i < 3; i++) {
  if (i === 2) {
    printNumTwo = function() {
      return i;
    };
  }
}
console.log(printNumTwo());
// 返回 2
console.log(i);
// 返回 "没有定义 i 变量"

i在全局作用域中没有声明,所以它没有被定义,它的声明只会发生在for循环内。在循环执行的时候,let关键字创建了三个不同的i变量,他们的值分别为 0、1 和 2,所以printNumTwo()返回了正确的值。

6.2.3 用 const 关键字声明只读变量

let并不是唯一的新的声明变量的方式。在 ES6里面,你还可以使用const关键字来声明变量。

const拥有let的所有优点,不同的是,通过code声明的变量是只读的。这意味着通过const声明的变量只能被赋值一次,而不能被再次赋值。

"use strict"
const FAV_PET = "Cats";
FAV_PET = "Dogs"; // 报错

可以看见,尝试给通过const声明的变量再次赋值会报错。你应该使用const关键字来对所有不打算再次赋值的变量进行声明。这有助于你避免给一个常量进行额外的再次赋值。一个最佳实践是对所有常量的命名采用全大写字母,并在单词之间使用下划线进行分隔。

6.2.4 改变一个用 const 声明的数组

在现代的 JavaScript 里,const声明有很多用法。

一些开发者倾向默认使用const来声明所有变量,但如果它们打算在后续的代码中修改某个值,那在声明的时候就会用let

然而,你要注意,对象(包括数组和函数)在使用const声明的时候依然是可变的。使用const来声明只会保证它的标识不会被重新赋值。

"use strict";
const s = [5, 6, 7];
s = [1, 2, 3]; // 试图给 const 变量赋值,报错
s[2] = 45; // 与用 var 或 let 声明的数组一样,这个操作也会成功
console.log(s); // 返回 [5, 6, 45]

从以上代码看出,你可以改变[5, 6, 7]自身,所以s变量指向了改变后的数组[5, 6, 45]。和所有数组一样,数组s中的数组元素是可以被改变的,但是因为使用了const关键字,你不能使用赋值操作符将变量标识s指向另外一个数组。

6.2.5 防止对象改变

const声明并不会真的保护你的数据不被改变。为了确保数据不被改变,JavaScript 提供了一个函数Object.freeze来防止数据改变。

当一个对象被冻结的时候,你不能再对它的属性再进行增、删、改的操作。任何试图改变对象的操作都会被阻止,却不会报错。

let obj = {
  name:"FreeCodeCamp",
  review:"Awesome"
};
Object.freeze(obj);
obj.review = "bad"; //obj 对象被冻结了,这个操作会被忽略
obj.newProp = "Test"; // will be ignored. Mutation not allowed
console.log(obj);
// { name: "FreeCodeCamp", review:"Awesome"}

6.2.6 使用箭头函数编写简洁的匿名函数

在 JavaScript 里,我们会经常遇到不需要给函数命名的情况,尤其是在需要将一个函数作为参数传给另外一个函数的时候。这时,我们会创建匿名函数。因为这些函数不会在其他地方复用,所以我们不需要给它们命名。

这种情况下,我们通常会使用以下语法:

const myFunc = function() {
  const myVar = "value";
  return myVar;
}

ES6 提供了其他写匿名函数的方式的语法糖。你可以使用箭头函数:

const myFunc = () => {
  const myVar = "value";
  return myVar;
}

当不需要函数体,只返回一个值的时候,箭头函数允许你省略return关键字和外面的大括号。这样就可以将一个简单的函数简化成一个单行语句。

const myFunc= () => "value"

这段代码仍然会返回value。

6.2.7 编写带参数的箭头函数

和一般的函数一样,你也可以给箭头函数传递参数:

// doubles input value and returns it
const doubler = (item) => item * 2;

你同样可以给箭头函数传递多个参数。

6.2.8 使用箭头函数编写简洁的匿名函数

箭头函数在类似map,filter,reduce等需要其他函数作为参数来处理数据的高阶函数里会很好用。

阅读以下代码:

FBPosts.filter(function(post) {
  return post.thumbnail !== null && post.shares > 100 && post.likes > 500;
})

我们写下了filter函数,并尽量保证可读性。现在让我们用箭头函数来写同样的代码看看:

FBPosts.filter((post) => post.thumbnail !== null && post.shares > 100 && post.likes > 500)

这段代码完成了同样的任务,却变得更加简短易懂了。

6.2.9 设置函数的默认参数

ES6 里允许给函数传入默认参数,来构建更加灵活的函数。

请看以下代码:

function greeting(name = "Anonymous") {
  return "Hello " + name;
}
console.log(greeting("John")); // Hello John
console.log(greeting()); // Hello Anonymous

默认参数会在参数没有被指定(值为 undefined )的时候起作用。在上面的例子中,参数name会在没有得到新的值的时候,默认使用值 “Anonymous”。你还可以给多个参数赋予默认值。

6.2.10 将 rest 操作符与函数参数一起使用

ES6 推出了用于函数参数的 rest 操作符帮助我们创建更加灵活的函数。在rest操作符的帮助下,你可以创建有一个变量来接受多个参数的函数。这些参数被储存在一个可以在函数内部读取的数组中。

请看以下代码:

function howMany(...args) {
  return "You have passed " + args.length + " arguments.";
}
console.log(howMany(0, 1, 2)); // 你需要输入3个参数。
console.log(howMany("string", null, [1, 2, 3], { })); //你需要输入4个参数。

rest操作符可以避免查看args数组的需求,并且允许我们在参数数组上使用map(),fiter(),和reduce()

6.2.11 使用 spread 运算符展开数组项

ES6 允许我们使用 展开操作符 来展开数组,以及需要多个参数或元素的表达式。

下面的 ES5 代码使用了apply()来计算数组的最大值:

var arr = [6, 89, 3, 45];
var maximus = Math.max.apply(null, arr); // 返回 89

我们必须使用Math.max.apply(null,arr),是因为直接调用Math.max(arr)会返回NaNMath.max()函数需要传入的是一系列由逗号分隔的参数,而不是一个数组。

扩展运算符使这种语法更易于阅读和维护。

const arr = [6, 89, 3, 45];
const maximus = Math.max(...arr); // 返回 89

…arr返回了一个“打开”的数组。或者说它 展开 了数组。

然而,展开操作符只能够在函数的参数中,或者数组之中使用。下面的代码将会报错:

const spreaded = ...arr; // 将会发生语法错误

6.2.12 use-destructuring-assignment-to-assign-variables-from-objects

对于对象,我们也可以做同样的操作。解构赋值 就是可以从对象中直接获取对应值的语法。

看看以下 ES5 的代码:

var voxel = {x: 3.6, y: 7.4, z: 6.54 };
var x = voxel.x; // x = 3.6
var y = voxel.y; // y = 7.4
var z = voxel.z; // z = 6.54

使用 ES6 的解构语法可以完成同样的赋值语句:

const { x, y, z } = voxel; // x = 3.6, y = 7.4, z = 6.54

如果你想将voxel.x,voxel.y,voxel.z的值分别赋给a,b,c,可以用以下这种很棒的方式:

const { x : a, y : b, z : c } = voxel // a = 3.6, b = 7.4, c = 6.54

你可以这样理解:“将x地址中的值拷贝到a当中去。”,等等。

6.2.13 使用解构赋值从嵌套对象中分配变量

同样,我们可以将 嵌套的对象解构到变量中。

请看以下代码:

const a = {
  start: { x: 5, y: 6},
  end: { x: 6, y: -9 }
};
const { start : { x: startX, y: startY }} = a;
console.log(startX, startY); // 5, 6

在上面的例子里,a.start将值赋给了变量start,start同样也是个对象。

6.2.14 使用解构赋值从数组中分配变量

在 ES6 里面,解构数组可以如同解构对象一样简单。

与数组解构不同,数组的扩展运算会将数组里的所有内容分解成一个由逗号分隔的列表。所以,你不能选择哪个元素来给变量赋值。

而对数组进行解构却可以让我们做到这一点:

const [a, b] = [1, 2, 3, 4, 5, 6];
console.log(a, b); // 1, 2

变量a以及b分别被数组的第一、第二个元素赋值。

我们甚至能在数组解构中使用逗号分隔符,来获取任意一个想要的值:

const [a, b,,, c] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c); // 1, 2, 5