前言

先吐槽一件事,最近把原先的 TOP 域名更换到 CN 域名,并且用 Gatsby 重建个人站点,之前是用采用 HTTPS 部署的方式绕过阿里云的域名备案系统。更换 CN 域名后,这招不管用了,?? 域名必须要备案了,等待幕布邮寄中……

有人要问了,都 9102 年,ES10 都出来了,怎么还在讲 ES6,非也!本文针对 ES6 几个不为人知、和重要的特性做讲解,精彩的在后面!

基础篇

Let + Const

ES6 除了固有的函数作用域,还引入了块级作用域({})

function f() {{let x; // ①{// 包含在当前块中,与 ① 中的 x 分属不同作用域const x = "sneaky";// 错误,const 定义的变量不可以重新赋值,如果 const 定义了一个对象,那么对象的属性是可以修改的x = "foo";}// let 定义的变量可以重新赋值x = "bar";// 错误,x 在 ① 块中已被定义let x = "inner";}
}
复制代码

默认、剩余、展开参数(Default + Rest + Spread)

function f(x, y=12) {// y 等于 12 如果不传递 (或者传递 undefined)return x + y;
}
f(3); // 15
复制代码
function f(x, ...y) {// y 是一个数组return x * y.length;
}
f(3, "hello", true); // 6
复制代码
function f(x, y, z) {return x + y + z;
}
// 将数组的每一项作为参数传递
f(...[1,2,3]); // 6
复制代码

解构(Destructuring)

var [a, ,b] = [1,2,3];
a === 1; // true
b === 3; // truevar { op: a, lhs: { op: b }, rhs: c } = getASTNode()// var {op: op, lhs: lhs, rhs: rhs} = getASTNode()
var {op, lhs, rhs} = getASTNode() // 参数解构
function g({name: x}) {console.log(x);
}
g({name: 5})var [a] = [];
a === undefined; // truevar [a = 1] = [];
a === 1; // true// 解构 + 默认参数
function r({x, y, w = 10, h = 10}) {return x + y + w + h;
}
r({x:1, y:2}) === 23 // true
复制代码

箭头函数(Arrows and Lexical This)

// 除了支持返回语句,还可以将表达式作为返回主体
const foo = () => ({ name: 'es6' });
const bar = (num) => (num++, num ** 2);foo();  // 返回一个对象 { name: 'es6' }
bar(3); // 执行多个表达式,并返回最后一个表达式的值 16
复制代码

JS 中 this 的指向问题一直都是面试高频考点,不少人在实战中也掉入坑中,总结起来就是一句话:“ this 永远指向调用它的那个对象”,而箭头函数则改写了这一规则,就是

箭头函数共享当前代码上下文的 this

什么意思呢?可以理解为

  • 箭头函数不会创建自己的 this它只会从自己的作用域链的上一层继承 this,如果上一层还是箭头函数,则继续向上查找,直至全局作用域,在浏览器环境下即 window
  • 函数具有作用域链,对象则不具有

因此,在下面的代码中,传递给 setInterval 的函数内的 thissayHello 函数中的 this 一致:

const bob = {name: 'Bob',sayHello() {setTimeout(() => {console.log(`hello, I am ${this.name}`);}, 1000);}
};
const hello = bob.sayHello;bob.sayHello();
// hello, I am Bob
// 作为对象的方法调用,sayHello的this指向bob
hello();
// hello, I am undefined
// 作为普通函数调用,相当于window.hello(),this指向全局对象
hello.call({name:'Mike'});
// hello, I am Mike
// call,apply调用,第一个参数为this指向的对象
复制代码
language = 'Python';
const obj = {language: 'TS',speak() {language = 'GO';return function() {return () => {console.log(`I speak ${this.language}`);};};}
};obj.speak()()(); // 做个小测试,会打印什么呢?
复制代码

箭头函数还有以下特点

  • 由于箭头函数没有自己的 this 指针,通过 callapply 调用,第一个参数会被忽略
  • 不绑定 Arguments 对象,其引用上一层作用域链的 Arguments 对象
  • 不能用作构造器,和 new 一起用会抛出错误。
  • 没有 prototype 属性。

现在你应该明白为何 React 中的函数写法都为箭头函数,就是为了绑定 this

Symbols

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,它的功能类似于一种标识唯一性的 ID

// 每个 Symbol 实例都是唯一的。因此,当你比较两个 Symbol 实例的时候,将总会返回 false
const s1 = Symbol('macOS');
const s2 = Symbol('macOS');// Symbol.for 机制有点类似于单例模式
const s3 = Symbol.for('windows'); // 注册一个全局 Symbol
const s4 = Symbol.for('windows'); // 已存在相同名称的 Symbol,返回全局 Symbols1 === s2; // false
s3 === s4; // true复制代码
let key = Symbol('key');function MyClass(privateData) {// 注意,Symbol值作为对象属性名时,不能用点运算符this[key] = privateData;
}MyClass.prototype = {doStuff() {console.log(this[key]);}
};// Symbol的一些特性必须要浏览器的原生实现,不可被 transpiled 或 polyfilled
typeof key // symbollet c = new MyClass('hello');
c.key; // undefined
c[key]; // hello复制代码

应用场景

  • 更好的设计我们的数据对象,让“对内操作”和“对外选择性输出”变得更加优雅。

在实际应用中,我们经常会需要使用 Object.keys() 或者 for...in 来枚举对象的属性名,那在这方面,Symbol 类型的 key 表现的会有什么不同之处呢?来看以下示例代码:

let obj = {[Symbol('name')]: '一斤代码',age: 18,title: 'Engineer'
}Object.keys(obj)   // ['age', 'title']for (let p in obj) {console.log(p)   // 分别会输出:'age' 和 'title'
}Object.getOwnPropertyNames(obj)   // ['age', 'title']
复制代码

