用简单的方法学习ES6

ES6 简要概览

这里是ES6 简要概览。本文大量参考了ES6特性代码仓库,请允许我感谢其作者@Luke Hoban的卓越贡献,也感谢@Axel Rauschmayer所作的[优秀书籍]//exploringjs.com/es6/)。

起初当我听说ES6时,我花了很多精力去消化学习其概念和基础知识。我经历了这些,希望你们无需重蹈覆辙。因此我写下了这篇对ES6及其新特性的简要介绍,所有知识点都解释得通俗易懂,简明扼要,对于像我这样的新人非常友好。

简介

ES6,也称作ECMAScript 2015,是JavaScript的下一代版本,在2015年6月正式标准化。ES6是该语言自2009年的ES5以来的一次重大更新。

  • 这是一门全新的语言吗?:绝对不是!它就是我们所了解的JavaScript,只不过拥有了更优雅的语法和更多特性。
  • 这是否意味着我现有的JavaScript代码不久就将变得不可用了?:并非如此!如果是那样的话,对整个网站将是巨大的损失!JavaScript一直都是向后兼容的。比如,新的特性将会被添加,而现存特性将会变得更强大。这叫做唯一的JavaScript
  • 它的目标是什么?:总体来说是成为了一门更好的语言!它让我们的代码更快,更安全,更搞笑。
  • ES6之后还会有什么?:会出现ECMAScript 7等等后续版本。TC39计划每年发布一个ECMAScript的新版本。比如,从现在开始,ECMAScript版本将会作相对较小的升级。因此,当你想开始学ES7和后续版本时,你现在所学的关于ES6的一切将会派上用场。

安装

这个部分是为那些还不熟悉命令行的Web设计开发者准备的。如果你已经知道如何安装node.js 和 Babeljs,以及ES6编译器,你可以跳过这部分。

我是否需要安装一些东西?是的!由于ES6是新的,浏览器尚未支持其大多数特性。但我们无需等待。我们可以在node.js 和 Babeljs, 以及S6编译器的帮助下开始编写ES6代码Babeljs将会将ES6语法转换为ES5,这样现有的浏览器就可以解释我们的代码了,就好像我们一开始就是用ES5编写的一样。这是不是很酷?让我们来看看所有这一套是如何安装的,然后开始编写代码。

  • 首先下载和安装node.js到你的机器上。
  • 打开终端/命令行,输入npm install --global babel。按下回车运行该命令,然后第一次安装Babeljs到你的机器。Babeljs就是ES6的编译器。
  • 运行命令: npm install -g browserify。如果你想使用ES6模块加载器语法,需要把Browserify也装上。Browserify使你能在独立的JavaScript文件中编写更加模块化的代码,然后将它们打包,最后让你的HTML页面只需引用一个JavaScript文件。
  • 运行命令:cd path/to/my/project,将路径更改为你的项目路径。
  • 运行命令:babel src --out-dir build。这条命令会将'src'文件夹下的所有 .js 后缀的文件从ES6转换为ES5语法,然后将转换后的文件放入'build'目录下。

现在你已经准备好了,你可以将新的转换后的.js文件引入你的HTML页面,浏览器就可以像往常一样正常运行你的代码。

ES6 特性

字符串,数组,及对象的新增API

在ES6中,我们有许多新增的库,包括核心的Math库,数组转换帮助工具和用于拷贝的Object.assign()

'hello'.startsWith('hell'); // => true
'hello'.endsWith('ello');   // => true
'hello'.includes('ell');    // => true
'doo '.repeat(3);           // => 'doo doo doo '

Array.from(document.querySelectorAll("*")); // => 返回一个真实的数组
Array.of(1, 2, 3);                          // => 和 new Array(...)相似, 但是不需要指定单参数
[0, 0, 0].fill(7, 1);                       // => [0,7,7]
[1, 2, 3].findIndex(x => x == 2);           // => 1
['a', 'b', 'c'].entries();                  // => 迭代器 [0, 'a'], [1,'b'], [2,'c']
['a', 'b', 'c'].keys();                     // => 迭代器 0, 1, 2
['a', 'b', 'c'].values();                   // => 迭代器 'a', 'b', 'c'

