JavaScript ES7/8/9 新特性
ES7 新特性 (ECMAScript 2016)
ES7 在 ES6 的基础上主要添加了两项内容:
- Array.prototype.includes() 方法
- 求幂运算符(**)
Array.prototype.includes() 方法
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
let arr = [1, 2, 3];
console.log(arr.includes(3))
// true
复制代码
includes() 接受第二个参数,数值型,表示从当前数组的第几个索引开始查找。
['a', 'b', 'c', 'd'].includes('b', 1) // true
['a', 'b', 'c', 'd'].includes('b', 2) // false
复制代码
对比 indexOf() 方法:
let arr = ['a', 'b', 'c', 'd', NaN]
console.log(arr.indexOf('c')); // 2
console.log(arr.indexOf('f')); // -1
console.log(arr.indexOf(NaN)); // -1
console.log(arr.includes(NaN)); // true
// 判断条件
if (arr.indexOf('b') !== -1) {
console.log('数组存在 b');
}
if (arr.includes('b')) {
console.log('数组存在 b');
}
复制代码
从上可以看出,若一个数组中存在 NaN, 则 indexOf() 无法判断,includes() 则可以。 if 条件判断的时候,includes() 也要简单一些。
求幂运算符(**)
ES7 中,使用 **
来替代 Math.pow().
4 ** 3 // 64
复制代码
效果等同于:
Math.pow(4, 3) // 64
复制代码
还支持一下写法:
let n = 4
n **= 3
console.log(n) // 64
复制代码
ES8 新特性 (ECMAScript 2017)
主要新功能:
- 异步函数 Async Functions
- 共享内存和Atomics(暂没研究)
次要新功能:
- Object.values / Object.entries
- String padding
- Object.getOwnPropertyDescriptors() (没研究)
- 函数参数列表和调用中的尾逗号
异步处理 - async / await
async 起什么作用
问题在于 async 是怎么处理返回值的, 写段代码测试一下,看看会返回什么。
async function testAsync() {
return "hello world"
}
const result = testAsync()
console.log(result)
复制代码
结果表明 --- 输出的是一个 Promise 对象.
所以,async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
那么:
testAsync().then(v => {
console.log(v); // hello world
})
复制代码
猜想一下,如果 async 没有返回值,又该怎样呢? 则,它会返回 Promise.resolve(undefined)
await 是干什么的呢
一般来说,都认为 await 是在等待一个 async 函数完成。 我认为,因为 async 是返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数。
function getSomething () {
return 'something'
}
async function testAsync () {
return Promise.resolve("hello world")
}
async function test () {
const v1 = await getSomething()
const v2 = await testAsync()
console.log(v1, v2);
}
test()
复制代码
awiat 是一个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
async / await 尝试
用 setTimeout 模拟耗时的异步操作,与 promise 比较;
- promise 实现
function takeLongTime() {
return new Promsie(resolve => {
setTimeout(() => {
resolve("hello world")
}, 1500)
})
}
takeLongTime.then(v => {
console.log(v) // hello world
})
复制代码
- async / await 实现
function takeLongTime() {
return new Promsie(resolve => {
setTimeout(() => {
resolve("hello world")
}, 1500)
})
}
async function test() {
const result = await takeLongTime()
console.log(result)
}
test()
复制代码
这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显。下面测试 promise 含有多个 then 链的情况
async/await 处理 then 链
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:
// 处理多个 promise 组成的 then 链
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLoneTime(n) {
return new Promise(resolve => {
setTimeout(() => {
resolve(n + 200)
}, n)
})
}
function step1(n) {
console.log(`step1 with ${n}`)
return takeLoneTime(n)
}
function step2(n) {
console.log(`step2 with ${n}`)
return takeLoneTime(n)
}
function step3(n) {
console.log(`step3 with ${n}`)
return takeLoneTime(n)
}
复制代码
- promise 实现
function doIt() {
console.time("doIt")
const time1 = 500
step1(time1).then(time2 => step2(time2))
.then(time3 => step3(times))
.then(result => {
console.log(result)
console.timeEnd("doIt")
})
}
// step1 with 500
// step2 with 700
// step3 with 900
// result is 1100
// doIt: 2114.56689453125ms (500 + 700 + 900)
复制代码
- async/await 实现
async function doIt() {
console.time("doIt")
const time1 = 500
const time2 = await step1(time1)
const time3 = await step2(time2)
const result = await step3(time3)
console.log(`result is ${result}`)
console.timeEnd("doIt")
}
// step1 with 500
// step2 with 700
// step3 with 900
// result is 1100
// doIt: 2113.227783203125ms
复制代码
async/await 用法简洁了许多。
那么,现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
function takeLoneTime(n) {
return new Promise(resolve => {
setTimeout(() => {
resolve(n + 200)
}, n)
})
}
function step1(n) {
console.log(`step1 with ${n}`)
return takeLoneTime(n)
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`)
return takeLoneTime(m + n)
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`)
return takeLoneTime(k + m + n)
}
复制代码
- async/await 实现
async function doIt() {
console.time("doIt")
const time1 = 500
const time2 = await step1(time1)
const time3 = await step2(time2, time1)
const result = await step3(time3, time2, time1)
console.timeEnd("doIt")
console.log(`result is ${result}`)
}
// step1 with 300
// step2 with 500 and 300
// step3 with 1000, 500 and 300
// result is 2000
// doIt: 2908.927978515625ms (300 + 800 + 1800)
复制代码
- promise 实现
function doIt() {
console.time("doIt")
let time1 = 300, time2, time3
step1(time1)
.then(param => step2(time1, time2 = param))
.then(param => step3(time1, time2, time3 = param))
.then(param => console.log(`result is ${param}`))
console.timeEnd("doIt")
}
// step1 with 300
// step2 with 300 and 500
// step3 with 300, 500 and 1000
// result is 2000
// doIt: 2917.6201171875ms
复制代码
Object.values and Object.entries
ES5 中引入了 Object.keys() 方法,返回一个指定对象自己的所有的可遍历的属性的键名的 数组。
let obj = { a: 'aaa', b: 'bbb' }
console.log(Object.keys(obj)); // ["a", "b"]
复制代码
- Object.values() 返回一个给定对象自己的所有可枚举的属性的 value 值的数组。对象自身可遍历
const obj = { x: 'hello', y: 'world' }
console.log(Object.values(obj)) // ["hello", "world"]
const arr = ['a', 'b', 'c']
console.log(Object.values(arr)) // ["a", "b", "c"]
// 如果键 是使用数字时,则返回的是数字排序
const obj = { 10: 'aaa', 1: 'bbb', 5: 'ccc' }
console.log(Object.values(obj)) // ["bbb", "ccc", "aaa"]
// 为字符串时,将每个字符切割成数组的每一项
console.log(Object.values('abc2')) // ["a", "b", "c", "2"]
复制代码
- Object.entries() 方法返回一个对象自身可遍历属性 [key, value] 的数组,排序规则与 Object.values() 一样。
const obj = { x: 'hello', y: 'world' }
console.log(Object.entries(obj)) // [[["x", "hello"]], ["y", "world"]]
const arr = ['a', 'b', 'c']
console.log(Object.entries(arr)) // [["0", "a"], ["1", "b"], ["2", "c"]]
// 如果键 是使用数字时,则返回的是数字排序
const obj = { 10: 'aaa', 1: 'bbb', 5: 'ccc' }
console.log(Object.entries(obj)) // [["1", "bbb"], ["5", "ccc"], ["10", "aaa"]]
// 为字符串时,将每个字符切割成数组的每一项
console.log(Object.entries('abc2')) // [["0", "a"], ["1", "b"], ["2", "c"], ["3", "2"]]
// 基本用途:遍历对象的属性
const obj = { one: 1, two: 2}
for (let [k, v] of Object.entries(obj)) {
console.log([k, v]);
// ["one", 1]
// ["two", 2]
}
复制代码
String - padStart / padEnd
String 对象增加的 2 个函数, 作用是填补字符串的首部和尾部。
str.padStart(targetLength, padString)
str.padEnd(targetLength, padString)
第一个参数 targetLength (目标长度), 想要的结果字符串的长度。 第二个参数 padString 是可选的,用于填充到源子符串。 默认是 空格。
结尾逗号
代码展示:
// 参数定义时
function foo(
param1,
param2,
) {}
// 函数调用时
foo(
'abc',
'def',
);
// 对象中
let obj = {
first: 'Jane',
last: 'Doe',
};
// 数组中
let arr = [
'red',
'green',
'blue',
];
复制代码
改动的好处呢?
- 操作最后一项的数据时,不必添加或删除逗号;
- 它可以帮助版本控制系统跟踪实际发生的变化。(网上查阅,不是很明白)
ES9 新特性 (ECMAScript 2018)
主要新功能:
- 异步迭代
- Rest/Spread 属性
新的正则表达式:
- RegExp named capture groups
- RegExp Unicode Property Escapes
- RegExp Lookbehind Assertions
- s (dotAll) flag for regular expressions
其他新功能:
- Promise.prototype.finally()
- 模板字符串修改
rest / spread 属性
ES6 中已经引入 rest 参数和扩展运算符,但是仅限于数组:
// rest参数 p3 为一个数组
function restParam(p1, p2, ...p3) {
console.log(p1, p2, p3);
}
restParam(1, 2, 3, 4, 5) // 1 2 [3, 4, 5]
// 扩展运算符: 将一个数组转为用逗号隔开的参数序列
const arr = [99, 100, -3, 45, 10]
console.log(Math.max(...arr)); // 100
复制代码
ES9 中,为对象提供了像数组一样的 rest 参数和扩展运算符
const obj = {
a: 1,
b: 2,
c: 3,
}
const { a, ...param } = obj
console.log(a); // 1
console.log(param); // { b: 2, c: 3 }
function foo({a, ...param}) {
console.log(a);
console.log(param);
}
foo(obj)
// 1
// { b: 2, c: 3 }
// 合并对象
const a = {one: 1, two: 2 }
const b= {three: 3}
const ab = { ...a, ...b }
console.log(ab); // {one: 1, two: 2, three: 3}
// 等价于 =》
console.log(Object.assign(a, b))
复制代码
Promise.prototype.finally()
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码