也正因为这样一个特性,当使用 JSON.stringify() 将对象转换成 JSON 字符串的时候,Symbol 属性也会被排除在输出内容之外:

JSON.stringify(obj)  // {"age":18,"title":"Engineer"}
复制代码

由上代码可知,Symbol 类型的 key 是不能通过 Object.keys() 或者 for...in 来枚举的,所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用 Symbol 来定义。

  • 消除魔术字符串
const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'function handleFileResource(resource) {switch(resource.type) {case TYPE_AUDIO:playAudio(resource)breakcase TYPE_VIDEO:playVideo(resource)breakcase TYPE_IMAGE:previewImage(resource)breakdefault:throw new Error('Unknown type of resource')}
}
复制代码

上面的代码中那样,我们需要为常量赋一个唯一的值(比如这里的 'AUDIO'),'AUDIO' 就是一个魔术字符串,它本身没意义,只是为了保证常量唯一的关系。常量一多,就变得十分臃肿且难以理解

现在有了 Symbol,我们大可不必这么麻烦了:

// 保证了三个常量的值是唯一的
const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()
复制代码

增强的对象字面量(Enhanced Object Literals)

const obj = {// 允许设置原型__proto__: theProtoObj,// 允许覆盖属性['__proto__']: somethingElse,// 属性简写,等于 ‘handler: handler’handler,// 计算 (动态) 属性名['prop_' + (() => 42)()]: 42
};
obj.prop_42 // 42
obj.__proto__ // somethingElse
复制代码

__proto__ 需要原生支持,它在之前的 ECMAScript 版本中被移除,但大多数浏览器都实现了这一特性,包括 Node 环境

Map + Set + WeakMap + WeakSet

Set

Set 是 ES6 中新增的数据结构,它允许创建唯一值的集合。集合中的值可以是简单的基本类型(如字符串或数值),但更复杂的对象类型(如对象或数组)也可以,亦或是一个新的 Set

let animals = new Set();animals.add('?');
animals.add('?');
animals.add('?');
animals.add('?');
console.log(animals.size); // 4
animals.add('?');
console.log(animals.size); // 4console.log(animals.has('?')); // true
animals.delete('?');
console.log(animals.has('?')); // falseanimals.forEach(animal => {console.log(`Hey ${animal}!`);
});// Hey ?!
// Hey ?!
// Hey ?!animals.clear();
console.log(animals.size); // 0
复制代码

我们还可以传入一个数组来初始化集合

let myAnimals = new Set(['?', '?', '?', '?']);myAnimals.add(['?', '?']);
myAnimals.add({ name: 'Rud', type: '?' });
console.log(myAnimals.size); // 4// Set 内置了遍历器,可以调用 forEach, for…of
myAnimals.forEach(animal => {console.log(animal);
});// ?
// ?
// ["?", "?"]
// Object { name: "Rud", type: "?" }
复制代码

Map

与普通对象(Object)不同,Map 的键名(Key)可以是任何类型,不再局限于字符串(String),包括但不限于 objectsfunctions