Object.assign(Point, { origin: new Point(0,0) }); // => 为'Point'对象添加新属性.

Symbol

Symbol是ES6中的一种新的原始数据类型。它们是作为独有ID使用的字符。你可以通过工厂函数Symbol()创造symbol字符。它们都是独一无二的。每次我们创建一个新的symbol,我们实际上是创建了一个新的独一无二的标识符,它不会与我们项目中其他任何变量名、属性名冲突。这就是为什么某些场景下它很有用的原因。例如,我们可以使用它定义一个常量。

在ES5中,我们以前会使用两个不同的对无二的字符串来定义常量。我们会不得不依赖于字符串!但众所周知,字符串并不具备唯一性。我们可能在偶然的时机改变它,或在不同的地方输入它们,这些操作会使我们的常量的行为崩坏。但是现在,我们可以很容易地使用Symbol()来定义常量,并能确保每次我们调用Symbol()时都会产生一个在我们项目中独一无二的标识符,并且永远不会和其他属性名产生冲突。这很酷!

const COLOR_RED    = Symbol();
const COLOR_ORANGE = Symbol();

console.log( 'each Symbol() is always unique: ', Symbol() === Symbol() ); // => 没错,这样也会返回false.

// 它也可以帮我们为对象和类创建独一无二的动态的方法。
const MY_KEY = Symbol();
let obj0 = {};

obj0[MY_KEY] = 123;
console.log('my dynamic object method: ', obj0[MY_KEY]); // => 123

扩展阅读

模板字符串

模板字符串为构造字符串提供了语法糖。字符串本身被撇号包裹,字符串中插入的表达式使用${var}分隔。模板字符串通常用来创建字符串。

// 多行字符串
const HTML5_SKELETON = `
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
</body>
</html>`;

// 内嵌变量绑定
let name = 'Bob', time = 'today';
let greeting = `Hello ${name}, how are you ${time}?`;

// 与标签模板一起使用,生成的原始字符串将包含模板字符串中的所有转义字符和反斜杠。
let str = String.raw`This is a text
with multiple lines.
Escapes are not interpreted,
\n is not a newline.`;

扩展阅读

Let + Const

ES6提供了两种声明变量的方式:let 和 const,它们几乎替代了ES5中使用var声明变量的方式。letvar的工作方式很像,但是它声明的变量是有块作用域的,它只在于当前的块作用域中有效。而var声明的变量是在函数作用域内有效。

function func(randomize) {
    if (randomize) {
        let x = Math.random(); // 注意:变量x只存在于这个if作用域中
        var y = Math.random(); // 但是变量y可以在整个func函数中访问到
    }

    // 块作用域意思是:我们可以在一个函数中保护一个变量。比如,这里的x与上述的x没有任何关系。
    let x = 5;

    return y;
}

const 和 let工作原理类似,但是你声明变量的同时必须立即用一个不会再改变的值对其进行初始化。

const a = 123;

注意const 陷阱!const只保证变量自身是永恒不变的,如果变量是一个对象,则其属性仍然是可变的,相应的解决办法就是JavaScript的 freeze() 方法。

const freezObj = Object.freeze({});

解构

解构允许我们在支持匹配数组和对象的条件下,使用模式匹配进行绑定。 解构实际上是一种从存储于对象和数组(可能是嵌套存储)的数据中提取值的简便方法。

// 让我们更好地理解解构:
let obj1 = {}; 
obj1.first = 'Jane';
obj1.last = 'Doe'; // 这是我们构造对象数据的方式
let f1 = obj1.first; 
let l1 = obj1.last; // 这是我们从对象中提取数据的方式

// 我们也可以使用对象字面量来构造:
let obj2 = { 
    first: 'Jane', 
    last: 'Doe' 
};

// 解构和它很类似。解构就是构造的对立面。它使我们提取数据变得更加容易。
let { first: f2, last: l2 } = obj2; // 现在我们获得了变量f2和l2。

