JavaScript:ES6之语法

严格来说 ES6 和 ES2015 是同一个事物的不同名称,重要的是这些名称代表 JavaScript 编程语言现今的最大更新,从 1995 年到今天 JavaScript 编程语言经历了一些大刀阔斧的必要改进,随着这些改进产生了一批新的关键字,编写函数的方法、异步简便方法等等。

Let 和 Const

现在可以在 JavaScript 中使用两种新的方式来声明变量:let 和 const。

到目前为止,在 JavaScript 中声明变量的唯一方式是使用关键字 var。为了理解为何添加了 let 和 const,先看一个示例,了解使用 var 会带来怎样的麻烦。

function getClothing(isCold) {
  if (isCold) {
    var freezing = "Grab a jacket!";
  } else {
    var hot = "It's a shorts kind of day.";
    console.log(freezing);
  }
}
getClothing(false);

返回:
undefined

提升

提升是浏览器解析 JavaScript 的结果。本质上,在执行任何 JavaScript 代码之前,所有变量都会被「提升」,也就是提升到函数作用域的顶部。因此在运行时,getClothing() 函数实际上看起来如下所示…

function getClothing(isCold) {
  var freezing,hot
  if (isCold) {
    freezing = "Grab a jacket!";
  } else {
    hot = "It's a shorts kind of day.";
    console.log(freezing);
  }
}
getClothing(false);

相当于变量一开始就声明了但是没有进行赋值,而因为传入了 false 走了 else 条件就没有让 freezing 得到赋值

let 和 const

使用 let 和 const 声明的变量解决了这种提升问题,因为它们的作用域是到块,而不是函数。在之前,当使用 var 时变量要么为全局作用域,要么为本地作用域,也就是整个函数作用域。

如果在代码块(用花括号 { } 表示)中使用 let 或 const 声明变量,那么该变量会陷入暂时性死区,直到该变量的声明被处理。这种行为会阻止变量被访问,除非它们被声明了。

function getClothing(isCold) {
  if (isCold) {
    let freezing = "Grab a jacket!";
  } else {
    let hot = "It's a shorts kind of day.";
    console.log(freezing);
  }
}
getClothing(false);

返回:
ReferenceError: freezing is not defined

let 和 const 的使用规则

let 和 const 还有一些其他有趣特性。

  • 使用 let 声明的变量可以重新赋值,但是不能在同一作用域内重新声明。
  • 使用 const 声明的变量必须赋初始值,但是不能在同一作用域内重新声明,也无法重新赋值。

使用案例

最大的问题是何时应该使用 let 和 const?一般法则如下:

  • 当你打算为变量重新赋值时,使用 let,以及
  • 当你不打算为变量重新赋值时,使用 const。

因为 const 是声明变量最严格的方式,我们建议始终使用 const 声明变量,因为这样代码更容易读懂,你知道标识符在程序的整个生命周期内都不会改变。如果你发现你需要更新变量或更改变量,则回去将其从 const 切换成 let。

很简单吧?但是 var 呢?

var 该怎么办?

还有必要使用 var 吗?没有了。

在某些情况下有必要使用 var,例如如果你想全局定义变量,但是这种做法通常都不合理,应该避免。从现在开始,建议放弃使用 var,改为使用 let 和 const。

模板字面量

在 ES6 之前,将字符串连接到一起的旧方法是使用字符串连接运算符 (+)。

let beijing = "北京";
let shanghai = "上海";

console.log(beijing + "和" + shanghai + "是超级大城市。");

上面这样比较简单的还好说,如果是下面这样的

let beijing = "北京";
let shanghai = "上海";
let shenzhen = "深圳";

let note = beijing+ ',\n\n' +
  '以及 ' + shanghai + '\n' +
  '都是数一数二的大都市\n\n' +
  '当然我还喜欢\n' +
  shenzhen;

各种变量甚至对象的属性再加上换行转义符,或是以后利用 JavaScript 处理 HTML 的列表元素,这字符串链接运算符和换行转义符就很够恶心人的了。

注意:作为字符串连接运算符 ( + ) 的替代方法,你可以使用字符串的 concat() 方法。但是这两种方式都比较笨拙。

模板字面量的到来解决了这个问题,模板字面量本质上是包含嵌入式表达式的字符串字面量。

模板字面量用倒引号(或称为反引号,在 Tab 键上方)而不是单引号 ( '' ) 或双引号( "" )表示,可以包含用 ${expression} 表示的占位符。这样更容易构建字符串。

let shenzhen = "深圳";

let citys = {
    beijing: "北京",
    shanghai: "上海",
}

let note = `${citys.beijing}


  以及 ${citys.shanghai}

  都是数一数二的大都市

  当然我还喜欢
  ${shenzhen};`

console.log(note)

通过使用模板字面量,可以不用再使用引号和字符串连接运算符。并且可以随意换行不需要转义换行符。

提示:模板字面量中的嵌入式表达式不仅仅可以用来引用变量和对象属性。还可以在嵌入式表达式中进行运算、调用函数和使用循环!

