该篇为翻译文章,原文地址为:https://css-tricks.com/lets-learn-es2015/

这篇文章的作者是Ryan Christiani,Ryan是HackerYou的讲师,他有一个系列的视频课程叫做一起学习ES6。Ryan把ES6的一些东西整理并做出一系列的教程,我觉得这非常吊,因为CSS-Tricks中关于这方面的内容相对来说是比较少的。

ES2015?

在2015年的六月,JavaScript的最重要的一些新特性被确定下来,官方称这次的升级为”ECMAScript 2015″,有时也被称
为”ES6″,现在更多的是被称为”ES2015″.其实”ES2015″是ES这些年发展变化到现在的一个结晶。

如果按照这样来命名的话,那么将来还会有ECMAScript 2016,可能会被称为”ES7″或”ES2016″,也就是说,ECMAScript会一年更新一个版本。

大部分浏览器开始支持ES2015的一些新特性,但它们的支持其实也是有一些差异化的。你可以通过这张表看到当前一些浏览器对ES2015的支持情况。

好在我们有像Babel这样的工具,有了这个工具,我们就可以放心大胆的书写ES2015了,因为它会把一些ES2015的新语法转自动转换成JS早期的一些旧语法以获得更好的浏览器兼容性。如果你用过SassLESS这种CSS预处理语言的话,那么你就不会迷惑,因为,它们的工作原理是差不多的,Sass和LESS最终会被编译成浏览器能识别的CSS。

概述

这篇文章将给大家介绍一些现在可以使用的新特性。

我们将介绍一些新的关键字,比如letconst。如何使用”模板字符串”更方便的拼接字符串。新的”箭头函数”语法。”
展开运算符”还有”剩余参数”。
以下是目录:

  1. letconst
  2. 模板字符串-Template Literals
  3. 箭头函数-Arrow Functions
  4. 展开运算符-Spread Operators
  5. 剩余参数-Rest Parameters

let 和 const

letconst是ES2015中的两个新关键字。它们都是用来定义变量的,但是它们和var的最大区别就是letconst是用来定义块级域的本地变量

使用var关键字声明的变量的作用域是函数,也就是说它只在自身所处函以及数函数内的函数当中起作用。如果使用var在所有函数外面声明一个变量,那么这个变量将是全局的。

这里有一个很常见的问题,就是在for循环里使用var:

for (var i = 0; i < 10; i++) {
    console.log(i);
}
console.log(i); // 这句话将会打印 10;

如果你运行上面的代码,你会发现在for循环的外面也可以找到变量i

如果你想使用letconst,则必须让你的JS开启”严格模式-strict mode”。通过在JS文件的顶部添加'use strict'即可开启该模式。

“严格模式”会将JavaScript的一些坑直接变成明显的错误告诉你。它还会禁止你使用一些可能在将来会成为关键字的单词。比如,在”严格模式”中,你不可以使用let作为变量名。更多关于”严格模式”的知识,可以到MDN进行查阅。

(作者提醒:如果你在使用Babel,你不用手动添加”use strict”,因为它会自动将其添加到代码当中。)

JavaScript中的”块”指的是包含在{ }中间的代码块。所以当我们说到块级作用域的时候,指的就是定义在{ }之间的变量只存在于{ },也就是说,在{ }外面,你就不应该能找到{ }里面的变量。

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定义了一个变量,那么它以后则不能再被更改(其实可以理解为使用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会无形之中提高代码运行的效率!

模板字符串

我们现在或之前在JS中定义字符串的时候可以使用' '" ",而在ES2015当中,又多了一种方法,并且这种方法有一些很强大的功能。
首先来看一个例子,我们如果想把变量和字符串拼起来的话,老方法是使用+号:

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);

这样的话,我们可以在JS中很方便的写一些HTML

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;

这里的返回是一个隐式的。如果函数只接收一个参数的话,那么( )也可以省略。

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指向的则是它父级作用域(箭头函数则会捕获其所在上下文的 this 值,作为自己的 this 值)。

const person = {
  firstName: "Ryan",
  sayName: () => {
    return this.firstName; 
  }
}
console.log(person.sayName()); // undefined

在上面的代码当中,我们把之前的匿名函数改成了”箭头”式写法后再运行,将返回undefined!this指向的已不再是person,而是window对象,而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并没有和任何对象绑定(这里是指在严格模式中,而在非严格模式中,this将指向window)。但是,如果我们把forEach()里的函数改成箭头形式就会得到我们想要要结果!

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对象!

展开运算符

有时候,我们想很方便的对数组做一些操作,但JS内置的一些方法实在有限。比如,我们想得到一个数组当中最大的一个数。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()方法接收的第一个参数是改变this的指向,这个例子当中我们不需要考虑this,所以传入了null。第二个参数接收的是一个数组,我们把numbers数组传了过去。这种写法可以不是特别好理解,那么有没有更简单明了的方法呢?请继续往下看。

加入展开运算符

ES2015新加入了一种叫做”展开运算符”,语法是这样:

...numbers

“展开运算符”,顾名思义,作用就是”展开”,说的再清楚一点,它可以把数组给拆开来!它可以在适当的时候将数组拆开来。例如,我们可以将上面的例子改成这样。

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),但是这段代码很难让人理解。arguments是一个类数组的对象,但它不是一个真正的数组。如果我们想对arguments使用数组的一些方法的话,像reduce(),我们则需要做这样一些特殊操作。

JavaScript的基础是一堆对象组成的。所有的对象都会继承父对象的一些方法和属性,它们是通过.prototype属性来进行继承的。我们可以借用数组当中的slice()方法,然后传入arguments来创建一个真正的数组,通过数组的prototype下的slice()调用call()方法来创建一个新数组。还有很多很多其他的方法。

使用”剩余参数”

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);
}

上面的函数中,第二个参数是”剩余参数”,是一个可以有N多的参数的数组,我们可以这样调用:

const num = [2,1,4,5,6];
console.log(multi(2,...num));//[4, 2, 8, 10, 12]

结语

在ES2015里还有很多新特性是这篇文章没提及到的,但这篇文章介绍的一些新语法和新特性将是一个很好的开始!如果想学习更多关于ES2015的知识,可以查看YouTube一起学习ES6系列视频,另外,在 letslearnes6.com也有一本我写的关于ES6的书。