// 解构对数组同样适用
let [x1, y1] = ['a', 'b']; // => x1 = 'a'; y1 = 'b'

// 用计算过的值作为属性
const FOO = 'foo';
let { [FOO]: f4 } = { foo: 123 }; // => f4 = 123

我们也可为解构选择一种模式。注意:值得一提的是,当我们使用解构赋值时,我们需要声明要从数组或对象中抽取的变量。比如,在下面的例子中,我们要从obj3中抽取foo,并将其存储为变量f3。我们只创建了访问对象的foo属性的模式,并且只声明了该属性,因为我们只需要用到它。

let obj3 = { a: [{ foo: 123, bar: 'abc' }, {}], b: true };
let { a: [{foo: f3}] } = obj3; // => f3 = 123

解构赋值也可以有默认值:

let [x3 = 3, y3] = [];           // => x3 = 3; y3 = undefined
let {foo: x4 = 3, bar: y4} = {}; // => x4 = 3; y4 = undefined

// 当然,默认值也可以是函数(执行的结果 -- 译者注):
function log() { return 'YES' }
let [aa=log()] = [];

// Default values can refer to other variables in the pattern.默认值可以指向模式中的其他变量,但它们的顺序至关重要!以下写法会产生引用错误:
// let [x=y, y=3] = [];
// 为什么呢?因为当x指定y为其默认值时,y还没有被定义。
let [xx=3, yy=xx] = [];

解构也可以用于for...of循环。**注意: **在ES6中有一种新型的循环,for...of。在ES5之前,当我们想要遍历一个数组时,会使用for,ES5中有一个forEach()方法帮助我们达成目的。现在的for...of更易用。

// 使用for...of循环数组示例
let arr = ['a', 'b', 'c'];
for ( let item of arr ) {
    //console.log(item);
}

// 通过使用新的数组方法 entries()和解构赋值,我们可以得到数组中每个元素的索引和值。
for ( let [index, item] of arr.entries() ) {
    //console.log(index + '. ' + item);
}

// 也可以使用下面的方法实现
for ( {name: n, age: a} of arr ) {
    // do something
}

// 数组模式对可迭代对象都有效
let [x2,...y2] = 'abc';           // => x2='a'; y2=['b', 'c']; 展开运算符'rest'
let [,,x] = ['a', 'b', 'c', 'd']; // => x = 'c'; 可以省略元素

好,那么除此之外,解构赋值还能用于哪些场景呢?

// 用于分割数组
let [first1, ...rest1] = ['a', 'b', 'c'];

// 返回多个值
function testing() {
    return {element: undefined, index: -1};
}

扩展阅读

默认值和展开运算符

ES6提供了一个新的更好的定义函数参数默认值的方式:

// 在ES5中,你是这样定义参数的默认值的:
function foo(x, y) {
    x = x || 0; y = y || 0;

    // do something
}

// ES6用更好的语法来实现:
function foo(x=0, y=0) {
    // y is 0 if not passed (or passed as undefined)
}

// 通过ES6,你可以在定义参数时使用解构赋值,代码会变得更简洁:
function selectEntries1({ start=0, end=-1, step=1 } = {}) {
    // do something
}

// 上述函数与这个等同:
function selectEntries2(options) {
    options = options || {};
    var start = options.start || 0;
    var end = options.end || -1;
    var step = options.step || 1;

    // do something
}

ES6也支持rest展开运算符:

function format(pattern, ...params) {
    return params;
}

format('a', 'b', 'c'); // ['b', 'c'] // params是一个数组

// ES6 中我们有展开运算符'...'。
// 在ES5中,我们使用apply()来将数组中的元素转成参数。
Math.max.apply(null, [-1, 5, 11, 3]);

// 现在,我们很容易就可以实现这个功能,因为展开运算符会提取它的每一项,然后将其转换到参数中。
Math.max(...[-1, 5, 11, 3]);

扩展阅读

箭头函数和this关键字

箭头函数是使用=>语法简写的函数。但是与其他函数不同的是,箭头函数包裹的内部代码共享同一个this关键字。

函数体是表达式:

var evens = [0,2,4];

// 以下两种写法效果相同:
var odds = evens.map(v => v + 1);
var odds = evens.map(function(v){ return v + 1; });

// 以下两种写法效果相同:
var nums = evens.map((v, i) => v + i);
var nums = evens.map(function(v, i){ return v + i; });

函数体是声明:

var fives = [];
nums.forEach(v => {
    // 看见了吗,对于更复杂的声明,我们可以把所有东西放进大括号{}中,就像我们使用普通函数那样。
    if (v % 5 === 0) fives.push(v);
});

关键字this:

var bob = {
    _name: 'Bob',
    _friends: [],
    printFriends() {
        this._friends.forEach(f =>
        // 'this' 关键字就指向'bob'对象,而不是指向这个闭包作用域本身。
        console.log(this._name + ' knows ' + f));
    },
};

class UiComponent {
    constructor() {
        let button = document.getElementById('myButton');
        button.addEventListener('click', () => {
            // 通过使用箭头函数,'this'关键字就指向我们的'UiComponent'类,而不是闭包。这是ES6提供的很棒的特性。在这种场景下,我们再也不需要使用bind()了。
            this.handleClick();
        });
    }

    handleClick() {
        console.log('CLICK');
    }
}

ES2015的类是一种基于原型的简单语法糖。

class Person {
    // 当一个类初始化时,会自动调用构造函数。
    constructor(fname, lname) {
        // 类内部只能包含方法,而不能包含属性,因此我们需要在构造函数内部设置我们的属性。
        this.fname = fname;
        this.lname = lname;
    }
}

class Employee extends Person {
    constructor(fname, lname, name = 'no name') {
        // 在继承类中,必须调用super()才能使用'this'关键字去定义属性,比如this.name. 以及,如果我们不调用super(),会得到引用错误。
        super(fname, lname);

        if (name === 'no name') this.name = this.fname + ' ' + this.lname;
    }

    setJob(title) {
        this.job = title;
    }

    static greeting() {
        return 'Hello World!';
    }

    // 同时,类智能让我们创建静态方法,而不能创建静态数据属性。但是我们可以创建一个静态的getter函数。
    static get JOHN() {
        return new Employee('John', 'Doe');
    }

    // Getters and setters
    get prop() {
        return this.prop;
    }

    set prop(value) {
        this.prop = value;
    }

    // 计算过后的方法名
    ['my'+'Method']() {
        // do something
    }
}

var john = new Employee('John', 'Doe');
john.setJob('Designer');

console.log('Class-> Employee class, just initialized: ', Employee.JOHN);
console.log('Class-> Employee class, greeting: ', Employee.greeting());
console.log('Class-> John: ', john);

扩展阅读

增强的对象字面量

对象字面量被扩展以支持在构造时以foo: foo的简写形式设置原型,定义方法和调用上层函数。

let first = 'Jane';
let last = 'Doe';
let propKey = 'foo';

let obj = {
    // 方法的定义
    myMethod(x, y) {
        // do something
    },

    // 属性值简写,如下:
    // let obj = { first, last };
    // 效果同下:
    // let obj = { first: first, last: last };
    first,
    last,

    // 计算后的属性值:
    [propKey]: true,
    ['b'+'ar']: 123,

    ['h'+'ello']() {
        // console.log(obj.hello());
        return 'hi';
    },

    // Setter和Getter函数
    get sth() {
        console.log('Object Literal-> ', 'sth getter');
        return 123;
    },

    set sth(value) {
        console.log('Object Literal-> ', 'sth setter');
        // 返回值被忽略
    }
};

// 对象中的新方法
// 对象中最重要的新方法就是assign().
Object.assign(obj, { bar: true }); // 它为我们的对象新增参数。
console.log('Object Literal-> ', JSON.stringify(obj)); // {"first":"Jane","last":"Doe","foo":true,"bar":true,"sth":123}

迭代器与for..of循环

ES6 为遍历引入了新的接口,iterable(Iterable 实际上意味着任何东西都可以被重复)。数组,字符串,Map对象,Set对象,DOM数据结构(正在使用中的)都是可迭代的iterable对象。

