首页 » 学习JavaScript数据结构与算法(第2版) » 学习JavaScript数据结构与算法(第2版)全文在线阅读

《学习JavaScript数据结构与算法(第2版)》1.9 ECMAScript 6的功能

关灯直达底部

本节,我们将演示如何使用ES6的一些新功能。这既对日常的JavaScript编码有用,也可以简化本书后面章节中的例子。我们将介绍以下功能。

  • letconst

  • 模板字面量

  • 解构

  • 展开操作符

  • 箭头函数:=>

1.9.1 用let替代var声明变量

到ES5为止,我们可以在代码中任意位置声明变量,甚至重写已声明的变量,代码如下:

var framework = /'Angular/';var framework = /'React/';console.log(framework);  

上面代码的输出是React,这个值被赋给最后声明的framework变量。这段代码中有两个同名的变量,这是非常危险的,可能会导致错误的输出。

C、Java、C#等其他语言不允许这种行为。ES6引入了一个let关键字,它是新的var,这意味着我们可以直接把var关键字都替换成let。以下代码就是一个例子:

let language = /'JavaScript!/'; // {1}let language = /'Ruby!/'; // {2} - 抛出错误console.log(language);  

{2}会抛出错误,因为在同一作用域中已经声明过language变量(行{1})。后面会讨论let和变量作用域。

 你可以访问https://goo.gl/he0udZ,测试和执行上面的代码。

let的变量作用域

