作为开发者,我们就要不断的学习,学习ES6,不知他的新特性怎么行?ES6 是 Javascript 的下一个版本,它有很多很棒的新特性。这些特性复杂程度各不相同,但对于简单的脚本和复杂的应用都很有用。在本文中,我们将讨论一些精心挑选的 ES6 特性,这些特性可以用于你日常的 Javascript 编码中。
请注意,当前浏览器已经全面展开对这些 ES6 新特性的支持,尽管目前的支持程度还有所差异。如果你需要支持一些缺少很多 ES6 特性的旧版浏览器,我将介绍一些当前可以帮助你开始使用 ES6 的解决方案。
变量
LET
你习惯于用 var 声明变量。现在你也可以用 let 了。两者微妙的差别在于作用域。var 声明的变量作用域为包围它的函数,而 let 声明的变量作用域仅在它所在的块中。
JavaScript
1 2 3 4 |
if(true) { let x = 1; } console.log(x); // undefined |
这样使得代码更加干净,减少滞留的变量。看看以下经典的数组遍历:
JavaScript
1 2 3 4 5 |
for(let i = 0, l = list.length; i < l; i++) { // do something with list[i] }
console.log(i); // undefined |
举个例子,通常情况下,我们在同一作用域里使用变量 j 来完成另一个遍历。但是,现在有了 let,可以安全地再一次声明 i 变量。因为它只在被声明的块中有效。
CONST
还有另一个用于声明块作用域变量的方法。使用 const,你可以声明一个值的只读引用。必须直接给一个变量赋值。如果尝试修改变量或者没有立即给变量赋值,都将报错:
JavaScript
1 2 3 |
const MY_CONSTANT = 1; MY_CONSTANT = 2 // Error const SOME_CONST; // Error |
注意,对象的属性或数组成员还是可以改变的。
JavaScript
1 2 |
const MY_OBJECT = {some: 1}; MY_OBJECT.some = 'body'; // Cool |
箭头函数
箭头函数为 Javascript 语言增色不少。它使得代码更简洁。我们早早地在本文中介绍箭头函数,这样就可以在后面的示例中加以利用了。以下代码片段是箭头函数和我们熟悉的 ES5 版本的写法:
JavaScript
1 2 3 4 5 6 7 8 |
let books = [{title: 'X', price: 10}, {title: 'Y', price: 15}];
let titles = books.map( item => item.title );
// ES5 equivalent: var titles = books.map(function(item) { return item.title; }); |
如果我们观察箭头函数的语法,会发现其中并没有出现 function 关键词。只保留零或多个参数,“胖箭头”(=>)和函数表达式。return 声明被隐式加入。
带有零或多个参数时,必须使用括号:
JavaScript
1 2 3 4 5 |
// No arguments books.map( () => 1 ); // [1, 1]
// Multiple arguments [1,2].map( (n, index) => n * index ); // [0, 2] |
如果需要写更多的逻辑或更多的空格,可以把函数表达式放在({...})块中。
JavaScript
1 2 3 4 |
let result = [1, 2, 3, 4, 5].map( n => { n = n % 3; return n; }); |
箭头函数不单只是为了输入更少的字符,它们的表现也和一般函数不同。它继承了当前上下文的 this 和 arguments。这就意味着,你可以避免写 var that = this 这样丑陋的代码,也不需要把函数绑定到正确的上下文了。举例如下(注意对比 this.title 和 ES5 版本的 that.title 的不同):
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
let book = { title: 'X', sellers: ['A', 'B'], printSellers() { this.sellers.forEach(seller =>console.log(seller + ' sells ' + this.title)); } }
// ES5 equivalent: var book = { title: 'X', sellers: ['A', 'B'], printSellers: function() { var that = this; this.sellers.forEach(function(seller) { console.log(seller + ' sells ' + that.title) }) } } |
字符串
方法
几个方便的方法被添加到 String 的原型中。其中大多数用于简化需要用 indexOf() 方法来解决的问题的复杂度,并达到同样的效果:
JavaScript
1 2 3 |
'my string'.startsWith('my'); //true 'my string'.endsWith('my'); // false 'my string'.includes('str'); // true |
很简单但是很有效。添加了另外一个方便的用于创建重复字符串的方法:
JavaScript
1 |
'my '.repeat(3); // 'my my my ' |
模板字符串
模板字符串提供一个简洁的方式来实现字符串插值。你可能已经对这种语法很熟悉了;它基于美元符号和花括号 ${..}。模板字符串置于引号之中。以下是快速示例:
JavaScript
1 2 3 4 5 6 7 8 9 10 11 |
let name = 'John', apples = 5, pears = 7, bananas = function() { return 3; }
console.log(`This is ${name}.`);
console.log(`He carries ${apples} apples, ${pears} pears, and ${bananas()} bananas.`);
// ES5 equivalent: console.log('He carries ' + apples + ' apples, ' + pears + ' pears, and ' + bananas() +' bananas.'); |
以上形式对比 ES5 仅仅是便于字符串拼接。事实上,模板字符串还可以用于多行字符串。记住空格也是字符串的一部分。
JavaScript
1 2 3 4 5 6 7 8 9 10 |
let x = `1... 2... 3 lines long!`; // Yay
// ES5 equivalents: var x = "1...n" + "2...n" + "3 lines long!";
var x = "1...n2...n3 lines long!"; |
数组
Array 对象增加了一些新的静态方法,Array 原型上也增加了一些新方法。
首先, Array.from 从类数组和可遍历对象中创建 Array 的实例。类数组对象示例包括:
函数中的 arguments;
由 document.getElementByTagName() 返回的 nodeList;
新增加的 Map 和 Set 数据结构。
JavaScript
1 2 3 4 5 6 7 8 |
let itemElements = document.querySelectorAll('.items'); let items = Array.from(itemElements); items.forEach(function(element) { console.log(element.nodeType) });
// A workaround often used in ES5: let items = Array.prototype.slice.call(itemElements); |
在上面的例子中,可以看到 items 数组拥有 forEach 方法,该方法是 itemElements 集合所不具备的。
Array.from 的一个有趣的特性是它的第二个可选参数mapFunction 。该参数允许你通过一次单独调用创建一个新的映射数组。
JavaScript
1 2 |
let navElements = document.querySelectorAll('nav li'); let navTitles = Array.from(navElements, el => el.textContent); |
然后,我们可以使用 Array.of 方法,该方法的表现很像 Array 构造函数。它适合只传递一个参数的情况。因此 Array.of 是 new Array() 的更优选择。然而,更多的情况下,你会想使用数组字面量。
JavaScript
1 2 3 |
let x = new Array(3); // [undefined, undefined, undefined] let y = Array.of(8); // [8] let z = [1, 2, 3]; // Array literal |
最后但同样重要的,有几个方法被添加到 Array 的原型上。我想 find 方法将会很受 Javascript 开发者欢迎。
find 返回回调返回 true 的第一个元素。
findIndex 返回回调函数返回 true的第一个元素的下标。
fill 用所给参数“覆盖”数组的元素。
JavaScript
1 2 3 4 5 6 |
[5, 1, 10, 8].find(n => n === 10) // 10
[5, 1, 10, 8].findIndex(n => n === 10) // 2
[0, 0, 0].fill(7) // [7, 7, 7] [0, 0, 0, 0, 0].fill(7, 1, 3) // [0, 7, 7, 7, 0] |
Math
Math 对象新增了几个方法。
Math.sign 返回数字的符号,结果为 1、-1 或 0。
Math.trunc 返回无小数位的数字。
Math.cbrt 返回数字的立方根。
JavaScript
1 2 3 4 5 6 7 |
Math.sign(5); // 1 Math.sign(-9); // -1
Math.trunc(5.9); // 5 Math.trunc(5.123); // 5
Math.cbrt(64); // 4 |
扩展操作符
扩展操作符(...)这个语法用于特定地方扩展元素非常方便,例如函数调用中的参数。让你了解它们用途的最好方法就是举例子了。
首先,我们看看如何在一个另数组中扩展一个数组的元素。
JavaScript
1 2 3 4 5 6 7 8 |
let values = [1, 2, 4]; let some = [...values, 8]; // [1, 2, 4, 8] let more = [...values, 8, ...values]; // [1, 2, 4, 8, 1, 2, 4]
// ES5 equivalent: let values = [1, 2, 4]; // Iterate, push, sweat, repeat... // Iterate, push, sweat, repeat... |
扩展语法在传参数调用函数时也非常有用:
JavaScript
1 2 3 4 5 6 7 8 9 10 |
let values = [1, 2, 4];
doSomething(...values);
function doSomething(x, y, z) { // x = 1, y = 2, z = 4 }
// ES5 equivalent: doSomething.apply(null, values); |
正如你所看到的,该语法让我们免去通常使用 fn.apply() 的麻烦。它非常灵活,因为扩展操作符可以用在参数列表中的任意位置。这意味着以下调用方式会产生一样的结果:
JavaScript
1 2 |
let values = [2, 4]; doSomething(1, ...values); |
我们已经把扩展操作符应用在数组和参数中。事实上,它可以用在所有的可遍历对象中,例如一个 NodeList:
JavaScript
1 2 3 4 5 |
let form = document.querySelector('#my-form'), inputs = form.querySelectorAll('input'), selects = form.querySelectorAll('select');
let allTheThings = [form, ...inputs, ...selects]; |
现在, allTheThings 是一个包含 <form> 节点、 <input> 子节点和 <select> 子节点的二维数组。
解构
解构提供了一个方便地从对象或数组中提取数据的方法。对于初学者,请看以下数组示例:
JavaScript
1 2 3 4 5 6 |
let [x, y] = [1, 2]; // x = 1, y = 2
// ES5 equivalent: var arr = [1, 2]; var x = arr[0]; var y = arr[1]; |
使用这个语法,可以一次性给多个变量赋值。一个很好的附加用处是可以很简单地交换变量值:
JavaScript
1 2 3 4 |
let x = 1, y = 2;
[x, y] = [y, x]; // x = 2, y = 1 |
解构也可以用于对象。注意对象中必须存在对应的键:
JavaScript
1 2 |
let obj = {x: 1, y: 2}; let {x, y} = obj; // x = 1, y = 2 |
你也可以使用该机制来修改变量名:
JavaScript
1 2 |
let obj = {x: 1, y: 2}; let {x: a, y: b} = obj; // a = 1, b = 2 |
另一个有趣的模式是模拟多个返回值:
JavaScript
1 2 3 4 5 |
function doSomething() { return [1, 2] }
let [x, y] = doSomething(); // x = 1, y = 2 |
解构可以用来为参数对象赋默认值。通过对象字面量,可以模拟命名参数:
JavaScript
1 2 3 4 |
function doSomething({y = 1, z = 0}) { console.log(y, z); } doSomething({y: 2}); |
参数
默认值
在 ES6 中,可以定义函数的参数默认值。语法如下:
JavaScript
1 2 3 4 5 6 7 |
function doSomething(x, y = 2) { return x * y; }
doSomething(5); // 10 doSomething(5, undefined); // 10 doSomething(5, 3); // 15 |
看起来很简洁,对吧? 我肯定你之前在 ES5 中曾经需要给某些参数赋默认值:
JavaScript
1 2 3 4 |
function doSomething(x, y) { y = y === undefined ? 2 : y; return x * y; } |
传递 undefined 或不传参数时都会触发参数使用默认值。
REST参数
我们已经学习了省略号操作符。剩余参数和它很类似。它同样是使用 ... 语法,允许你把末尾的参数保存在数组中:
JavaScript
1 2 3 4 5 |
function doSomething(x, ...remaining) { return x * remaining.length; }
doSomething(5, 0, 0, 0); // 15 |
模块
模块当然是一个受欢迎的 Javascript 语言新功能。我想仅仅是这个主要特性就值得我们投入到 ES6 中来。
当前任何重要的 Javascript 项目都使用某种模块系统 —— 可能是“展示模块模式”或其他 AMD 或 CommonJS 扩展形式的东西。然而,浏览器并没有任何模块系统特性。为了实现 AMD 或 CommonJS,你通常需要一个构建步骤或加载器。解决这个问题的工具包括 RequireJS、Browserify 和 WebPack。
ES6 规范包含模块化的新语法和加载器。如果你未来想使用模块,应该使用这个语法。现代构建工具支持这种形式(可能通过插件),所以你可以放心使用。(不用担心 —— 我们将在后面的“转译”章节中讨论)
在 ES6 的模块语法中。模块设计围绕 export 和 import 关键词。现在让我们看一个包含两个模块的例子:
JavaScript
1 2 3 4 5 6 7 8 9 10 |
// lib/math.js
export function sum(x, y) { return x + y; } export var pi = 3.141593; // app.js
import { sum, pi } from "lib/math"; console.log('2π = ' + sum(pi, pi)); |
正如你所见,可以存在多个 export 声明。每一个都要明确地指明输出值的类型(本例中的function 和 var)。
本例中的 import 声明使用一种语法(类似解构)来明确定义被导入的内容。可以使用 * 通配符,结合 as 关键词给模块提供一个本地名称,把模块当成一个整体导入。
JavaScript
1 2 3 4 |
// app.js
import * as math from "lib/math"; console.log('2π = ' + math.sum(math.pi, math.pi)); |
模块系统有一个 default 输出。它可以是一个函数。只需要提供一个本地名称就可以导入这个默认值(即无解构):
JavaScript
1 2 3 4 5 6 7 8 9 10 |
// lib/my-fn.js
export default function() { console.log('echo echo'); }
// app.js
import doSomething from 'lib/my-fn'; doSomething(); |
请注意 import 声明是同步的,但是模块代码需在所有依赖加载完后才会运行。
类
类是 ES6 中备受热议的一个特性。一部分人认为它不符合 Javascript 的原型特性,另一部分人认为类可以降低从其他语言转过来的入门门槛,并帮助人们构建大规模应用。不管怎样,它是 ES6 的一部分。这里我们快速介绍一下。
类的创建围绕 class 和 constructor 关键词。以下是个简短的示例:
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 |
class Vehicle { constructor(name) { this.name = name; this.kind = 'vehicle'; } getName() { return this.name; } }
// Create an instance let myVehicle = new Vehicle('rocky'); |
注意类的定义不是一般的对象,因此,类的成员间没有逗号。
创造一个类的对象时,需要使用 new 关键词。继承一个基类时,使用 extends:
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 |
class Car extends Vehicle { constructor(name) { super(name); this.kind = 'car' } }
let myCar = new Car('bumpy');
myCar.getName(); // 'bumpy' myCar instanceof Car; // true myCar instanceof Vehicle; //true |
从衍生类中,你可以使用从任何构造函数或方法中使用 super 来获取它的基类:
使用 super() 调用父类构造函数。
调用其它成员,举个例子,使用 super.getName() 。
记号
记号是一个新的原生数据类型,像 Number 和 String 一样。你可以使用记号为对象属性创建唯一标识或创建唯一的常量。
JavaScript
1 2 3 4 |
const MY_CONSTANT = Symbol();
let obj = {}; obj[MY_CONSTANT] = 1; |
注意通过记号产生的键值对不能通过 Object.getOwnPropertyNames() 获得,在 for...in 遍历、 Object.keys() 、JSON.stringify() 中均不可见。这是与基于字符串的键相反的。你可以通过 Object.getOwnPropertySymbols() 获取一个对象的记号数组。
记号与 const 配合很合适,因为它们都有不可改变的特性。
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const CHINESE = Symbol(); const ENGLISH = Symbol(); const SPANISH = Symbol();
switch(language) { case CHINESE: // break; case ENGLISH: // break; case SPANISH: // break; default: // break; } |
你可以为 symbol 添加描述。虽然不可以通过描述获取 symbol,但是可用于代码调试。
JavaScript
1 2 3 4 5 6 |
const CONST_1 = Symbol('my symbol'); const CONST_2 = Symbol('my symbol');
typeof CONST_1 === 'symbol'; // true
CONST_1 === CONST_2; // false |
转译
我们现在可以用 ES6 来写代码了。正如介绍中提到的,浏览器对 ES6 特性的支持尚不广泛,且各浏览器也各不相同。很有可能你写的的代码在用户的浏览器中不能完全解析。这就是我们为什么需要把代码转换成能在当前的任何浏览器中良好运行的旧版本 Javascript(ES5) 。这种转换通常称为“转译”。我们必须在应用中这么做,直到所有我们想兼容的浏览器都能运行 ES6 为止。
原文来自:伯乐在线