因此,用简单的话来说,迭代器就是一种结构,每次调用它时都会按序列返回下一个结果。例如数组的entries()方法。每次我们调用arr.entries(),它都会返回数组中的下一项。

注意:有的可迭代结构并不是什么新鲜事情,例如for循环。但是,我这里只是想解释迭代协议是什么,使它的概念更清晰,并且引入关于它的ES6新特性。

通过迭代协议接收数据的语言构造:

// 解构实际上是在做迭代的工作(重复性的工作)来从数组中提取数据。完成这个目标是以一个特别的模式进行重复性的工作。
let [a,b] = new Set(['a', 'b', 'c']);

// for-of显然是可迭代的。
for (let x of ['a', 'b', 'c']) {
console.log('for-of iteration-> ', x);
}

let arr2 = Array.from(new Set(['a', 'b', 'c']));    // => Array.from()
let arr3 = [...new Set(['a', 'b', 'c'])];           // => 展开运算符 (...)
let map0 = new Map([[false, 'no'], [true, 'yes']]); // => Maps的构造函数
let set0 = new Set(['a', 'b', 'c']);                // => Sets的构造函数
// Promise.all(iterableOverPromises).then(); // Promise.all()
// Promise.race(iterableOverPromises).then(); // Promise.race()

// yield* 也是可迭代的
// 注意:当我们想创建一个nGenerator函数时,'yield' 与Generators相关
// (Generator 是ES6中的新特性)

让我们来使用迭代器:

let arr4 = ['a', 'b'];
let iter = arr4[Symbol.iterator](); //我们通过键为Symbol.iterator的方法创造了一个迭代器

// 然后我们重复调用迭代器的next()方法来检索每一项。
// 在数组内部:
iter.next(); // returns an object: { value: 'a', done: false }
iter.next(); // { value: 'b', done: false }
iter.next(); // { value: undefined, done: true }
// 注意:布尔属性'done'暗示了item序列是否到达了末尾。

看见了吗?这其实有一点像循环。它每次都返回一个新的东西。注意:迭代协议的一个关键特性就是它的有序性:迭代器本身每次只返回一个值,这意味着如果一个迭代的数据结构是非线性的(比如树),迭代器会对其进行线性化。

现在,让我们在对象中使用Symbol,使其行为表现像一个迭代器一样:

let iterableObject = {
    // 我们的对象必须要有一个动态方法,实际上是这个动态方法在使用Symbol原始类型。正如我们所知,Symbol总是独一无二的,这也正是我们的使用场景,利用它为我们的类创建一个独一无二的动态方法。
    [Symbol.iterator]() {

        let data = ['hello', 'world'];
        let index = 0;

        // 现在我们的迭代器方法必须返回一个含有next()方法的对象
        return {
            // Here is our iterator logic! In our example here, we check 'index'
            // variable and act accordingly based on its value.这就是我们的迭代器逻辑!在这个例子中,我们检验了'index'变量和基于它的值的表现。
            next() {
                if (index < data.length) {
                    return { value: data[index++] };
                } else {
                    return { done: true };
                }
            }
        };
    }
};

// 这就是我们如何使用迭代对象的方法。
for (let x of iterableObject) {
    // x每次都不同。第一次循环它是'hello',第二次是'world'.
    console.log('iterableObject-> ', x);
}

注意:正如我们所知,我们在对象中使用了symbol作为方法的键名。这个独一无二字符制造器使对象可迭代,并且使我们可以使用for...of循环。酷~现在我们已经在我们的代码里创建了一个定制的迭代对象(或类),这使我们可以在项目中是的迭代部分的代码更简单。

如果以上可迭代对象是一个真实的样本,它可能在项目中非常有用。对我来说没有必要把所有逻辑都放进for...of循环来做一个迭代的工作,我只需要创建一个有意义的可迭代类,然后把我的逻辑都放在其中,然后我就可以在不同的地方用for...of循环使用我的类,并且可以很简单地实现迭代工作。很简单吧~这将使我的代码更简洁。

