文章是翻译,主要是很多 ES6 的用法技巧以及最佳实践~
原文 https://github.com/DrkSephy/es6-cheatsheet
ES6 手册
这篇手册包含了 ES2015(ES6) 的使用小技巧、最佳实践以及可以给你每天的工作参考的代码片段。
内容列表
var 和 let/const 的比较
用块级作用域代替 IIFES
箭头函数
字符串
解构
模块
参数
类 Classes
Symbols
Maps
WeakMaps
Promises
Generators 生成器
Async Await
Getter/Setter 函数
var 和 let/const 的比较
除了 var ,我们现在还可以使用两个新的标示符来定义一个变量 —— let 和 const。和 var 不一样的是,let 和 const 不存在变量提升。
var snack = 'Meow Mix';function getFood(food) { if (food) { var snack = 'Friskies'; return snack; } return snack;}getFood(false); // undefined
当我们用 let 代替 var 的时候,观察会发生什么:
let snack = 'Meow Mix';function getFood(food) { if (food) { let snack = 'Friskies'; return snack; } return snack;}getFood(false); // 'Meow Mix'
当我们重构使用 var 的老代码的时候应该注意上面的变化。盲目地使用 let 替换 var 可能会出现出乎意料的情况。
注意: let 和 const 是块级作用域,因此在变量未被定义之前使用它会产生一个 ReferenceError。
console.log(x); // ReferenceError: x is not definedlet x = 'hi';
最佳实践: 在遗留代码中放弃使用 var 声明意味着需要很小心地重构;在新的项目代码中,使用 let 声明一个可以改变的变量,用 const 声明一个不能被重新赋值的变量。
用块级作用域代替 IIFES
函数立即执行表达式 的通常用途是创造一个内部的作用域,在 ES6 中,我们能够创造一个块级作用域而不仅限于函数作用域:
IIFES:
(function () { var food = 'Meow Mix';}());console.log(food); // Reference Error
使用 ES6 的块级作用域:
{ let food = 'Meow Mix';}console.log(food); // Reference Error
箭头函数
我们经常需要给回调函数维护一个词法作用域的上下文 this。
看看这个栗子:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name character; // Cannot read property 'name' of undefined });};
一个常用的解决办法是把 this 存在一个变量中:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name character; });};
我们也可以传递一个合适的 this 上下文:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name character; }, this);}
我们还可以绑定上下文:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name character; }.bind(this));};
使用 箭头函数,this 将不会受到影响,并且我们可以重写上面的函数:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map(character => this.name character);};
最佳实践:当你需要维护一个 this 上下文的时候使用 箭头函数。
在我们写一个函数的时候,箭头函数更加简洁并且可以很简单地返回一个值:
var squares = arr.map(function (x) { return x * x }); // Function Expression
const arr = [1, 2, 3, 4, 5];const squares = arr.map(x => x * x); // Arrow Function for terser implementation
最佳实践:尽可能使用箭头函数代替原来的写法。
字符串
在 ES6 中,标准库升级了很多,在这些变化中有许多新的对于字符串的函数,比如 .includes() 和 .repeat()。
.includes( )
var string = 'food';var substring = 'foo';console.log(string.indexOf(substring) > -1);
之前我们使用 indexOf() 函数的返回值是否 >-1 来判断字符串是否包含某些字符串,现在我们更简单地使用 .includes() 来返回一个布尔值来判断:
const string = 'food';const substring = 'foo';console.log(string.includes(substring)); // true
.repeat( )
function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join('');}
在 ES6 中,可以更简便地实现:
// String.repeat(numberOfRepetitions)'meow'.repeat(3); // 'meowmeowmeow'
模版字符串
使用 模版字符串 我们就可以不用对某些特殊自负进行转义处理了:
var text = 'This string contains \'double quotes\' which are escaped.';
let text = `This string contains 'double quotes' which don't need to be escaped anymore.`;
模版字符串 还支持插入,可以把变量值和字符串连接起来.
var name = 'Tiger';var age = 13;console.log('My cat is named ' name ' and is ' age ' years old.');
更简单:
const name = 'Tiger';const age = 13;console.log(`My cat is named ${name} and is ${age} years old.`);
在 ES5 中,需要换行时,需要这样:
var text = ( 'cat\n' 'dog\n' 'nickelodeon');
或者这样:
var text = [ 'cat', 'dog', 'nickelodeon'].join('\n');
模版字符串 可以支持换行并且不需要额外的处理:
let text = ( `catdognickelodeon`);
模版字符串 还支持表达式:
let today = new Date();let text = `The time and date is ${today.toLocaleString()}`;
解构
结构可以让我们用一个更简便的语法从一个数组或者对象(即使是深层的)中分离出来值,并存储他们。
结构数组
var arr = [1, 2, 3, 4];var a = arr[0];var b = arr[1];var c = arr[2];var d = arr[3];
let [a, b, c, d] = [1, 2, 3, 4];console.log(a); // 1console.log(b); // 2
结构对象
var luke = { occupation: 'jedi', father: 'anakin' };var occupation = luke.occupation; // 'jedi'var father = luke.father; // 'anakin'
let luke = { occupation: 'jedi', father: 'anakin' };let {occupation, father} = luke;console.log(occupation); // 'jedi'console.log(father); // 'anakin'
模块
在 ES6 之前,我们使用 Browserify 这样的库来创建客户端的模块化,在 node.js 中使用 require。
在 ES6 中,我们可以直接使用所有类型的模块化(AMD 和 CommonJS)。
使用 CommonJS 的出口
module.exports = 1;module.exports = { foo: 'bar' };module.exports = ['foo', 'bar'];module.exports = function bar () {};
使用 ES6 的出口
在 ES6 中我们可以暴漏多个值,使用 Exports:
export let name = 'David';export let age = 25;
或者暴露一个对象列表:
function sumTwo(a, b) { return a b;}function sumThree(a, b, c) { return a b c;}export { sumTwo, sumThree };
我们还可以暴露函数、对象和其他的值,通过简单地使用 export 这个关键字:
export function sumTwo(a, b) { return a b;}export function sumThree(a, b, c) { return a b c;}
最后,我们还可以绑定一个默认的输出:
function sumTwo(a, b) { return a b;}function sumThree(a, b, c) { return a b c;}let api = { sumTwo, sumThree};export default api;/* Which is the same as * export { api as default }; */
最佳实践:总是在模块的最后面使用 export default 方法,这可以让暴露的东西更加清晰并且可以节省时间去找出暴漏出来值的名字。尤其如此,在 CommonJS 中通常的实践就是暴露一个简单的值或者对象。坚持这种模式,可以让我们的代码更加可读,并且在 ES6 和 CommonJS 模块之间更好地兼容。
ES6 中的导入
在 ES6 中同样提供了多样的导入方式,我们可以这么导入一个整个文件:
import 'underscore';
需要着重注意的一点是简单的导入整个文件会在那个文件的顶部执行所有的代码
和 Python 中类似,我们可以命名导入的值:
import { sumTwo, sumThree } from 'math/addition';
我们还可以重命名导入:
import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers} from 'math/addition';
另外,我们可以导入所有的东西(整体加载):
import * as util from 'math/addition';
最后,我们可以从一个模块中导入一个值的列表:
import * as additionUtil from 'math/addition';const { sumTwo, sumThree } = additionUtil;
可以像这样导入默认绑定的输出:
import api from 'math/addition';// Same as: import { default as api } from 'math/addition';
虽然最好保持出口的简单,但如果需要的话我们有时可以混合默认的进口和混合进口。当我们这样出口的时候:
// foos.jsexport { foo as default, foo1, foo2 };
我们可以这样导入它们:
import foo, { foo1, foo2 } from 'foos';
当我们用 commonjs 的语法导入一个模块的出口时(比如 React),我们可以这样做:
import React from 'react';const { Component, PropTypes } = React;
还有更精简的写法:
import React, { Component, PropTypes } from 'react';
注意:导出的值是动态引用的,而不是拷贝。因此,在一个模块中改变一个变量的绑定将影响输出模块中的值。应该避免改变这些导出值的公共接口。(原文这里我觉得有误)
参数
在 ES5 中,在函数中我们需要各种操作去处理 默认参数、不定参数 和 重命名参数 等需求,在 ES6 中我们可以使用更简洁的语法完成这些需求:
默认参数
function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x y;}
ES6 中,函数的参数可以支持设置默认值:
function addTwoNumbers(x=0, y=0) { return x y;}
addTwoNumbers(2, 4); // 6addTwoNumbers(2); // 2addTwoNumbers(); // 0
rest 参数
在 ES5 中,我们需要这么处理不定参数:
function logArguments() { for (var i=0; i < arguments.length; i ) { console.log(arguments[i]); }}
使用 rest ,我们就可以处理不确定数目的参数:
function logArguments(...args) { for (let arg of args) { console.log(arg); }}
命名参数
在 ES5 中是使用配置对象的模式来处理命名参数,jQuery 中的使用:
function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || 'black';}
我们可以利用解构的一个函数的形参实现相同的功能:
function initializeCanvas( { height=600, width=400, lineStroke='black'}) { // Use variables height, width, lineStroke here }
如果我们想使整个值可选择,我们可以结构将一个空的对象:
function initializeCanvas( { height=600, width=400, lineStroke='black'} = {}) { // ... }
展开操作
在 ES5 中,我们可以 apply Math.max 方法来获得一个数组中的最大值:
Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
在 ES6 中,我们可以通过展开操作把一个数组的值作为参数传递给一个函数:
Math.max(...[-1, 100, 9001, -32]); // 9001
我们可以更简洁地使用这个语法来合并数组:
let cities = ['San Francisco', 'Los Angeles'];let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']
类 Classes
在 ES6 之前,我们通过构造函数来创造一个类,并且通过原型来扩展属性:
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender;}Person.prototype.incrementAge = function () { return this.age = 1;};
然后可以这样继承类:
function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby;}Personal.prototype = Object.create(Person.prototype);Personal.prototype.constructor = Personal;Personal.prototype.incrementAge = function () { Person.prototype.incrementAge.call(this); this.age = 20; console.log(this.age);};
在 ES6 中,提供了更多的语法糖,可以直接创造一个类:
class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age = 1; }}
使用 extends 关键字来继承一个类:
class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age = 20; console.log(this.age); }}
最佳实践:虽然使用 ES6 的语法创造类的时候,js引擎是如何实现类以及如何操作原型是令人费解的,但是未来对初学者来说这是一个好的开始,同时也可以让我们写更简洁的代码。
Symbols
Symbols 在 ES6 之前就已经存在,但是我们现在可以直接使用一个开发的接口了。Symbols 是不可改变并且是第一无二的,可以在任意哈希中作一个key。Symbol() 调用 Symbol() 或者 Symbol(description) 可以创造一个第一无二的符号,但是在全局是看不到的。Symbol() 的一个使用情况是给一个类或者命名空间打上补丁,但是可以确定的是你不会去更新它。比如,你想给 React.Component 类添加一个 refreshComponent 方法,但是可以确定的是你不会在之后更新这个方法:
const refreshComponent = Symbol();React.Component.prototype[refreshComponent] = () => { // do something}
Symbol.for(key)
Symbol.for(key) 同样会创造一个独一无二并且不可改变的 Symbol,但是它可以全局看到,两个相同的调用 Symbol.for(key) 会返回同一个 Symbol 类:
Symbol('foo') === Symbol('foo') // falseSymbol.for('foo') === Symbol('foo') // falseSymbol.for('foo') === Symbol.for('foo') // true
对于 Symbols 的普遍用法(尤其是Symbol.for(key))是为了协同性。它可以通过在一个第三方插件中已知的接口中对象中的参数中寻找用 Symbol 成员来实现,比如:
function reader(obj) { const specialRead = Symbol.for('specialRead'); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError('object cannot be read'); }}
在另一个库中:
const specialRead = Symbol.for('specialRead');class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; }}
原文:https://github.com/DrkSephy/es6-cheatsheet
译文:http://qiutc.me/post/es6-cheatsheet.html