ES7在ES6的基础上添加了三项内容: 加粗样式求幂运算符(**)、Array.prototype.includes()方法、函数作用域中严格模式的变更。
includes()的作用,是查找一个值在不在数组里,若在,则返回true,反之返回false。 基本用法:
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].includes('d') // false
Array.prototype.includes()方法接收两个参数:要搜索的值和搜索的开始索引。当第二个参数被传入时,该方法会从索引处开始往后搜索(默认索引值为0)。若搜索值在数组中存在则返回true,否则返回false。 且看下面示例:
['a', 'b', 'c', 'd'].includes('b') // true
['a', 'b', 'c', 'd'].includes('b', 1) // true
['a', 'b', 'c', 'd'].includes('b', 2) // false
那么,我们会联想到ES6里数组的另一个方法indexOf,下面的示例代码是等效的:
['a', 'b', 'c'].includes('a') //true
['a', 'b', 'c'].indexOf('a') > -1 //true
此时,就有必要来比较下两者的优缺点和使用场景了。
简便性
从这一点上来说,includes
略胜一筹。熟悉indexOf
的同学都知道,indexOf
返回的是某个元素在数组中的下标值,若想判断某个元素是否在数组里,我们还需要做额外的处理,即判断该返回值是否>-1。而includes
则不用,它直接返回的便是Boolean
型的结果。
精确性
两者使用的都是===
操作符来做值的比较。但是includes()方法有一点不同,两个NaN被认为是相等的,即使在NaN === NaN
结果是false的情况下。这一点和indexOf()
的行为不同,indexOf()
严格使用===
判断。请看下面示例代码:
let demo = [1, NaN, 2, 3]
demo.indexOf(NaN) //-1
demo.includes(NaN) //true
上述代码中,indexOf()
方法返回-1
,即使NaN存在于数组中,而includes()
则返回了true。
提示:由于它对NaN
的处理方式与indexOf
不同,假如你只想知道某个值是否在数组中而并不关心它的索引位置,建议使用includes()
。如果你想获取一个值在数组中的位置,那么你只能使用indexOf
方法。
includes()
还有一个怪异的点需要指出,在判断 +0
与 -0
时,被认为是相同的。
[1, +0, 3, 4].includes(-0) //true
[1, +0, 3, 4].indexOf(-0) //1
在这一点上,indexOf()
与includes()
的处理结果是一样的,前者同样会返回 +0
的索引值。
基本用法
3 ** 2 // 9
效果同:
Math.pow(3, 2) // 9
** 是一个用于求幂的中缀算子,比较可知,中缀符号比函数符号更简洁,这也使得它更为可取。 下面让我们扩展下思路,既然说**是一个运算符,那么它就应该能满足类似加等的操作,我们姑且称之为幂等,例如下面的例子,a的值依然是9:
let a = 3
a **= 2 // 9
众所周知,JavaScript语言的执行环境是“单线程”的,那么异步编程对JavaScript语言来说就显得尤为重要。以前我们大多数的做法是使用回调函数来实现JavaScript语言的异步编程。回调函数本身没有问题,但如果出现多个回调函数嵌套,例如:进入某个页面,需要先登录,拿到用户信息之后,调取用户商品信息,代码如下:
this.$http.jsonp('/login', (res) => {
this.$http.jsonp('/getInfo', (info) => {
// do something
})
})
假如上面还有更多的请求操作,就会出现多重嵌套。代码很快就会乱成一团,这种情况就被称为“回调函数地狱”(callback hell)
。
于是,我们提出了Promise
,它将回调函数的嵌套,改成了链式调用。写法如下:
var promise = new Promise((resolve, reject) => {
this.login(resolve)
})
.then(() => this.getInfo())
.catch(() => { console.log("Error") })
从上面可以看出,Promise
的写法只是回调函数的改进,使用then
方法,只是让异步任务的两段执行更清楚而已。Promise
的最大问题是代码冗余,请求任务多时,一堆的then
,也使得原来的语义变得很不清楚。此时我们引入了另外一种异步编程的机制:Generator
。
Generator
函数是一个普通函数,但是有两个特征 :
一是,function
关键字与函数名之间有一个星号;
二是,函数体内部使用yield表达式
,定义不同的内部状态(yield
在英语里的意思就是“产出”)。一个简单的例子用来说明它的用法:
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代码定义了一个 Generator
函数helloWorldGenerator
,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)
。Generator
函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator
函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator
函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。上述代码分步执行如下:
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator函数的机制更符合我们理解的异步编程思想。
用户登录的例子,我们用Generator来写,如下:
var gen = function* () {
const f1 = yield this.login()
const f2 = yield this.getInfo()
};
虽然Generator将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。此时,我们便希望能出现一种能自动执行Generator函数的方法。我们的主角来了:
ES8引入了async函数,使得异步操作变得更加方便。简单说来,它就是Generator函数的语法糖。
async function asyncFunc(params) {
const result1 = await this.login()
const result2 = await this.getInfo()
}
异步函数存在以下四种使用形式:
函数声明: async function foo() {}
函数表达式: const foo = async function() {}
对象的方式: let obj = { async foo() {} }
箭头函数: const foo = async () => {}
常见用法汇总
处理单个异步结果:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
顺序处理多个异步结果:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
并行处理多个异步结果:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2()
]);
console.log(result1, result2);
}
处理错误:
async function asyncFunc() {
try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
如果一个对象是具有键值对的数据结构,则每一个键值对都将会编译成一个具有两个元素的数组,这些数组最终会放到一个数组中,返回一个二维数组。简言之,该方法会将某个对象的可枚举属性与值按照二维数组的方式返回。若目标对象是数组时,则会将数组的下标作为键值返回。例如:
Object.entries({ one: 1, two: 2 }) //[['one', 1], ['two', 2]]
Object.entries([1, 2]) //[['0', 1], ['1', 2]]
注意:键值对中,如果键的值是Symbol,编译时将会被忽略。例如:
Object.entries({ [Symbol()]: 1, two: 2 }) //[[‘two’, 2]]
Object.entries()返回的数组的顺序与for-in循环保持一致,即如果对象的key值是数字,则返回值会对key值进行排序,返回的是排序后的结果。例如:
Object.entries({ 3: 'a', 4: 'b', 1: 'c' })
//[['1', 'c'], ['3', 'a'], ['4', 'b']]
使用Object.entries(),我们还可以进行对象属性的遍历。例如:
let obj = { on``e: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}
//输出结果如下:
'one': 1
'two': 2
它的工作原理跟Object.entries()很像,顾名思义,它只返回自己的键值对中属性的值。它返回的数组顺序,也跟Object.entries()保持一致。
Object.values({ one: 1, two: 2 }) //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' }) //['c', 'a', 'b']
10分钟学会ES7+ES8