扩展阅读

模块

组件定义中,对模块的语言层面的支持。从流行的JavaScript模块加载器(AMD, CommonJS)整理的模式。在ES6中,模块存储于文件。每个文件都是一个模块,每个模块也都是一个文件。

// lib/math.js
// 我们可以从文件中导出任何变量或函数。
export function sum(x, y) { return x + y; }
export var pi = 3.141593;

// app.js
// 从其他文件中引入我们想要的任何东西
import * as math from "lib/math";

// 现在,我们可以像下面这样访问我们从math.js导出的任何东西:
alert("2π = " + math.sum(math.pi, math.pi));

// otherApp.js
// 我们也可以明确指明要引入的函数而不是用一个通用的名字。
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));

也允许有一个单独的默认输出。Node.js社区中,有很多只导出一个值的模块。我们可以让模块只导出一个类或函数。

// myFunc.js
// 当然,我们导出的函数也可以有一个名称:export default function foo() {}
export default function() { }
import myFunc from 'myFunc';
myFunc();

// MyClass.js
// 以及我们导出的函数当然也可以有一个名称: export default class Bar {}
export default class { }
import MyClass from 'MyClass';
let inst = new MyClass();

正如我们所知,在 ES5代码里,不会通过库(类似于RequireJS, Browserify 或 Webpack)来使用模块,其模块模式非常流行,是基于IIFE(立即执行函数)的。它的优点就是明确将共有和私有部分区分开来了.

// 在ES5中如何合理创建模块:
// my_module.js

var my_module = (function () {
    // 私有模块的变量
    var countInvocations = 0;

    function myFunc(x) {
        countInvocations++;
    }

    // 通过模块导出:
    return {
        myFunc: myFunc
    };
}());

// 该模块模式产生了一个全局变量,它的使用如下:
my_module.myFunc(33);

在ES6中,模块是内建的,这就是为什么使用它们的门槛非常低的原因:

// 如何在ES6中合理创建模块:
// my_module.js

// 私有模块的变量:
let countInvocations = 0;

// 导出模块
export function myFunc(x) {
    countInvocations++;
}

扩展阅读

四种数据结构:Map,Set,WeakMap,WeakSet

对于通用算法很高效的数据结构。接下来的四种数据结构是ES6中新增的:MapWeakMapSetWeakSet

Map ES5中缺失的是一种值到值的映射。ES6 的 Map数据结构让你能使用任意值作为键,很是一种很流行的做法。

// 创建一个空的Map
let map = new Map();

// 我们也可以在初始化时就填充map为其赋值:
let map = new Map([ [ 1, 'one' ], [ 2, 'two' ] ]);

// Map的set()方法时可链式调用的。因此我们也可以像这样填充map赋值:
let map = new Map().set(1, 'one').set(2, 'two');

// 任意值,甚至是一个对象,都可以作为键。
// 如果我们将要获得的值由于某种原因是undefined,那我们也可以设置或运算符:map.get(KEY) || 0;
const KEY = {};
map.set(KEY, 123);
map.get(KEY);     // => 123
map.has(KEY);     // => true
map.delete(KEY);  // => true
map.size;         // => 1
map.clear();      // => 清空map

// keys()返回一个Map中的键可迭代的对象。比如我们可以在一个for-of循环中使用它。
map.keys();

// values() 返回一个Map中的值可迭代的对象。
map.values();

// 返回一个Map中的键值对[key,value]可迭代的对象。
// 注意:我们可以在for-of循环中使用解构,同时访问到keys和values(键-值),就像我们用数组的entries()方法能做的那样。
map.entries();

WeakMap: 是一种防止其键被垃圾回收机制回收的Map。这意味着你可以用对象协调数据而不需要担心内存泄漏。WeakMap是一种keys必须为对象,值可以为任意值的数据结构。它有同Map一样的API,唯一一点显著差别是:你不能对内容进行迭代,无论是keyvalue,还是entries。你也不能清除WeakMap

