ES6特性整理

es6-特性

这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片段,来帮助小白来学习,我也是从github 上“拿来的”,所以要想学好前端,勤逛github.

var let / const

除了 var 以外,我们现在多了两个新的标识符来声明变量的存储,它们就是 let 和 const。 
不同于 var ,let 和 const 语句不会造成声明提升。

一个 var 的例子:

var snack = 'Meow Mix';

function getFood(food) {
    if (food) {
        var snack = 'Friskies';
        return snack;
    }
    return snack;
}

getFood(false); // undefined
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

让我们再观察下面语句中,使用 let 替换了 var 后的表现:

let snack = 'Meow Mix';

function getFood(food) {
    if (food) {
        let snack = 'Friskies';
        return snack;
    }
    return snack;
}

getFood(false); // 'Meow Mix'
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

当我们重构使用 var 的老代码时,一定要注意这种变化。盲目使用 let 替换 var 后可能会导致预期意外的结果。

注意let 和 const 是块级作用域语句。所以在语句块以外引用这些变量时,会造成引用错误 ReferenceError

console.log(x);

let x = 'hi'; // ReferenceError: x is not defined
 
 
  • 1
  • 2
  • 3

最佳实践: 在重构老代码时,var 声明需要格外的注意。在创建一个新项目时,使用 let 声明一个变量,使用 const 来声明一个不可改变的常量。


Replacing IIFEs with Blocks

我们以往创建一个 立即执行函数 时,一般是在函数最外层包裹一层括号。 
ES6支持块级作用域(更贴近其他语言),我们现在可以通过创建一个代码块(Block)来实现,不必通过创建一个函数来实现,

(function () {
    var food = 'Meow Mix';
}());

console.log(food); // Reference Error
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

使用支持块级作用域的ES6的版本:

{
    let food = 'Meow Mix';
}

console.log(food); // Reference Error
 
 
  • 1
  • 2
  • 3
  • 4
  • 5


Arrow Functions

一些时候,我们在函数嵌套中需要访问上下文中的 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
    });
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

一种通用的方式是把上下文中的 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;
    });
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

我们也可以把 this 通过属性传进去:

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character;
    }, this);
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

还可以直接使用 bind

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character;
    }.bind(this));
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用 箭头函数this 的值不用我们再做如上几段代码的特殊处理,直接使用即可。 
上面的代码可以重写为下面这样:

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(character => this.name + character);
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最佳实践:使用箭头函数,再也不用考虑 this 的问题了。

当我们编写只返回一个表达式值的简单函数时,也可以使用箭头函数,如下:

var squares = arr.map(function (x) { return x * x }); // Function Expression
 
 
  • 1
const arr = [1, 2, 3, 4, 5];
const squares = arr.map(x => x * x); // Arrow Function for terser implementation
 
 
  • 1
  • 2

最佳实践:尽可能地多使用 箭头函数


Strings

在ES6中,标准库也被同样增强了,像字符串对象就新增了 .includes() 和 .repeat() 方法还有startsWith()、endsWith() 。

.includes( )

var string = 'food';
var substring = 'foo';

console.log(string.indexOf(substring) > -1);
 
 
  • 1
  • 2
  • 3
  • 4

现在,我们可以使用 .inclues() 方法,替代以往判断内容 > -1 的方式。 
.includes() 方法会极简地返回一个布尔值结果。

const string = 'food';
const substring = 'foo';

console.log(string.includes(substring)); // true
 
 
  • 1
  • 2
  • 3
  • 4

.repeat( )

