这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片断,帮助你
完成日复一日的开发工做。javascript
除了var
之外,咱们如今多了两个新的标识符来声明变量的存储,它们就是let
和const
。
不一样于var
,let
和const
语句不会形成声明提高。
一个 var
的例子:html
var snack = 'Meow Mix'; function getFood(food) { if (food) { var snack = 'Friskies'; return snack; } return snack; } getFood(false); // undefined
让咱们再观察下面语句中,使用 let
替换了 var
后的表现:java
let snack = 'Meow Mix'; function getFood(food) { if (food) { let snack = 'Friskies'; return snack; } return snack; } getFood(false); // 'Meow Mix'
当咱们重构使用 var
的老代码时,必定要注意这种变化。盲目使用 let
替换 var
后可能会致使预期意外的结果。node
注意:let
和const
是块级做用域语句。因此在语句块之外引用这些变量时,会形成引用错误ReferenceError
。
console.log(x); let x = 'hi'; // ReferenceError: x is not defined
最佳实践: 在重构老代码时,var
声明须要格外的注意。在建立一个新项目时,使用let
声明一个变量,使用const
来声明一个不可改变的常量。
咱们以往建立一个 当即执行函数 时,通常是在函数最外层包裹一层括号。
ES6支持块级做用域(更贴近其余语言),咱们如今能够经过建立一个代码块(Block)来实现,没必要经过建立一个函数来实现,react
(function () { var food = 'Meow Mix'; }()); console.log(food); // Reference Error
使用支持块级做用域的ES6的版本:git
{ let food = 'Meow Mix'; } console.log(food); // Reference Error
一些时候,咱们在函数嵌套中须要访问上下文中的 this
。好比下面的例子:es6
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
保存在一个变量里:github
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
经过属性传进去:json
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); };
还能够直接使用 bind
:api
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()
方法。
var string = 'food'; var substring = 'foo'; console.log(string.indexOf(substring) > -1);
如今,咱们可使用 .inclues()
方法,替代以往判断内容 > -1
的方式。.includes()
方法会极简地返回一个布尔值结果。
const string = 'food'; const substring = 'foo'; console.log(string.includes(substring)); // true
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 = ( `cat dog nickelodeon` );
字符串模板字面量 内部可使用表达式,像这样:
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); // 1 console.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这些模块了。
module.exports = 1; module.exports = { foo: 'bar' }; module.exports = ['foo', 'bar']; module.exports = function bar () {};
在ES6中,提供了多种设置模块出口的方式,好比咱们要导出一个变量,那么使用 变量名 :
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;
最佳实践:老是在模块的 最后 使用export default
方法。
它让模块的出口更清晰明了,节省了阅读整个模块来寻找出口的时间。
更多的是,在大量CommonJS模块中,通用的习惯是设置一个出口值或者出口对象。
最受这个规则,可让咱们的代码更易读,且更方便的联合使用CommonJS和ES6模块。
ES6提供了好几种模块的导入方式。咱们能够单独引入一个文件:
import 'underscore';
这里须要注意的是, 整个文件的引入方式会执行该文件内的最上层代码。
就像Python同样,咱们还能够命名引用:
import { sumTwo, sumThree } from 'math/addition';
咱们甚至可使用 as
给这些模块重命名:
import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from 'math/addition';
另外,咱们能 引入全部的东西(原文:import all the things) (也称为命名空间引入)
import * as util from 'math/addition';
最后,咱们能能够从一个模块的众多值中引入一个列表:
import * as additionUtil from 'math/addtion'; const { sumTwo, sumThree } = additionUtil;
像这样引用默认对象:
import api from 'math/addition'; // Same as: import { default as api } from 'math/addition';
咱们建议一个模块导出的值应该越简洁越好,不过有时候有必要的话命名引用和默认引用能够混着用。若是一个模块是这样导出的:
// foos.js export { 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';
注意:被导出的值是被 绑定的(原文:bingdings),而不是引用。
因此,改变一个模块中的值的话,会影响其余引用本模块的代码,必定要避免此种改动发生。
在ES5中,许多种方法来处理函数的 参数默认值(default values),参数数量(indefinite arguments),参数命名(named parameters)。
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); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0
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'; }
咱们能够利用上面提到的新特性 解构 ,来完成与上面一样功能的函数:
We can achieve the same functionality using destructuring as a formal parameter
to a function:
function initializeCanvas( { height=600, width=400, lineStroke='black'}) { // ... } // Use variables height, width, lineStroke here
若是咱们须要把这个参数变为可选的,那么只要把该参数解构为一个空对象就行了:
function initializeCanvas( { height=600, width=400, lineStroke='black'} = {}) { // ... }
咱们能够利用展开操做符(Spread Operator)来把一组数组的值,看成参数传入:
Math.max(...[-1, 100, 9001, -32]); // 9001
在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 () { return Person.prototype.incrementAge.call(this) += 20; };
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新的类语法把咱们从晦涩难懂的实现和原型操做中解救出来,这是个很是适合初学者的功能,并且能让咱们写出更干净整洁的代码。
符号(Symbols)在ES6版本以前就已经存在了,但如今咱们拥有一个公共的接口来直接使用它们。
Symbols对象是一旦建立就不能够被更改的(immutable)并且能被用作hash数据类型中的键。
调用 Symbol()
或者 Symbol(描述文本)
会建立一个惟一的、在全局中不能够访问的符号对象。
一个 Symbol()
的应用场景是:在本身的项目中使用第三方代码库,且你须要给他们的对象或者命名空间打补丁代码,又不想改动或升级第三方原有代码的时候。
举个例子,若是你想给 React.Component
这个类添加一个 refreshComponent
方法,但又肯定不了这个方法会不会在下个版本中加入,你能够这么作:
const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something }
使用 Symbol.for(key)
也是会建立一个不可改变的Symbol对象,但区别于上面的建立方法,这个对象是在全局中能够被访问到的。
调用两次 Symbol.for(key)
会返回相同的Symbol实例。
提示:这并不一样于 Symbol(description)
。
Symbol('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol.for('foo') // true
一个Symbols经常使用的使用场景,是须要使用特别 Symbol.for(key)
方法来实现代码间的协做。
这能让你在你的代码中,查找包含已知的接口的第三方代码中Symbol成员。(译者:这句话好难翻。。。原文:This can be
achieved by having your code look for a Symbol member on object arguments from third parties that contain some known interface. )举个例子:
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; } }
注意:
Symbol.iterable
在ES6中像其余可枚举的对象,如数组,字符串,generators同样,当这个方法被调用时会激活一个枚举器并返回一个对象。
Maps 是一个Javascript中很重要(迫切须要)的数据结构。
在ES6以前,咱们建立一个 hash 一般是使用一个对象:
var map = new Object(); map[key1] = 'value1'; map[key2] = 'value2';
可是,这样的代码没法避免函数被特别的属性名覆盖的意外状况:
getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned'); TypeError: Property 'hasOwnProperty' is not a function
Maps 让咱们使用 set
,get
和 search
操做数据。
let map = new Map(); map.set('name', 'david'); map.get('name'); // david map.has('name'); // true
Maps最强大的地方在于咱们没必要只能使用字符串来作key了,如今可使用任何类型来看成key,并且key不会被强制类型转换为字符串。
let map = new Map([ ['name', 'david'], [true, 'false'], [1, 'one'], [{}, 'object'], [function () {}, 'function'] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function }
提示:当使用map.get()
判断值是否相等时,非基础类型好比一个函数或者对象,将不会正常工做。
有鉴于此,仍是建议使用字符串,布尔和数字类型的数据类型。
咱们还可使用 .entries()
方法来遍历整个map对象:
for (let [key, value] of map.entries()) { console.log(key, value); }
在ES5以前的版本,咱们为了存储私有数据,有好几种方法。像使用这种下划线命名约定:
class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } }
在一个开源项目中,命名规则很难维持得一直很好,这样常常会形成一些困扰。
此时,咱们能够选择使用WeakMaps来替代Maps来存储咱们的数据:
let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log('Midlife crisis'); } } }
使用WeakMaps来保存咱们私有数据的理由之一是不会暴露出属性名,就像下面的例子中的 Reflect.ownKeys()
:
const person = new Person(50); person.incrementAge(); // 'Midlife crisis' Reflect.ownKeys(person); // []
一个使用WeakMaps存储数据更实际的例子,就是有关于一个DOM元素和对该DOM元素(有污染)地操做:
let map = new WeakMap(); let el = document.getElementById('someElement'); // Store a weak reference to the element with a key map.set(el, 'reference'); // Access the value of the element let value = map.get(el); // 'reference' // Remove the reference el.parentNode.removeChild(el); el = null; value = map.get(el); // undefined
上面的例子中,一个对象被垃圾回收期给销毁了,WeakMaps会自动的把本身内部所对应的键值对数据同时销毁。
提示:结合这个例子,再考虑下jQuery是如何实现缓存带有引用的DOM元素这个功能的,使用了WeakMaps的话,当被缓存的DOM元素被移除的时,jQuery能够自动释放相应元素的内存。
一般状况下,在涉及DOM元素存储和缓存的状况下,使用WeakMaps是很是适合的。
Promises让咱们让咱们多缩进难看的代码(回调地狱):
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); });
写成这样:
func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 });
在ES6以前,咱们使用bluebird 或者
Q。如今咱们有了原生版本的 Promises:
new Promise((resolve, reject) => reject(new Error('Failed to fulfill Promise'))) .catch(reason => console.log(reason));
这里有两个处理函数,resolve(当Promise执行成功完毕时调用的回调函数) 和 reject (当Promise执行不接受时调用的回调函数)
Promises的好处:大量嵌套错误回调函数会使代码变得难以阅读理解。
使用了Promises,咱们可让咱们代码变得更易读,组织起来更合理。
此外,Promise处理后的值,不管是解决仍是拒绝的结果值,都是不可改变的。
下面是一些使用Promises的实际例子:
var fetchJSON = function(url) { return new Promise((resolve, reject) => { $.getJSON(url) .done((json) => resolve(json)) .fail((xhr, status, err) => reject(status + err.message)); }); };
咱们还可使用 Promise.all()
来异步的 并行 处理一个数组的数据。
var urls = [ 'http://www.api.com/items/1234', 'http://www.api.com/items/4567' ]; var urlPromises = urls.map(fetchJSON); Promise.all(urlPromises) .then(function (results) { results.forEach(function (data) { }); }) .catch(function (err) { console.log('Failed: ', err); });
就像Promises如何让咱们避免回调地狱同样,Generators也可使咱们的代码扁平化,同时给予咱们开发者像开发同步代码同样的感受来写异步代码。Generators本质上是一种支持的函数,随后返回表达式的值。
Generators其实是支持暂停运行,随后根据上一步的返回值再继续运行的一种函数。
下面代码是一个使用generators函数的简单例子:
function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: false } console.log(generator.next()); // { value: 4, done: false }
就像上面的例子,当next运行时,它会把咱们的generator向前“推进”,同时执行新的表达式。
咱们能利用Generators来像书写同步代码同样书写异步代码。
// Hiding asynchronousity with Generators function request(url) { getJSON(url, function(response) { generator.next(response); }); }
这里咱们写个generator函数将要返回咱们的数据:
function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); }
借助于 yield
,咱们能够保证 entry1
确实拿到数据并转换后再赋值给 data1
。
当咱们使用generators来像书写同步代码同样书写咱们的异步代码逻辑时,没有一种清晰简单的方式来处理期间可能会产生的错误或者异常。在这种状况下,咱们能够在咱们的generator中引入Promises来处理,就像下面这样:
function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); }
咱们再写一个函数,其中使用 next
来步进咱们的generator的同事,再利用咱们上面的 request
方法来产生(yield)一个Promise。
function iterateGenerator(gen) { var generator = gen(); var ret; (function iterate(val) { ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); }
在Generator中引入了Promises后,咱们就能够经过Promise的 .catch
和 reject
来捕捉和处理错误了。
使用了咱们新版的Generator后,新版的调用就像老版本同样简单可读(译者注:有微调):
iterateGenerator(function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); });
在使用Generator后,咱们能够重用咱们的老版本代码实现,以此展现了Generator的力量。
当使用Generators和Promises后,咱们能够像书写同步代码同样书写异步代码的同时优雅地解决了错误处理问题。
此后,咱们实际上能够开始利用更简单的一种方式了,它就是async-await。
async await
随着ES2016版本就要发布了,它给咱们提供了一种更轻松的、更简单的能够替代的实现上面 Generators 配合 Promises 组合代码的一种编码方式,让咱们来看看例子:
var request = require('request'); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // NOT undefined! } main();
不断更新、修复...