解构

在 ES6 中,你可以使用「解构」从数组和对象中提取值并赋给独特的变量。

在 ES6 之前是这么干的

const point = [10, 25, -34];

const x = point[0];
const y = point[1];
const z = point[2];

console.log(x, y, z);

解构这一概念从 Perl 和 Python 等语言中获得灵感,使你能够指定要从赋值左侧上的数组或对象中提取的元素。听起来有点奇怪,实际上可以获得和之前一样的结果,但是用到的代码确更少;依然很好理解。

解构数组中的值

const point = [10, 25, -34, 50];

const [x, y, z] = point; // 定义了前三个变量,剩余没有被解构赋值的就忽略掉了

console.log(x, y, z); //10 25 -34

在此示例中,方括号 [ ] 表示被解构的数组,x、y 和 z 表示要将数组中的值存储在其中的变量。注意,你不需要指定要从中提取值的索引,因为索引可以暗示出来。

不仅是剩余的可以忽略,其中的也可以忽略掉其中的

const point = [10, 25, -34];
const [x, , z] = point; // 此时25
console.log(x, z);

解构对象中的值

const gemstone = {
  type: 'quartz',
  color: 'rose',
  karat: 21.29
};

const {type, color, karat} = gemstone;

console.log(type, color, karat);

在此示例中,花括号 { } 表示被解构的对象,typecolorkarat 表示要将对象中的属性存储到其中的变量。注意不用指定要从其中提取值的属性。因为 gemstone 具有 type 属性,值自动存储在 type 变量中。类似地,gemstone 具有 color 属性,因此 color 的值自动存储在 color 变量中。karat 也一样。

提示:你还可以指定在解构对象时要选择的值。例如,let {color} = gemstone; 将仅选择 gemstone 对象中的 color 属性。

对象字面量简写法

ES6 中经常出现的一个现象是删掉不必要的重复代码。通过删掉不必要的重复代码,代码更容易读懂,并且更简练。推出的新简写法(用来初始化对象并向对象中添加方法)就是这一体现。

你可能写过这样的代码:使用和所分配的变量名称相同的名称初始化对象。

如果没写过的话,看看下面的示例。

let type = 'quartz';
let color = 'rose';
let carat = 21.29;

const gemstone = {
  type: type,
  color: color,
  carat: carat
};

console.log(gemstone);

看到重复的地方了吗?type: typecolor: colorcarat:carat 不显得很冗长吗?

好消息是,如果属性名称和所分配的变量名称一样,那么就可以从对象属性中删掉这些重复的变量名称。

let type = 'quartz';
let color = 'rose';
let carat = 21.29;

const gemstone = { type, color,carat};

console.log(gemstone);

说到简写法,还有一种向对象中添加方法的简写方式。

要看看具体写法,我们先向 gemstone 对象添加 calculateWorth() 方法。calculateWorth() 方法将根据宝石的 typecolorcarat 告诉我们宝石成本多少。

const gemstone = {
  type,
  color,
  carat,
  calculateWorth: function() { // 之前的写法
      ...
  },
  calculateWorth() { ... } // 简写法
};

迭代

什么是迭代?可能描述它的最好方法是通过看一个普通的 for 循环,当编写一个 for 循环时要给此循环提供一个变量,此变量通常为字母 i(iterator) 因为它被用作迭代器来跟踪你在循环中的位置,当你循环数组时此迭代器会像索引一样让你一个接一个访问数组中的每个项,这种逐个获取项的过程就叫迭代。

const = years = ["2000","2010","2019"];

for ( let i=0; i< years.length; i++ ) {
  console.log(years[i]);
}

for...of 循环是最新添加到 JavaScript 循环系列中的循环。

它结合了其兄弟循环形式 for 循环和 for...in 循环的优势,可以循环任何可迭代(也就是遵守可迭代协议)类型的数据。默认情况下,包含以下数据类型:StringArrayMapSet,注意不包含 Object 数据类型(即 {})。默认情况下,对象不可迭代。

在研究 for...of 循环之前,先快速了解下其他 for 循环,看看它们有哪些不足之处。

for 循环

for 循环很明显是最常见的循环类型,因此快速复习下即可。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (let i = 0; i < digits.length; i++) {
  console.log(digits[i]);
}

for 循环的最大缺点是需要跟踪计数器和退出条件。

在此示例中,我们使用变量 i 作为计数器来跟踪循环并访问数组中的值。我们还使用 digits.length 来判断循环的退出条件。如果只看一眼这段代码,有时候会比较困惑,尤其是对于初学者而言。

虽然 for 循环在循环数组时的确具有优势,但是某些数据结构不是数组,因此并非始终适合使用 loop 循环。

for...in 循环

for...in 循环改善了 for 循环的不足之处,它消除了计数器逻辑和退出条件。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

但是依然需要使用 index 来访问数组的值,这样很麻烦;几乎比之前更让人迷惑。

此外,当你需要向数组中添加额外的方法(或另一个对象)时,for...in 循环会带来很大的麻烦。因为 for...in 循环循环访问所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性,这些属性也会出现在循环中。