function repeat(string, count) {
    var strings = [];
    while(strings.length < count) {
        strings.push(string);
    }
    return strings.join('');
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在ES6中,我们可以使用一个极简的方法来实现重复字符:

// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'
 
 
  • 1
  • 2

Template Literals 字符串模板

使用 字符串模板字面量,我可以在字符串中直接使用特殊字符,而不用转义。

var text = "This string contains \"double quotes\" which are escaped.";
 
 
  • 1
let text = `This string contains "double quotes" which don't need to be escaped anymore.`;
 
 
  • 1

字符串模板字面量 还支持直接插入变量,可以实现字符串与变量的直接连接输出。

var name = 'Tiger';
var age = 13;

console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
 
 
  • 1
  • 2
  • 3
  • 4

更简单的版本:

const name = 'Tiger';
const age = 13;

console.log(`My cat is named ${name} and is ${age} years old.`);
 
 
  • 1
  • 2
  • 3
  • 4

ES5中,我们要这样生成多行文本:

var text = (
    'cat\n' +
    'dog\n' +
    'nickelodeon'
);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

或者:

var text = [
    'cat',
    'dog',
    'nickelodeon'
].join('\n');
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

字符串模板字面量 让我们不必特别关注多行字符串中的换行转义符号,直接换行即可:

let text = ( `cat
dog
nickelodeon`
);
 
 
  • 1
  • 2
  • 3
  • 4

字符串模板字面量 内部可以使用表达式,像这样:

let today = new Date();
let text = `The time and date is ${today.toLocaleString()}`;
 
 
  • 1
  • 2


Destructuring

解构让我们可以使用非常便捷的语法,直接将数组或者对象中的值直接分别导出到多个变量中,

Destructuring Arrays

解构数组

var arr = [1, 2, 3, 4];
var a = arr[0];
var b = arr[1];
var c = arr[2];
var d = arr[3];
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
let [a, b, c, d] = [1, 2, 3, 4];

console.log(a); // 1
console.log(b); // 2
 
 
  • 1
  • 2
  • 3
  • 4

Destructuring Objects

解构对象

var luke = { occupation: 'jedi', father: 'anakin' };
var occupation = luke.occupation; // 'jedi'
var father = luke.father; // 'anakin'
 
 
  • 1
  • 2
  • 3
let luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;

console.log(occupation); // 'jedi'
console.log(father); // 'anakin'
 
 
  • 1
  • 2
  • 3
  • 4
  • 5


Modules

ES6之前,浏览器端的模块化代码,我们使用像Browserify这样的库, 
在 Node.js 中,我们则使用 require。 
在ES6中,我们现在可以直接使用AMD 和 CommonJS这些模块了。

Exporting in CommonJS

module.exports = 1;
module.exports = { foo: 'bar' };
module.exports = ['foo', 'bar'];
module.exports = function bar () {};
 
 
  • 1
  • 2
  • 3
  • 4

Exporting in ES6

在ES6中,提供了多种设置模块出口的方式,比如我们要导出一个变量,那么使用 变量名 :

export let name = 'David';
export let age  = 25;​​
 
 
  • 1
  • 2

还可以为对象 导出一个列表

function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

export { sumTwo, sumThree };
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们也可以使用简单的一个 export 关键字来导出一个结果值:

export function sumTwo(a, b) {
    return a + b;
}

export function sumThree(a, b, c) {
    return a + b + c;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后,我们可以 导出一个默认出口

function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

let api = {
    sumTwo,
    sumThree
};

export default api;
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

最佳实践:总是在模块的 最后 使用 export default 方法。 
它让模块的出口更清晰明了,节省了阅读整个模块来寻找出口的时间。 
更多的是,在大量CommonJS模块中,通用的习惯是设置一个出口值或者出口对象。 
最受这个规则,可以让我们的代码更易读,且更方便的联合使用CommonJS和ES6模块。

Importing in ES6

ES6提供了好几种模块的导入方式。我们可以单独引入一个文件:

import 'underscore';
 
 
  • 1

这里需要注意的是, 整个文件的引入方式会执行该文件内的最上层代码

就像Python一样,我们还可以命名引用:

import { sumTwo, sumThree } from 'math/addition';
 
 
  • 1

我们甚至可以使用 as 给这些模块重命名:

import {
    sumTwo as addTwoNumbers,
    sumThree as sumThreeNumbers
} from 'math/addition';
 
 
  • 1
  • 2
  • 3
  • 4

另外,我们能 引入所有的东西(原文:import all the things) (也称为命名空间引入)

import * as util from 'math/addition';
 
 
  • 1

最后,我们能可以从一个模块的众多值中引入一个列表:

import * as additionUtil from 'math/addtion';
const { sumTwo, sumThree } = additionUtil;
 
 
  • 1
  • 2

像这样引用默认对象:

import api from 'math/addition';
// Same as: import { default as api } from 'math/addition';
 
 
  • 1
  • 2

我们建议一个模块导出的值应该越简洁越好,不过有时候有必要的话命名引用和默认引用可以混着用。如果一个模块是这样导出的:

// foos.js
export { foo as default, foo1, foo2 };
 
 
  • 1
  • 2

那我们可以如此导入这个模块的值:

import foo, { foo1, foo2 } from 'foos';
 
 
  • 1

我们还可以导入commonjs模块,例如React:

import React from 'react';
const { Component, PropTypes } = React;
 
 
  • 1
  • 2

更简化版本:

import React, { Component, PropTypes } from 'react';
 
 
  • 1

注意:被导出的值是被 绑定的(原文:bingdings),而不是引用。 
所以,改变一个模块中的值的话,会影响其他引用本模块的代码,一定要避免此种改动发生。


Parameters

在ES5中,许多种方法来处理函数的 参数默认值(default values)参数数量(indefinite arguments)参数命名(named parameters)。 
ES6中,我们可以使用非常简洁的语法来处理上面提到的集中情况。

Default Parameters

function addTwoNumbers(x, y) {
    x = x || 0;
    y = y || 0;
    return x + y;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

ES6中,我们可以简单为函数参数启用默认值:

function addTwoNumbers(x=0, y=0) {
    return x + y;
}
 
 
  • 1
  • 2
  • 3
addTwoNumbers(2, 4); // 6
addTwoNumbers(2); // 2
addTwoNumbers(); // 0
 
 
  • 1
  • 2
  • 3

Rest Parameters

ES5中,遇到参数数量(实参数)不确定时,我们只能如此处理:

function logArguments() {
    for (var i=0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

使用 rest 操作符,我们可以给函数传入一个不确定数量的参数列表:

function logArguments(...args) {
    for (let arg of args) {
        console.log(arg);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

Named Parameters

命名函数 
ES5中,当我们要处理多个 命名参数 时,通常会传入一个 选项对象 的方式,这种方式被jQuery采用。

function initializeCanvas(options) {
    var height = options.height || 600;
    var width  = options.width  || 400;
    var lineStroke = options.lineStroke || 'black';
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

我们可以利用上面提到的新特性 解构 ,来完成与上面同样功能的函数:

function initializeCanvas(
    { height=600, width=400, lineStroke='black'}) {
        // ...
    }
    // Use variables height, width, lineStroke here
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果我们需要把这个参数变为可选的,那么只要把该参数解构为一个空对象就好了:

function initializeCanvas(
    { height=600, width=400, lineStroke='black'} = {}) {
        // ...
    }
 
 
  • 1
  • 2
  • 3
  • 4

Spread Operator

我们可以利用展开操作符(Spread Operator)来把一组数组的值,当作参数传入:

Math.max(...[-1, 100, 9001, -32]); // 9001
 
 
  • 1


Classes

在ES6以前,我们实现一个类的功能的话,需要首先创建一个构造函数,然后扩展这个函数的原型方法,就像这样:

function Person(name, age, gender) {
    this.name   = name;
    this.age    = age;
    this.gender = gender;
}

Person.prototype.incrementAge = function () {
    return this.age += 1;
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

继承父类的子类需要这样:

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;
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

ES6提供了一些语法糖来实现上面的功能,我们可以直接创建一个类:

class Person {
    constructor(name, age, gender) {
        this.name   = name;
        this.age    = age;
        this.gender = gender;
    }

    incrementAge() {
      this.age += 1;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

继承父类的子类只要简单的使用 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);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

最佳实践:ES6新的类语法把我们从晦涩难懂的实现和原型操作中解救出来,这是个非常适合初学者的功能,而且能让我们写出更干净整洁的代码。


Symbols

符号(Symbols)在ES6版本之前就已经存在了,但现在我们拥有一个公共的接口来直接使用它们。 
Symbols对象是一旦创建就不可以被更改的(immutable)而且能被用做hash数据类型中的键。

Symbol( )

调用 Symbol() 或者 Symbol(描述文本) 会创建一个唯一的、在全局中不可以访问的符号对象。 
一个 Symbol() 的应用场景是:在自己的项目中使用第三方代码库,且你需要给他们的对象或者命名空间打补丁代码,又不想改动或升级第三方原有代码的时候。 
举个例子,如果你想给 React.Component 这个类添加一个 refreshComponent 方法,但又确定不了这个方法会不会在下个版本中加入,你可以这么做:

const refreshComponent = Symbol();

React.Component.prototype[refreshComponent] = () => {
    // do something
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

Symbol.for(key)

使用 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
 
 
  • 1
  • 2
  • 3

一个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');
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

之后在另一个库中:

const specialRead = Symbol.for('specialRead');

class SomeReadableType {
    [specialRead]() {
        const reader = createSomeReaderFrom(this);
        return reader;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意Symbol.iterable 在ES6中像其他可枚举的对象,如数组,字符串,generators一样,当这个方法被调用时会激活一个枚举器并返回一个对象。


Maps

Maps 是一个Javascript中很重要(迫切需要)的数据结构。 
在ES6之前,我们创建一个 hash 通常是使用一个对象:

var map = new Object();
map[key1] = 'value1';
map[key2] = 'value2';
 
 
  • 1
  • 2
  • 3

但是,这样的代码无法避免函数被特别的属性名覆盖的意外情况:

> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
> TypeError: Property 'hasOwnProperty' is not a function
 
 
  • 1
  • 2

Maps 让我们使用 setget 和 search 操作数据。

let map = new Map();
> map.set('name', 'david');
> map.get('name'); // david
> map.has('name'); // true
 
 
  • 1
  • 2
  • 3
  • 4

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
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

提示:当使用 map.get() 判断值是否相等时,非基础类型比如一个函数或者对象,将不会正常工作。 
有鉴于此,还是建议使用字符串,布尔和数字类型的数据类型。

我们还可以使用 .entries() 方法来遍历整个map对象:

for (let [key, value] of map.entries()) {
    console.log(key, value);
}
 
 
  • 1
  • 2
  • 3


WeakMaps

在ES5之前的版本,我们为了存储私有数据,有好几种方法。像使用这种下划线命名约定:

class Person {
    constructor(age) {
        this._age = age;
    }

    _incrementAge() {
        this._age += 1;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在一个开源项目中,命名规则很难维持得一直很好,这样经常会造成一些困扰。 
此时,我们可以选择使用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');
        }
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

使用WeakMaps来保存我们私有数据的理由之一是不会暴露出属性名,就像下面的例子中的 Reflect.ownKeys()

> const person = new Person(50);
> person.incrementAge(); // 'Midlife crisis'
> Reflect.ownKeys(person); // []
 
 
  • 1
  • 2
  • 3

一个使用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
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

上面的例子中,一个对象被垃圾回收期给销毁了,WeakMaps会自动的把自己内部所对应的键值对数据同时销毁。

提示:结合这个例子,再考虑下jQuery是如何实现缓存带有引用的DOM元素这个功能的,使用了WeakMaps的话,当被缓存的DOM元素被移除的时,jQuery可以自动释放相应元素的内存。 
通常情况下,在涉及DOM元素存储和缓存的情况下,使用WeakMaps是非常适合的。


Promises

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
                });
            });
        });
    });
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

写成这样:

func1(value1)
    .then(func2)
    .then(func3)
    .then(func4)
    .then(func5, value5 => {
        // Do something with value 5
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在ES6之前,我们使用bluebird 或者 
Q。现在我们有了原生版本的 Promises:

new Promise((resolve, reject) =>
    reject(new Error('Failed to fulfill Promise')))
        .catch(reason => console.log(reason));
 
 
  • 1
  • 2
  • 3

这里有两个处理函数,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));
    });
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们还可以使用 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);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15


Generators

就像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 }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

就像上面的例子,当next运行时,它会把我们的generator向前“推动”,同时执行新的表达式。 
我们能利用Generators来像书写同步代码一样书写异步代码。

// Hiding asynchronousity with Generators

function request(url) {
    getJSON(url, function(response) {
        generator.next(response);
    });
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里我们写个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);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

借助于 yield,我们可以保证 entry1 确实拿到数据并转换后再赋值给 data1

当我们使用generators来像书写同步代码一样书写我们的异步代码逻辑时,没有一种清晰简单的方式来处理期间可能会产生的错误或者异常。在这种情况下,我们可以在我们的generator中引入Promises来处理,就像下面这样:

function request(url) {
    return new Promise((resolve, reject) => {
        getJSON(url, resolve);
    });
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

我们再写一个函数,其中使用 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);
        }
    })();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在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);
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在使用Generator后,我们可以重用我们的老版本代码实现,以此展示了Generator的力量。 
当使用Generators和Promises后,我们可以像书写同步代码一样书写异步代码的同时优雅地解决了错误处理问题。 
此后,我们实际上可以开始利用更简单的一种方式了,它就是async-await


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();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

