简介
JavaScript
是世界上发展最快的编程语言之一,不仅可以用于编写运行在浏览器的客户端程序,随着Node.js
的发展,JavaScript
也被广泛应用于编写服务端程序。而随着JavaScript
这门语言的不断发展和完善,在2015
年正式发布的ECMAScript6
已经成为了JavaScript
这门语言的下一代标准,使得JavaScript
用来编写复杂的大型应用程序更加的得心应手。近几年几乎所有使用JavaScript
这门语言开发的项目,都在使用ES
的新特性来开发。
随着ES2015
的发布,标准委员会决定在每年都会发布一个ES
的新版本。但很多开发者并没有真正的理解ES2015+
每个版本都具有哪些新特性,以及这些新特性与ES5
语法的差别,更不清楚这些新特性在实际项目中的应用场景是怎么样的。
由于篇幅原因笔者将ES6~ES12
分成了ES进阶之路一和ES进阶之路二两篇文章,如果对ES6
还不是很清楚了可以先看ES6(ES进阶之路一)
我相信只要你们认真看完了笔者的ES
系列文章,你一定会成为ES
大神。
接下来我们来看看ES7-ES12
吧。篇幅有点长,建议收藏后再看,后期也可以当做字典查阅。
ECMAScript2016(ES7)
Array扩展
Array.prototype.includes()
在 ES7
之前想判断数组中是否包含一个元素,基本可以这样写:
console.log(array1.find(function(item) {
return item === 2
}))
或者
console.log(array1.findIndex(function(item) {
return item === 2
}))
或者
console.log(array1.indexOf(2) > -1)
或者
console.log(array1.filter(function(item) {
return item === 2
}).length > 0)
ES7
引入的Array.prototype.includes()
方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true
,否则返回false
。
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es6')) // true
console.log(arr.includes('es9')) // false
includes
方法接收俩个参数,要搜索的值和搜索的开始索引。第二个参数可选。从该索引处开始查找 searchElement
。如果为负值,则从末尾第几个开始查找。
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es7', 1)) // true
console.log(arr.includes('es7', 2)) // false
console.log(arr.includes('es7', -1)) // false
console.log(arr.includes('es7', -2)) // true
与indexOf()
比较
indexOf
返回的不是boolean
值,而是下标或-1。
['a', 'b', 'c'].includes('a') //true
['a', 'b', 'c'].indexOf('a') > -1 //true
console.log(arr.indexOf('es7')) // 1
console.log(arr.indexOf('es7') > -1) // true
includes
方法只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些是无法判断的.
const arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
- 两者都是采用
===
的操作符来作比较的,不同之处在于:对于NaN
的处理结果不同。我们知道js
中NaN === NaN
的结果是false
,indexOf()
也是这样处理的,但是includes()
不是这样的。
const demo = [1, NaN, 2, 3]
demo.indexOf(NaN) //-1
demo.includes(NaN) //true
总结
如果只想知道某个值是否在数组中存在,而并不关心它的索引位置,建议使用includes()
。如果想获取一个值在数组中的位置,那么只能使用indexOf
方法。
幂运算符**
如果不使用任何函数,如何实现一个数的求幂运算?
function pow(x, y) {
let res = 1
for (let i = 0; i < y; i++) {
res *= x
}
return res
}
pow(2, 10) // 1024
除了自己封装函数来实现,也可是使用 Math.pow()
来完成。
Math.pow() 函数返回基数(base)的指数(exponent)次幂。
console.log(Math.pow(2, 10)) // 1024
在 ES7
可以这样写了:
console.log(2 ** 10) // 1024
注意,幂运算符的两个*号之间不能出现空格,否则语法会报错。
ECMAScript2017(ES8)
Async/Await
在ES2017(ES8)
中引入了 async
函数,使得异步操作变得更加方便。Async/Await
的出现,被很多人认为是js
异步操作的最终且最优雅的解决方案。我们可以简单理解Async/Await = Generator + Promise
。
语法
async
用于声明一个 function
是异步的,await
用于等待一个异步方法执行完成,只有当异步完成后才会继续往后执行。await
不是必须的并且await
只能出现在 async
函数中。
async function() {
const result = await getData()
console.log(result)
}
一个函数如果加上 async ,那么该函数就会返回一个 Promise
async function async1() {
return "1"
}
console.log(async1()) // -> Promise {<resolved>: "1"}
这种用书写同步代码的方式处理异步是不是很舒服呢。
错误处理
Async/Await
没有Promise
那么多的api
,错误需要自己使用try catch
处理。
async function() {
try{
const result = await getData()
console.log(result)
} catch(e) {
console.log(e)
}
}
Async/Await和Promise对比
-
Async/Await
相较于Promise
的链式调用完全用书写同步代码的方式处理异步使代码分厂优雅易懂。 -
Async/Await
这种用书写同步代码的方式使得await
会阻塞后面代码正常运行,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。
下面笔者使用Async/Await
和Promise
作为对比举个例子说明。
function getData() {
return Promise.resolve("模拟获取后端数据");
}
async function fun1() {
console.log("主程序开始执行");
const result = await getData();
console.log(result);
console.log("让异步代码自己去执行,不阻塞我们主程序");
}
fun1(); // 主程序开始执行、模拟获取后端数据、让异步代码自己去执行,不阻塞我们主程序
async function fun2() {
console.log("主程序开始执行");
getData().then((result) => {
console.log(result);
});
console.log("让异步代码自己去执行,不阻塞我们主程序");
}
fun2(); // 主程序开始执行、让异步代码自己去执行,不阻塞我们主程序、模拟获取后端数据
从上面的例子我们可以看出使用Async/Await
的弊端,就是不管后面依不依赖异步结果,Async/Await
都一定会阻塞后面代码的执行。
Async/Await和Generator对比
-
Async/Await
内置执行器。Generator
函数的执行必须靠执行器(如co
函数库),而Async/Await
函数自带执行器。也就是说,Async/Await 函数的执行,与普通函数一模一样。 -
Async/Await
更好的语义。async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
Object 扩展
之前的语法如何获取对象的每一个属性值
const obj = {
name: 'randy',
age: 24
}
console.log(Object.keys(obj)) // ['name', 'age']
const res = Object.keys(obj).map(key => obj[key])
console.log(res)// ["randy", 24]
ES8
中对象扩展补充了两个静态方法,用于遍历对象:Object.values(),Object.entries()
Object.values()
Object.values()
返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for...in
,但是for...in
还会遍历原型上的属性值)。
const obj = {
name: 'randy',
age: 24
}
console.log(Object.values(obj)) // ["randy", 24]
Object.entries()
Object.entries()
方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in
循环遍历该对象时返回的顺序一致。(区别在于 for-in
循环也枚举原型链中的属性)
const obj = {
name: 'randy',
age: 24
}
for (let [key, value] of obj) {
console.log(key, value) // Uncaught TypeError: obj is not iterable
}
我们知道 Object
是不可直接遍历的,上述代码足以说明直接遍历触发了错误。如果使用 Object.entries()
则可以完成遍历任务。
const obj = {
name: 'randy',
age: 24
}
for (let [k, v] of Object.entries(obj)) {
console.log(k, v)
// name randy
// age 24
}
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors
用来获取对象所有属性的描述符。
想理解 Object.getOwnPropertyDescriptors
这个方法之前,首先要弄懂什么是描述符descriptor
?
const data = {
Portland: '78/50',
Dublin: '88/52',
Lima: '58/40'
}
还是上述那个对象,这里有 key
和 value
,上边的代码把所有的 key、value
遍历出来,如果我们不想让 Lima
这个属性和值被枚举怎么办?
Object.defineProperty(data, 'Lima', {
enumerable: false
})
Object.entries(data).map(([city, temp]) => {
console.log( `City: ${
city.padEnd(16)} Weather: ${
temp}` )
// City: Portland Weather: 78/50
// City: Dublin Weather: 88/52
})
很成功,Lima
没有被遍历出来,那么 defineProperty
的第三个参数就是描述符descriptor
。这个描述符包括几个属性:
- value [属性的值]
- writable [属性的值是否可被改变]
- enumerable [属性的值是否可被枚举]
- configurable [描述符本身是否可被修改,属性是否可被删除]
在ES6
中笔者就已经讲过了,关于Object
更多API
可以看看笔者前面写的JS Object API详解
console.log(Object.getOwnPropertyDescriptor(data, 'Lima'))
// {value: "58/40", writable: true, enumerable: false, configurable: true}
这个是获取对象指定属性的描述符,如果想获取对象的所有属性的描述符:
console.log(Object.getOwnPropertyDescriptors(data))
String 扩展
在 ES8
中 String
新增了两个实例函数 String.prototype.padStart
和 String.prototype.padEnd
,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
String.prototype.padStart()
把指定字符串填充到字符串头部,返回新字符串。
str.padStart(targetLength [, padString])
参数 | 含义 | 必选 |
---|---|---|
targetLength | 目标字符要保持的长度值 | Y |
padString | 如果目标字符的长度不够需要的补白字符,默认为空 | N |
const str = 'randy'
console.log(str.padStart(8, 'x')) // xxxrandy
console.log(str.padEnd(8, 'y')) // randyyyy
console.log(str.padStart(8)) // randy
场景1:日期格式化,希望把当前日期格式化城:yyyy-mm-dd的格式:
const now = new Date()
const year =</