Array.prototype.decimalfy = function() {
  for (let i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

太可怕!这就是为何在循环访问数组时,不建议使用 for...in 循环。

注意: forEach 循环 是另一种形式的 JavaScript 循环。但是,forEach() 实际上是数组方法,因此只能用在数组中。也无法停止或退出 forEach 循环。如果希望你的循环中出现这种行为,则需要使用基本的 for 循环。

for...of 循环

for...of 循环用于循环访问任何可迭代的数据类型。

for...of 循环的编写方式和 for...in 循环的基本一样,只是将 in 替换为 of,可以忽略索引。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

这使得 for...of 循环成为所有 for 循环中最简洁的版本。

提示:建议使用复数对象名称来表示多个值的集合。这样,循环该集合时,可以使用名称的单数版本来表示集合中的单个值。例如,for (const button of buttons) {…}。

let years = [1993,2000,2006,2009,2012];
// for
for (let i = 0; i < years.length; i++){
    console.log(years[i]);
}
// for...in
for (let year in years){
    console.log(years[year]);
}
// for..of
for (let year of years){
    console.log(year);
}

for...of 循环还具有其他优势,解决了 for 和 for...in 循环的不足之处。

你可以随时停止或退出 for...of 循环。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  if (digit % 2 === 0) {
    continue;
  }
  console.log(digit);
}

不用担心向对象中添加新的属性。for...of 循环将只循环访问对象中的值。

Array.prototype.decimalfy = function() {
  for (i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

展开运算符

展开运算符(用三个连续的点 ( ... ) 表示)是 ES6 中的新概念,使你能够将字面量对象展开为多个元素。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(...digits); // 0 1 2 3 4 5 6 7 8 9

可以看到打印结果扩展开了单个元素。

结合数组

展开运算符的一个用途是结合数组。

const fruits = ["apples", "bananas", "pears"];
const vegetables = ["corn", "potatoes", "carrots"];
// 在有展开运算符之前,必须使用 Array 的 concat() 方法。
//const produce = fruits.concat(vegetables);
// 展开运算符的到来简化了简化了这一步
const produce = [...fruits, ...vegetables];
console.log(produce);

剩余参数

如果可以使用展开运算符将数组展开为多个元素,那么肯定有一种方式将多个元素绑定到一个数组中吧?

实际上,的确有!叫做剩余参数,它是 ES6 中新加的另一个运算符。

剩余参数也用三个连续的点 ( ... ) 表示,使你能够将不定数量的元素表示为数组。它在多种情形下都比较有用。

一种情形是将变量赋数组值时。例如,

const order = [20.17, 18.67, 1.50, "cheese", "eggs", "milk", "bread"];
const [total, subtotal, tax, ...items] = order;
console.log(total, subtotal, tax, items);

返回:
20.17 18.67 1.5 [ "cheese", "eggs", "milk", "bread" ]

该代码将 order 数组的值分配给单个变量。数组中的前三个值被分配给了 totalsubtotaltax,但是需要重点注意的是 items

通过使用剩余参数,数组中剩余的值(作为数组)被分配给了 items

可变参数函数

剩余参数的另一个用例是处理可变参数函数。可变参数函数是接受不定数量参数的函数。

例如,假设有一个叫做 sum() 的函数,它会计算不定数量的数字的和。在运行期间,如何调用 sum() 函数?

sum(1, 2);
sum(10, 36, 7, 84, 90, 110);
sum(-23, 3000, 575000);

实际上有无数种方式可以调用 sum() 函数。不管传入函数的数字有多少个,应该始终返回数字的总和。

使用参数对象

在之前版本的 JavaScript 中,可以使用参数对象处理这种类型的函数。参数对象是像数组一样的对象,可以当做本地变量在所有函数中使用。它针对传入函数的每个参数都包含一个值,第一个参数从 0 开始,第二个参数为 1,以此类推。

如果我们看看 sum() 函数的实现方法,会发现可以使用参数对象来处理传递给它的各种数字。

function sum() {
  let total = 0;  
  for(const argument of arguments) {
    total += argument;
  }
  return total;
}

现在可以正常运行,但是存在问题:

  1. 如果查看 sum() 函数的定义,会发现它没有任何参数。
    • 这容易引起误导,因为我们知道 sum() 函数可以处理不定数量的参数。
  2. 难以理解。
    • 如果你从未使用过参数对象,那么看了这段代码后很可能会疑问参数对象来自何处。是不是凭空出现的?看起来肯定是这样。

使用剩余参数

幸运的是,出现剩余参数后,你可以重写 sum() 函数,使其阅读起来更清晰。

function sum(...nums) {
  let total = 0;  
  for(const num of nums) {
    total += num;
  }
  return total;
}

这一版本的 sum() 函数更简练、更易读懂。此外,注意 for...in 循环被替换成了新的 for...of循环。

Conners Hua

这个家伙很懒,什么都没有留下。

您可能还喜欢...

发表评论

电子邮件地址不会被公开。 必填项已用*标注