Set: Set也是 ES5 所没有的数据结构。有两种可能会用到 Set 的地方:

  • 使用对象的key去存储字符串集合的元素。
  • 在数组中存储任意的集合元素:通过indexOf()来检验是否包含某个元素,通过filter()删除元素等等。这不是一个快速解决办法,但是很容易实现。一个需要知晓的地方是,indexOf()无法找出值为NaN的元素。

ES6 的 Set 数据结构对任意值的操作而言很奏效,而且能正确处理NaN

let set = new Set();

// 我们也能在初始化时就填充Set的值。
let set = new Set(['red', 'green', 'blue']);

// Setadd()方法是可链式调用的。因此我们可以像这样为set赋值:
let set = new Set().add('red').add('green').add('blue');

set.add('red');
set.has('red');    // => true
set.delete('red'); // => true
set.size;          // => 1
set.clear();       // => 清空set

WeakSet: Set可以防止其元素被垃圾回收机制回收。

注意: 为什么MapSet都是具备size属性而不是像数组那样用length属性呢?这个不同之处的原因在于length是对序列而言的,序列这种数据结构是有索引的,像数组这样。size属性是对于集合而言的,它们通常是无序的,像MapSet这样。

扩展阅读

Promise对象

Promise对象是用于异步编程的库。我们已经熟悉了JavaScript中的promise模式。但是在一些简单场景下,它实际上使得异步的行为更简单。我们可以设置一个新的promise,在其中编写任何一部行为。比如ajax调用或timeout定时器等等。

function getJSON (url) {
    let promise = new Promise(function (resolve, reject) {
        // OK,现在我们可以在promise中编写我们的异步行为代码了。比如ajax调用。

        // resolve(value); // 如果我们的ajax调用成功,会调用resolve()并传递必要的参数给它。参数是什么呢?由我们自己根据我们的异步工作而决定。
        // 比如,对于ajax工作,jquery的ajax()方法在其成功加载文件后会调用我们的成功回调函数。它也会传递一个参数,就是它实际加载的数据。
        // 因此我们这儿的参数就是这个数据。
        // reject(error); // 如果失败,我们会调用reject(),并且传递必要的参数给它。 
    });

    return promise; // r记得将promise返回
}

// 这就是我们使用promise的方式。
// 当promise状态转为resolved时,它的then()方法将会被调用。当它的状态转为rejected时,catch()方法将会被调用。
getJSON('promised.json')
    .then(value => {})
    .catch(error => {});

// 注意:then()方法也有可选的第二个参数,实际上就是发生的错误。这部分代码和上述相同。我们可以用任意一个。
getJSON('promised.json')
    .then(value => {}, error => {});

// 我们也可以链式使用promise。 then()方法会返回一个新的Promise对象Q。
getJSON('promised.json')
    .then(value1 => 123) // 无论第一个then()返回了什么,它都可以作为第二个then()的参数。
    .then(value2 => {}); // 因此'value2'等于123.

// 在链式调用中,如果任意一个promise失败,我们仍然通过在发生失败的promise的catch()方法返回一个默认值来继续执行调用链。
getJSON('promised.json')
    .catch(() => 'default value') // 无论第一个then()返回了什么,它都可以作为第二个then()的参数。
    .then(value2 => {}); // 因此'value2'等于'default value'.

// 我们也可以手动抛出异常,它会被传递给下一个错误处理器。
getJSON('promised.json')
    .then(value => {throw new Error();})
    .then(err => {});

// 有了可链式调用的promise,如我们所知,我们可以很轻松地做下面这样的事,拥有嵌套的promise:
asyncFunc1()
    .then(function (value1) {
        asyncFunc2()
            .then(function (value2) {
            // do something else...
        });
    });

// 或者将其扁平化,像下面这样:
asyncFunc1()
    .then(function (value1) {
        return asyncFunc2();
    })
    .then(function (value2) {
        // do something else...
    });

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: https://www.w3cplus.com/javascript/lets-learn-ecmascript-6-basics-in-simple-terms.html © w3cplus.com

posted @ 2019-02-15 15:52  冰柠檬草  阅读(365)  评论(0编辑  收藏  举报