它们看上去和Generators很像。我(作者)强烈推荐使用 async await 来替代Generators + Promises的写法。 
这里是个很好的学习资源,让我们学习和使用这项ES7中的新功能。


这个看了就是大概概况了一下ES6的特性,具体使用,还得自己都磨炼,我把他人分享的ES6 整理资料,放这边,希望可以帮助更多的小白,包括我自己,谢谢无私分享的程序猿。


曾经听许多前端从业者说:“前端发展太快了。”这里的快,十有八九是说层出不穷的新概念,余下的一二,大抵只是抒发一下心中的苦闷罢——前两日刚习得的新技术转眼就“落后”——仔细品味这苦闷,除却不得不持续奔跑的无奈,更多的是一口气,一口卯足了劲儿也要把新知识全数揽入囊中的不服气。作为刚入行的新人,对这点体会颇深。就像是蓦地从某个时间点切入,半数时间向前走,半数时间向后看,瞻前顾后,回味揣摩这十年间的岁月精魄,还得翘首盼着花花新世界,不时再问自己一句,这样走下去真的会好么?是的,其实答案人尽皆知,同时也无人知晓,因为没人能预言未来,顶多只能预测未来,但有一件事情永不会错,当你笃定地沿着一条路走下去,结果通常不会太糟糕,但凡能在浮躁的社会冷静下来潜心磨砺,多少总会有收获。幸而我有意弱化了对新信息的执念,开始做一些事情,《深入浅出ES6》就是其中一件。 纵观整个系列,亦即纵观ECMAScript 2015的整个体系,吸取了诸多成功经验:借鉴自CoffeeScript的箭头函数;始于C++项目Xanadu,接着被E语言采用,后来分别于Python和JavaScript框架Dojo中以Deferred对象的面貌出现的Promise规范(详见Async JavaScript一书3.1章);借鉴了C++、Java、C#以及Python等语言的for-of循环语句;部分借鉴Mustache、Nunjucks的模板字符串。 当然,新的语言体系也在之前的基础上查漏补缺:弥补块级作用域变量缺失的let和const关键字;弥补面向大型项目缺失的模块方案;标准委员会甚至为JavaScript增加了类特性,有关这一方面的特性褒贬不一,Douglas Crockford曾在2014年的Nordic.js大会发表了题为《The Better Parts》的演讲,重新阐述了他个人对于ECMAScript 6的看法,他认为Class特性是所有新标准中最糟糕的创新(我个人也略赞同这一说法,类的加入虽然有助于其它语言的使用者开始使用JavaScript,但是却无法发挥出JavaScript原型继承的巨大优势);以及为了保持非侵入式弥补其它新特性而诞生的Symbols。 其它的新特性也相当诱人,熟练掌握可以大幅度提升开发效率:迭代器Iterator、生成器Generators、不定参数Rest、默认参数Default、解构Destructuring、生成器Generator、代理Proxy,以及几种新类型:Set、Map、WeakSet、WeakMap、集合Collection。 以上提及的新特性只是其中的一部分,更多的新特性等待着大家进一步挖掘。整个系列的翻译历时150余天,坚持专栏翻译的日子艰苦也快乐,编辑徐川细心地帮我审校每一篇文章,编辑丁晓昀赠予钱歌川先生详解翻译之著作让我大开眼界,与李松峰老师的交流也让我深刻理解了“阅读、转换、表达”的奥义所在,最感谢我的母亲,在我遇到困难需要力量的时候永远支持着我。选择ES6作为前端生涯的切入点实之我幸,恰遇这样的机会让我可以一心一意地向前走,向未来走。我很敬佩在“洪荒”和“战乱”年代沉淀无数经 验的前辈们,你们在各种不确定的因素中左右互搏,为终端用户提供统一的用户体验,直到如今你们依然孜孜不倦地吸取业内新鲜的经验。技术在进步,也为前端人 提供着无限的可能性,我们有责任也有义务去推动新标准的发展和普及,诚然在商业的大环境下我们不愿放弃每一寸用户的土壤,但携众人之力定将能推动用户终端 的革新。ES7标准的提案纷纷提上日程,用不了多久也将登上前端大舞台。也感谢午川同学友情提供译文《深入浅出ES6(十):集合 Collection》,让我在困难时期得以顺利过渡。最后祝愿国内前端社区向着更光明美好的未来蓬勃生长!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值