let things = new Map();const myFunc = () => '?';things.set('?', 'Car');
things.set('?', 'House');
things.set('✈️', 'Airplane');
things.set(myFunc, '? Key is a function!');things.size; // 4things.has('?'); // truethings.has(myFunc) // true
things.has(() => '?'); // false
things.get(myFunc); // '? Key is a function!'things.delete('✈️');
things.has('✈️'); // falsethings.clear();
things.size; // 0// 链式设置键值对
things.set('?', 'Wrench').set('?', 'Guitar').set('?', 'Joystick');const myMap = new Map();// 甚至键名可以是另一个 Map
things.set(myMap, 'Oh gosh!');
things.size; // 4
things.get(myMap); // 'Oh gosh!'复制代码

可以通过传入包含两个元素的数组来初始化 Map

const funArray = [['?', 'Champagne'],['?', 'Lollipop'],['?', 'Confetti'],
];let funMap = new Map(funArray);
funMap.get('?'); // Champagne
复制代码

WeakMap

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。它最重要的特性是 WeakMap 保持了对键名所引用的对象的弱引用

我们可以通过 Node 来证明一下这个问题:

// 允许手动执行垃圾回收机制
node --expose-gcglobal.gc();
// 返回 Nodejs 的内存占用情况,单位是 bytes
process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4Mlet map = new Map();
let key = new Array(5 * 1024 * 1024); // new Array 当为 Obj
map.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46751472 注意这里大约是 44.6M// 所以当你设置 key = null 时,只是去掉了 key 对 Obj 的强引用
// 并没有去除 arr 对 Obj 的强引用,所以 Obj 还是不会被回收掉
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M// 这句话其实是无用的,因为 key 已经是 null 了
map.delete(key);
global.gc();
process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M
复制代码
node --expose-gcglobal.gc();
process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4Mconst wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M// 当我们设置 key = null 的时候,就只有 wm 对所引用对象的弱引用
// 下次垃圾回收机制执行的时候,该引用对象就会被回收掉。
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M
复制代码

应用场景

传统使用 jQuery 的时候,我们会通过 $.data() 方法在 DOM 对象上储存相关信息(就比如在删除按钮元素上储存帖子的 ID 信息),jQuery 内部会使用一个对象管理 DOM 和对应的数据,当你将 DOM 元素删除,DOM 对象置为空的时候,相关联的数据并不会被删除,你必须手动执行 $.removeData() 方法才能删除掉相关联的数据,WeakMap 就可以简化这一操作:

let wm = new WeakMap(), element = document.querySelector(".element");
wm.set(element, "data");let value = wm.get(elemet);
console.log(value); // dataelement.parentNode.removeChild(element);
element = null;
复制代码

WeakSet

特性与 WeakMap 相似

遍历器(Iterators + For..Of)

遍历器(Iterator)它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 Iterator 的作用有三个:

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列;
  • ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable

let fibonacci = {[Symbol.iterator]() {let pre = 0, cur = 1;return {next() {[pre, cur] = [cur, pre + cur]; // 数组解构return { done: false, value: cur }}}}
}for (var n of fibonacci) {// 当n超过1000时停止if (n > 1000)break;console.log(n);
}
复制代码

上面代码中,对象 fibonacci 是可遍历的(iterable),因为具有 Symbol.iterator 属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有 next 方法。每次调用 next 方法,都会返回一个代表当前成员的信息对象,具有 valuedone 两个属性

原生具备 Iterator 接口的数据结构如下

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

for...in 和 for..of 的差别

  • for...in 遍历键名(Key)并转化为字符串,for...of 遍历键值(Value)
  • for...in 语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非 Symbol 的属性
  • for...in 更适合遍历对象,for...of 更适合遍历数组。
for (let i in [1, 2, 3]) {console.log(typeof i); // string 数组下标被转化字符串console.log(i); // '1', '2', '3'
}var triangle = { a: 1, b: 2, c: 3 };function ColoredTriangle() {this.color = 'red';
}ColoredTriangle.prototype = triangle;var obj = new ColoredTriangle();for (var prop in obj) {if (obj.hasOwnProperty(prop)) { // 如果去了 hasOwnProperty() 这个约束条件会怎么样?console.log(`obj.${prop} = ${obj[prop]}`); // obj.color = red}
}
复制代码

新增 API(Math + Number + String + Object APIs)

我们先来看看新增的 Number.EPSILON,不少人都是懵逼的状态,WTF?
先来看看的 JS 世界中的一道送命题

0.1 + 0.2 // 结果0.30000000000000004 而不是0.3
复制代码

事出必有因,这是因为 JS 的数值采用了 IEEE 754 标准,而且 JS 是弱类型语言,所以数字都是以64位双精度浮点数据类型储存。也就是说,JS 语言底层根本没有整数,所有数字都是小数!当我们以为在用整数进行计算时,都会被转换为小数

而浮点数都是以多位二进制的方式进行存储的

十进制的0.1用二进制表示为:0.0 0011 0011 0011 0011…,循环部分是0011
十进制0.2用二进制表示为:0.0011 0011 0011 0011…,循环部分是0011

由于存储空间有限,最后计算机会舍弃后面的数值,所以我们最后就只能得到一个近似值

JS中采用的 IEEE 754 的双精度标准也是一样的道理在存储空间有限的情况下,当出现这种无法整除的小数的时候就会取一个近似值,在 JS 中如果这个近似值足够近似,那么 JS 就会认为他就是那个值。

console.log(0.1000000000000001)
// 0.1000000000000001 (中间14个0,不会被近似处理,输出本身)
console.log(0.10000000000000001)
// 0.1 (中间15个0,js会认为两个值足够近似,所以输出0.1)
复制代码

那么这个近似的界限如何判断呢?

ES6的 Number.EPSILON就是一个界限,它表示 1 与大于 1 的最小浮点数之间的差。

对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的1.00..001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方

Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"
复制代码

Number.EPSILON 实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

0.1 + 0.2 - 0.3
// 5.551115123125783e-175.551115123125783e-17.toFixed(20)
// '0.00000000000000005551'
复制代码
0.00000000000000005551 < 0.00000000000000022204 // true
复制代码

显然,0.30000000000000004 不存在误差,不会被近似处理

我们可以通过以下手段来达到我们想要的效果

function withinErrorMargin (left, right) {return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
复制代码

其他一些新增的 API

Number.isInteger(Infinity) // false
Number.isNaN("NaN") // falseMath.sign(-5) // 判断一个数到底是正数、负数、还是零 -1
Math.hypot(3, 4) // 返回所有参数的平方和的平方根 5
Math.imul(-2, -2) // 返回两个数以 32 位带符号整数形式相乘的结果 4"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"Array.from(document.querySelectorAll("*")) // 返回一个真正的数组
Array.of(1, 2, 3) // [1,2,3]
[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) }) // 合并对象
复制代码

二进制和八进制字面量(Binary and Octal Literals)

0b111 === 7 // true  二进制
0o111 === 73 // true  八进制
0x111 === 273 // true  十六进制
复制代码

进阶篇

尾递归(Tail Calls)

假设现在要实现一个阶乘函数,即 5!= 120,我们很容易想到递归实现

function factorial(n) {if (n === 1) return 1;return n * factorial(n - 1);
}
复制代码

但递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。

何为调用记录,在示例代码中,由于最后一步返回了一个表达式,内存会保留 n 这个变量的信息和 factorial(n - 1) 调用下一次函数的位置,形成一层层的调用栈

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身,返回函数本身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。尾递归优化如下

function factorial(n, acc = 1) {
    "use strict";if (n <= 1) return acc;return factorial(n - 1, n * acc);
}factorial(100000)
复制代码

由此可见,"尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署"尾调用优化"。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

ES6的尾调用优化只在严格模式下开启,正常模式是无效的。

反射(Reflect)

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect 对象的设计目的有这样几个。

  • 将Object对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上
  • 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回 false
var O = {a: 1};
Reflect.defineProperty(O, 'b', {value: 2});
O[Symbol('c')] = 3;Reflect.ownKeys(O); // ['a', 'b', Symbol(c)]
Reflect.getOwnPropertyDescriptor(O, 'b');
// { value: 2, writable: false, enumerable: false, configurable: false }
function C(a, b){this.c = a + b;
}
var instance = Reflect.construct(C, [20, 22]);
instance.c; // 42
复制代码

获取属性名的方法有很多,以上面的代码为例子,它们的区别如下

方法 结果 解释
Object.getOwnPropertyNames(O) [ 'a', 'b' ] 获取除 Symbol 外的所有属性
Object.getOwnPropertySymbols(O) [ Symbol(c) ] 只获取 Symbol 属性
OReflect.ownKeys(O) [ 'a', 'b', Symbol(c) ] 获取所有属性
for...in a 获取除 Symbol 外的可枚举属性

代理(Proxy)

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

// 代理一个对象
var target = {};
var handler = {get: function (receiver, name) {return `Hello, ${name}!`;}
};var p = new Proxy(target, handler);
p.world; // "Hello, world!"
复制代码
// 代理一个函数
var target = function () { return "I am the target"; };
var handler = {apply: function (receiver, ...args) {return "I am the proxy";}
};var p = new Proxy(target, handler);
p(); // "I am the proxy"
复制代码
// 代理会将所有应用到它的操作转发到这个对象上
let target = {};
let p = new Proxy(target, {});p.a = 37;
target.a; // 37 操作转发到目标
复制代码
// 如何实现 a == 1 && a == 2 && a == 3,利用Proxy的get劫持
const a = new Proxy({},{val: 1,get() {return () => this.val++;}}
);
a == 1 && a == 2 && a == 3; // true
复制代码

由于 ES5 的限制,Proxy 不能被 transpiled or polyfilled,自己亲自入的坑,由于在项目中使用了 Mobx5.x,其内部是用 Proxy 写的,结果 IE11 不支持 ES6,只得回退版本 Mobx 到 4.x

生成器(Generators)

Generator 函数是 ES6 提供的一种异步编程解决方案。 Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function 关键字与函数名之间有一个星号;二是,函数体内部使用 yield 表达式,定义不同的内部状态

function* helloWorldGenerator() {yield 'hello'; // yield使Generator函数暂停了执行,并将结果返回给调用者yield 'world'; // 当下一次调用时,从它中断的地方恢复执行return 'ending';
}var hw = helloWorldGenerator();
a = hw.next(); // { value: 'hello', done: false }
b = hw.next(); // { value: 'world', done: false }
c = hw.next(); // { value: 'ending', done: true }
复制代码

可以利用这种暂停执行的特性,来实现惰性求值

向Generator传递数据

function* sayFullName() {const firstName = yield;const secondName = yield;console.log(firstName + ' ' + secondName);
}
let fullName = sayFullName();
fullName.next();
// 第一次调用,代码暂停在 const firstName = yield,因为没有通过 yield 发送任何值,因此 next 将返回 undefined
fullName.next('Handsome');
// 第二次调用,传入了值 Handsome,yield 被 Handsome 替代,因此 firstName 的值变为 Handsome,代码执行恢复
// 直到再次遇到 const secondName = yield 暂停执行
fullName.next('Jack');
// 第三次调用,传入了值 Jack,yield 被 Jack 替代,因此 secondName 的值变为 Jack,代码执行恢复
// 打印 Handsome Jack
复制代码

使用Generator处理异步调用

let generator;
let getDataOne = () => {setTimeout(() => {generator.next('dummy data one');}, 1000);
};
let getDataTwo = () => {setTimeout(() => {generator.next('dummy data one');}, 1000);
};function* main() {let dataOne = yield getDataOne();let dataTwo = yield getDataTwo();console.log(dataOne, dataTwo);
}
generator = main();
generator.next();
// 执行 getDataOne(),然后 yield 暂停
// 直至一秒后 generator.next('dummy data one') 恢复代码执行,并赋值 dataOne
console.log('i am previous print');
// i am previous print
// dummy data one dummy data one
复制代码

Promises

Promises 是一个异步编程的解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

function timeout(duration = 0) {return new Promise((resolve, reject) => {setTimeout(resolve, duration);})
}var p = timeout(1000).then(() => {return timeout(2000);
}).then(() => {throw new Error("hmm");
}).catch(err => {return Promise.all([timeout(100), timeout(200)]);
})
复制代码

这里强调几点

  • 不要剥夺函数 return 的能力,很多人写 Promise,照样有大量嵌套,掉进 Promise 地狱,要记得及时 return,避免嵌套
  • 当需要多个请求全部结束时,才更新数据,可以用 Promise.all(fetch1,fetch2)
  • 当需要从多个请求中,接受最先返回数据的那个请求,可以用 Promise.race(fetch1,fetch2)

结尾

ES6 是 ECMAScript 一个非常重要的版本,我们必须深入理解,不仅能提高我们书写代码的能力,还能增强业务能力

附上一张我之前精心整理的思维导图

本文参考资料

  • Learn ES2015
  • ECMAScript 6 入门
  • ES6 系列之 WeakMap

转载于:https://juejin.im/post/5cf0ebc26fb9a07ee27afc60

什么?ES6 中还有 Tail Calls!相关推荐

  1. ES6中export及export default的区别

    在ES6中,export和export default均可用于导出常量.函数.文件.模块等,你可以在其他文件或模块中通过import + (常量 | 函数 | 文件 | 模块)名的方式将其导入,以便能 ...

  2. 三,ES6中需要注意的特性(重要)

    -----书接上文,前文中我们总结了关于JS的组成部分以及如何快速开展学习JS,相信已经有很多朋友掌握到这些方法.接下来就从更深的ECMAScript开始说起. 1.什么是ES6? ECMAScrip ...

  3. 33 ES6中的类和对象

    技术交流QQ群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder的程序人生 1.面向对象 面向对象的思维特点: a.抽取(抽象)对象共有的属性和行为组织(封装)成一个 ...

  4. ES6中的class是如何实现的?(附Babel编译的ES5代码详解)

    序言 这篇文章主要讲解面试某大厂遇到的一个问题 - ES6中的class语法的实现? ECMAScript 6 实现了class,class是一个语法糖,使得js的编码更清晰.更人性化.风格更接近面向 ...

  5. [译] ES6+ 中的 JavaScript 工厂函数(第八部分)

    本文讲的是[译] ES6+ 中的 JavaScript 工厂函数(第八部分), 原文地址:JavaScript Factory Functions with ES6+ 原文作者:Eric Elliot ...

  6. es6中组件属性的形式

    var info = {name : 'sisi',school : 'hust' }; var MyInfo = React.createClass({render: function() {ret ...

  7. 使对象具有ES6中Iterator接口的实现方法

    es6中只有具有iterator接口的数组或者类数组(arguments)都可以使用for of来循环,但是对于对象来说不行,可以利用迭代器中的原理来给对象生成一个迭代器,实现让对象可以使用for o ...

  8. ES6中表达export default const是无效的

    问题 如果您是ES6新手,可以参考一下本文--高手请移驾别往! 请先看下面的图形描述: 也就是说,ES6中default后面是不允许跟const关键字的. 分析 上图中表达可以更换成另一种形式,就可以 ...

  9. ES6中新增字符串方法,字符串模板

    ES6中新增字符串方法,字符串模板 多了两个新方法 startsWith endsWith 返回的是一个Boolean值 let str='git://www.baidu.com/2123123';i ...

最新文章

  1. Spring 注解 @bean 和 @component 的区别, 你知道吗?
  2. Java 面试高频题:Spring Boot+Sentinel+Nacos高并发已撸完
  3. control.add()
  4. Java 中pdf部分内容加边线_Java 在PDF中添加骑缝章示例解析
  5. 漫谈数据仓库之维度建模
  6. 282. 给表达式添加运算符
  7. 5.6亿人没有存款、人均负债13万,大数据揭示负债累累的年轻人
  8. Cortex-M3异常
  9. Objective-C的动态特性
  10. 汉字字符编码的科普笔记(GB2312汉字编码,Unicode与UTF-8,字符映射表,vim,文泉驿,正则表达式)
  11. RF无线射频电路设计难点分析
  12. abaqus实例手册_《ABAQUS 6.14超级学习手册》——1.5 ABAQUS帮助文档
  13. 类似endnote_除了EndNote,竟还有如此强大的文献管理软件!重点是正版免费!
  14. SE11字段过多时怎么快速建表
  15. 移动设备管理与OMA DM 协议 V5(3)
  16. 搜站 - 聚合搜索,一站访问
  17. 找个好人就嫁了吧 - 刘思伟
  18. SpringUtil 工具类
  19. ios11手机怎么投屏到电脑屏幕上
  20. python geany是什么_Geany

热门文章

  1. inner join on, left join on, right join on的区别与介绍
  2. php中OpenFlashChart使用之线性图表使用例子
  3. concurrent: CyclicBarrier
  4. 哈佛大学 CS50,全美最受欢迎的计算机课程!
  5. 孤陋寡闻了吧?Python 居然可以做这30件神奇好玩的事情(附教程)
  6. 60道Python面试题答案精选!找工作前必看
  7. html+下拉箭头样式,HTML选择下拉箭头样式
  8. linux 字符串转数字排序,linux中sort命令排序功能实现方法
  9. rhel6.2安装oracle11g,RHEL 6.2 x86_64 下安装Oracle 11g步骤
  10. 家族关系查询系统程序设计算法思路_【学习笔记】数据库基础 - 查询优化