本节,我们将演示如何使用ES6的一些新功能。这既对日常的JavaScript编码有用,也可以简化本书后面章节中的例子。我们将介绍以下功能。
let
和const
模板字面量
解构
展开操作符
箭头函数:
=>
类
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 PI
或let 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
函数。可以执行如下代码来传入参数x
、y
和z
:
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执行上面的例子。
继承
除了新的声明类的方式,类的继承也有简化的语法。我们看一个例子:
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执行上面的例子。
使用属性存取器
使用新的类语法也可以为属性创建存取器函数。虽然不像其他面向对象语言(封装概念),类的属性不是私有的,但最好还是遵循一种命名模式。
下面的例子是一个声明了
get
和set
函数的类: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);
要声明
get
和set
函数,只需要在我们要暴露和使用的函数名前面加上get
或set
关键字(行{2}
和行{3}
)。我们可以用相同的名字声明类属性,或者在属性名前面加下划线(行{1}
),让这个属性看起来像是私有的。然后,只要像普通的属性一样,引用它们的名字(行
{4}
和行{5}
),就可以执行get
和set
函数。_name
并非真正的私有属性,我们仍然可以引用它。本书后面的章节还会谈到这一点。你可以访问https://goo.gl/SMRYsv执行上面的例子。
其他功能
ES6还有其他一些功能,包括列表迭代器、类型数组、
Set
、Map
、WeakSet
、WeakMap
、模块、尾调用、Symbol
,等等。本书的其他章节会介绍其中部分功能。更多关于ES6全部功能和规范的信息,请参考http://www.ecma-international.org/ecma-262/6.0/。