我们通过下面这个例子(https://goo.gl/NbsVvg),来理解let关键字声明的变量如何工作:

let movie = /'Lord of the Rings/'; //{1}//var movie = /'Batman v Superman/'; //抛出错误,movie变量已声明function starWarsFan{  let movie = /'Star Wars/'; //{2}  return movie;}function marvelFan{  movie = /'The Avengers/'; //{3}  return movie;}function blizzardFan{  let isFan = true;  let phrase = /'Warcraft/'; //{4}  console.log(/'Before if: /' + phrase);  if (isFan){    let phrase = /'initial text/'; //{5}    phrase = /'For the Horde!/'; //{6}    console.log(/'Inside if: /' + phrase);  }  phrase = /'For the Alliance!/'; //{7}  console.log(/'After if: /' + phrase);}console.log(movie); //{8}console.log(starWarsFan); //{9}console.log(marvelFan); //{10}console.log(movie); //{11}blizzardFan; //{12}  

以上代码的输出如下:

Lord of the RingsStar WarsThe AvengersThe AvengersBefore if: WarcraftInside if: For the Horde!After if: For the Alliance!  

现在,我们来讨论得到这些输出的原因。

  • 我们在行{1}声明了一个movie变量并赋值为Lord of the Rings,然后在行{8}输出它的值。你在1.3.1节中的“变量作用域”部分已经学过,这个变量拥有全局作用域。

  • 我们在行{9}执行了starWarsFan函数。在这个函数里,我们也声明了一个movie变量(行{2})。这个函数的输出是Star Wars,因为行{2}的变量拥有局部作用域,也就是说它只在函数内部可见。

  • 我们在行{10}执行了marvelFan函数。在这个函数里,我们改变了movie变量的值(行{3})。这个变量是行{1}声明的全局变量。因此,行{11}的全局变量输出和行{10}的输出相同,都是The Avengers

  • 最后,我们在行{12}执行了blizzardFan函数。在这个函数里,我们声明了一个拥有函数内作用域的phrase变量(行{4})。然后,又声明了一个phase变量(行{5}),但这个变量的作用域只在if语句内。

  • 我们在行{6}改变了phrase的值。由于还在if语句内,值发生改变的是在行{5}声明的变量。

  • 然后,我们在行{7}再次改变了phrase的值,但由于不是在if语句内,行{4}声明的变量的值改变了。

作用域的行为与在Java或C等其他编程语言中一样。然而,这是ES6才引入到JavaScript的。

1.9.2 常量

ES6还引入了const关键字。它的行为和let关键字一样,唯一的区别在于,用const定义的变量是只读的,也就是常量。

举例来说,考虑如下代码:

const PI = 3.141593;PI = 3.0; //抛出错误console.log(PI);  

当我们试图把一个新的值赋给 PI,甚至只是用var PIlet PI重新声明时,代码就会抛出错误,告诉我们PI是只读的。

 你可以访问https://goo.gl/4xuWrC执行上面的例子。

1.9.3 模板字面量

模板字面量真的很棒,因为我们创建字符串的时候不必再拼接值。

举例来说,考虑如下ES5代码:

var book = {  name: /'学习JavaScript数据结构与算法/'};console.log(/'你正在阅读/' + book.name + /'。n这是新的一行n 这也是。/');  

我们可以用如下代码改进上面这个console.log输出的语法:

console.log(`你正在阅读${book.name}。这是新的一行这也是。`);  

模板字面量用一对`包裹。要插入变量的值,只要把变量放在${}里就可以了,就像例子中的book.name

模板字面量也可以用于多行的字符串。再也不需要用n了。只要按下键盘上的Enter就可以换一行,就像上面例子里的这是新的一行。

这个功能对简化我们例子的输出非常有用!

 你可以访问https://goo.gl/PTqnwO执行上面的例子。

1.9.4 箭头函数

ES6的箭头函数极大地简化了函数的语法。考虑如下例子:

var circleArea = function circleArea(r) {  var PI = 3.14;  var area = PI * r * r;  return area;};console.log(circleArea(2));  

上面这段代码的语法可以简化为如下代码:

let circleArea = (r) => { //{1}  const PI = 3.14;  let area = PI * r * r;  return area;}console.log(circleArea(2));  

这个例子最大的区别在于行{1},我们可以省去function关键字,只用=>

如果函数只有一条语句,还可以变得更简单,连return关键字都可以省去。看看下面的代码:

let circleArea2 = (r) => 3.14 * r * r;console.log(circleArea2(2));  

 你可以访问https://goo.gl/CigniJ执行上面的例子。

1.9.5 函数的参数默认值

在ES6里,函数的参数还可以定义默认值。下面是一个例子:

function sum (x = 1, y = 2, z = 3) {  return x + y + z};console.log(sum(4,2)); //输出9  

由于我们没有传入参数z,它的值默认为3。因此,4 + 2 + 3 == 9

在ES6之前,上面的函数我们只能写成这样:

function sum (x, y, z) {  if (x === undefined)    x = 1;  if (y === undefined)    y = 2;  if (z === undefined)    z = 3;  return x + y + z;};  

有了ES6的参数默认值,代码可以少写好几行。

 你可以访问https://goo.gl/2MiJ59执行上面的例子。

1.9.6 声明展开和剩余参数

在ES5中,我们可以用apply函数把数组转化为参数。为此,ES6有了展开操作符(...)。举例来说,考虑我们上一节声明的sum函数。可以执行如下代码来传入参数xyz

var params = [3, 4, 5];console.log(sum(...params));  

以上代码和下面的ES5代码的效果是相同的:

var params = [3, 4, 5];console.log(sum.apply(undefined, params));  

在函数中,展开操作符(...)也可以代替arguments,当作剩余参数使用。考虑如下例子:

function restParamaterFunction (x, y, ...a) {  return (x + y) * a.length;}console.log(restParamaterFunction(1, 2, /"hello/", true, 7)); //输出9;  

以上代码和下面代码的效果是相同的:

function restParamaterFunction (x, y) {  var a = Array.prototype.slice.call(arguments, 2);  return (x + y) * a.length;};  

 你可以访问https://goo.gl/8equk5执行展开操作符的例子,访问https://goo.gl/LaJZqU执行剩余参数的例子。

增强的对象属性

ES6引入了数组解构的概念,可以用来一次初始化多个变量。考虑如下例子:

var [x, y] = [/'a/', /'b/'];  

以上代码和下面代码的效果是相同的:

var x = /'a/';var y = /'b/';  

数组解构也可以用来进行值的互换,而不需要创建临时变量,如下:

[x, y] = [y, x];

以上代码和下面代码的效果是相同的:

var temp = x;x = y;y = temp;  

这对你学习排序算法会很有用,因为互换值的情况很常见。

还有一个称为属性简写的功能,它是对象解构的另一种方式。考虑如下例子:

var [x, y] = [/'a/', /'b/'];var obj = { x, y };console.log(obj); // { x: /"a/", y: /"b/" }  

以上代码和下面代码的效果是相同的:

var x = /'a/';var y = /'b/';var obj2 = { x: x, y: y };console.log(obj2); // { x: /"a/", y: /"b/" }  

本节我们要讨论的最后一个功能是方法属性。这使得开发者可以在对象中声明看起来像属性的函数。下面是一个例子:

var hello = {  name : /'abcdef/',  printHello {    console.log(/'Hello/');  }}console.log(hello.printHello);  

以上代码也可以写成下面这样:

var hello = {  name: /'abcdef/',  printHello: function printHello {    console.log(/'Hello/');  }};  

你可以访问以下URL执行上面三个例子。

  • 数组解构:https://goo.gl/VsLecp

  • 变量互换:https://goo.gl/EyFAII

  • 属性简写:https://goo.gl/DKU2PN

1.9.7 使用类进行面向对象编程

ES6还引入了一种更简洁的声明类的方式。在1.6节,你已经学习了像下面这样声明一个Book类的方式:

function Book(title, pages, isbn){ //{1}  this.title = title;  this.pages = pages;  this.isbn = isbn;}Book.prototype.printTitle = function{  console.log(this.title);};  

我们可以用ES6把语法简化如下:

class Book { //{2}  constructor (title, pages, isbn) {    this.title = title;    this.pages = pages;    this.isbn = isbn;  }  printIsbn{    console.log(this.isbn);  }}  

只需要使用class关键字,声明一个有constructor函数和诸如printIsbn等其他函数的类。行{1}声明Book类的代码与行{2}声明的代码具有相同的效果和输出:

let book = new Book(/'title/', /'pag/', /'isbn/');console.log(book.title);  //输出图书标题book.title = /'new title/'; //更新图书标题console.log(book.title);  //输出图书标题  

 你可以访问https://goo.gl/UhK1n4执行上面的例子。

  1. 继承

    除了新的声明类的方式,类的继承也有简化的语法。我们看一个例子:

    class ITBook extends Book { //{1}  constructor (title, pages, isbn, technology) {    super(title, pages, isbn); //{2}    this.technology = technology;  }  printTechnology{    console.log(this.technology);  }} let jsBook = new ITBook(/'学习JS算法/', /'200/', /'1234567890/', /'JavaScript/');console.log(jsBook.title);console.log(jsBook.printTechnology);  

    我们可以用extends关键字扩展一个类并继承它的行为(行{1})。在构造函数中,我们也可以通过super关键字引用父类的构造函数(行{2})。

    尽管在JavaScript中声明类的新方式的语法与Java、C、C++等其他编程语言很类似,但JavaScript面向对象编程还是基于原型实现的。

     你可以访问https://goo.gl/hgQvo9执行上面的例子。

  2. 使用属性存取器

    使用新的类语法也可以为属性创建存取器函数。虽然不像其他面向对象语言(封装概念),类的属性不是私有的,但最好还是遵循一种命名模式。

    下面的例子是一个声明了getset函数的类:

    class Person {  constructor (name) {    this._name = name; //{1}  }  get name { //{2}    return this._name;  }  set name(value) { //{3}    this._name = value;  }} let lotrChar = new Person(/'Frodo/');console.log(lotrChar.name); //{4}lotrChar.name = /'Gandalf/'; //{5}console.log(lotrChar.name);lotrChar._name = /'Sam/'; //{6}console.log(lotrChar.name);  

    要声明getset函数,只需要在我们要暴露和使用的函数名前面加上getset关键字(行{2}和行{3})。我们可以用相同的名字声明类属性,或者在属性名前面加下划线(行{1}),让这个属性看起来像是私有的。

    然后,只要像普通的属性一样,引用它们的名字(行{4}和行{5}),就可以执行getset 函数。

    _name并非真正的私有属性,我们仍然可以引用它。本书后面的章节还会谈到这一点。

    你可以访问https://goo.gl/SMRYsv执行上面的例子。

  3. 其他功能

    ES6还有其他一些功能,包括列表迭代器、类型数组、SetMapWeakSetWeakMap、模块、尾调用、Symbol,等等。本书的其他章节会介绍其中部分功能。

     更多关于ES6全部功能和规范的信息,请参考http://www.ecma-international.org/ecma-262/6.0/。