ES6(ES2015)
ES6是一次重大的革新,比起过去的版本,改动比较大,本文仅对常用的API进行讲解。
1.Let
和 Const
在ES6以前,JS只有var一种声明方式,但是在ES6之后,就多了let跟const这两种方式。用var定义的变量没有块级作用域的概念,而let跟const则会有。let 声明的变量在所在的代码块中有效,而 const 一旦声明,就不能再次赋值。这样能够提升代码的可读性和可维护性。
let variable1 = 10;
const variable2 = 'Hello world!';
variable1 = 20;
variable2 = 'Goodbye!' // 报错
2.类(Class)
在ES6之前,如果我们要生成一个实例对象,传统的方法就是写一个构造函数,例子如下:
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.information = function () {
return 'My name is ' + this.name + ', I am ' + this.age
}
但是在ES6之后,我们只需要写成以下形式:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
information() {
return 'My name is ' + this.name + ', I am ' + this.age
}
}
3.箭头函数(Arrow function)
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this
,arguments
,super
或 new.target
。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。
在ES6以前,我们写函数一般是:
var list = [1, 2, 3, 4, 5, 6, 7]
var newList = list.map(function (item) {
return item * item
})
但是在ES6里,我们可以:
const list = [1, 2, 3, 4, 5, 6, 7]
const newList = list.map(item => item * item)
看,是不是简洁了不少
4.函数参数默认值(Function parameter defaults)
在ES6之前,如果我们写函数需要定义初始值的时候,需要这么写:
function config (data) {
var data = data || 'data is empty'
}
这样看起来也没有问题,但是如果参数的布尔值为false
时就会出问题,例如我们这样调用config
:
config(0)
config('')
那么结果就永远是后面的值
如果我们用函数参数默认值就没有这个问题,写法如下:
const config = (data = 'data is empty') => {}
5.模板字符串(Template string)
在ES6之前,如果我们要拼接字符串,则需要像这样:
var name = 'kris'
var age = 24
var info = 'My name is ' + this.name + ', I am ' + this.age
但是在ES6之后,我们只需要写成以下形式:
const name = 'kris'
const age = 24
const info = `My name is ${name}, I am ${age}`
6. 解构赋值(Destructuring assignment)
我们通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。
比如我们需要交换两个变量的值,在ES6之前我们可能需要:
var a = 10
var b = 20
var temp = a
a = b
b = temp
但是在ES6里,我们有:
let a = 10
let b = 20
[a, b] = [b, a]
确实方便很多
7.模块化(Module)
在ES6之前,JS并没有模块化的概念,有的也只是社区定制的类似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:
// circle.js
// 输出
const { PI } = Math
exports.area = (r) => PI * r ** 2
exports.circumference = (r) => 2 * PI * r
// index.js
// 输入
const circle = require('./circle.js')
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`)
在ES6之后我们则可以写成以下形式:
// circle.js
// 输出
const { PI } = Math
export const area = (r) => PI * r ** 2
export const circumference = (r) => 2 * PI * r
// index.js
// 输入
import {
area
} = './circle.js'
console.log(`半径为 4 的圆的面积是: ${area(4)}`)
在调用导出的函数area
时,不需要声明变量来承载该函数,可直接调用。
8.扩展操作符(Spread operator)
扩展操作符可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。
比如在ES5的时候,我们要对一个数组的元素进行相加,在不使用reduce
或者reduceRight
的场合,我们需要:
function sum(x, y, z) {
return x + y + z;
}
var list = [5, 6, 7]
var total = sum.apply(null, list)
但是如果我们使用扩展操作符,只需要如下:
const sum = (x, y, z) => x + y + z
const list = [5, 6, 7]
const total = sum(...list)
非常的简单,但是要注意的是扩展操作符只能用于可迭代对象
如果是下面的情况,是会报错的:
var obj = {'key1': 'value1'}
var array = [...obj] // TypeError: obj is not iterable
因为当你尝试使用扩展运算符(spread operator)…来展开一个对象时,会报错 TypeError: obj is not iterable
。这是因为对象本身不是可迭代的(iterable),而扩展运算符只能用于可迭代的对象,如数组、字符串、Map、Set等。
9.对象属性简写(Object attribute shorthand)
在ES6之前,如果我们要将某个变量赋值为同样名称的对象元素,则需要:
var cat = 'Miaow'
var dog = 'Woof'
var bird = 'Peet peet'
var someObject = {
cat: cat,
dog: dog,
bird: bird
}
但是在ES6里我们就方便很多:
let cat = 'Miaow'
let dog = 'Woof'
let bird = 'Peet peet'
let someObject = {
cat,
dog,
bird
}
console.log(someObject)
//{
// cat: "Miaow",
// dog: "Woof",
// bird: "Peet peet"
//}
10.Promise
Promise
是ES6提供的一种异步解决方案,比回调函数更加清晰明了。
Promise
翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:
1.等待中(pending)2.完成了 (resolved)3.拒绝了(rejected)
这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变
new Promise((resolve, reject) => {
resolve('success')
// 无效
reject('reject')
})
当我们在构造 Promise
的时候,构造函数内部的代码是立即执行的
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh
Promise
实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包装成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})
当然了,Promise 也很好地解决了回调地狱的问题,例如:
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax(url2, () => {
// 处理逻辑
})
})
})
可以改写成:
ajax(url)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
11. for…of
for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
例子如下:
const array1 = ['a', 'b', 'c'];
for (const element of array1) {
console.log(element)
}
// "a"
// "b"
// "c"
ES7(ES2016)
1.Array.prototype.includes()
includes()
方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
代码如下:
const array1 = [1, 2, 3]
console.log(array1.includes(2)) // true
const pets = ['cat', 'dog', 'bat']
console.log(pets.includes('cat')) // true
console.log(pets.includes('at')) // false
2.幂运算符**
幂运算符**,具有与Math.pow()一样的功能,代码如下:
console.log(2**10) // 1024
console.log(Math.pow(2, 10)) // 1024
ES8(ES2017)
1.async/await
虽然Promise可以解决回调地狱的问题,但是链式调用太多,则会变成另一种形式的回调地狱 —— 面条地狱,所以在ES8里则出现了Promise的语法糖async/await,专门解决这个问题。
我们先看一下下面的Promise代码:
fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message)
})
然后再看看async/await版的,这样看起来是不是更清晰了。
async function myFetch() {
let response = await fetch('coffee.jpg')
let myBlob = await response.blob()
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
}
myFetch()
当然,如果你喜欢,你甚至可以两者混用
async function myFetch() {
let response = await fetch('coffee.jpg')
return await response.blob()
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
2.Object.values()
Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
代码如下:
const object1 = {
a: 'somestring',
b: 42,
c: false
}
console.log(Object.values(object1)) // ["somestring", 42, false]
3.Object.entries()
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
代码如下:
const object1 = {
a: 'somestring',
b: 42
}
for (let [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`)
}
// "a: somestring"
// "b: 42"
4.padStart()
padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。
代码如下:
const str1 = '5'
console.log(str1.padStart(2, '0')) // "05"
const fullNumber = '2034399002125581'
const last4Digits = fullNumber.slice(-4)
const maskedNumber = last4Digits.padStart(fullNumber.length, '*')
console.log(maskedNumber) // "************5581"
5.padEnd()
padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
const str1 = 'Breaded Mushrooms'
console.log(str1.padEnd(25, '.')) // "Breaded Mushrooms........"
const str2 = '200'
console.log(str2.padEnd(5)) // "200 "
###函数参数结尾逗号(Function parameter lists and calls trailing commas)
在ES5里就添加了对象的尾逗号,不过并不支持函数参数,但是在ES8之后,便开始支持这一特性,代码如下:
// 参数定义
function f(p) {}
function f(p,) {}
(p) => {}
(p,) => {}
class C {
one(a,) {},
two(a, b,) {},
}
var obj = {
one(a,) {},
two(a, b,) {},
};
// 函数调用
f(p)
f(p,)
Math.max(10, 20)
Math.max(10, 20,)
但是以下的方式是不合法的:
仅仅包含逗号的函数参数定义或者函数调用会抛出 SyntaxError。而且,当使用剩余参数的时候,并不支持尾后逗号,例子如下:
function f(,) {} // SyntaxError: missing formal parameter
(,) => {} // SyntaxError: expected expression, got ','
f(,) // SyntaxError: expected expression, got ','
function f(...p,) {} // SyntaxError: parameter after rest parameter
(...p,) => {} // SyntaxError: expected closing parenthesis, got ','
在解构里也可以使用,代码如下:
// 带有尾后逗号的数组解构
[a, b,] = [1, 2]
// 带有尾后逗号的对象解构
var o = {
p: 42,
q: true,
}
var {p, q,} = o
同样地,在使用剩余参数时,会抛出 SyntaxError,代码如下:
var [a, ...b,] = [1, 2, 3] // SyntaxError: rest element may not have a trailing comma
ES9(ES2018)
1.for await…of
for await…of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如arguments 或者NodeList),TypedArray,Map, Set和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。
配合迭代异步生成器,例子如下:
async function* asyncGenerator() {
var i = 0
while (i < 3) {
yield i++
}
}
(async function() {
for await (num of asyncGenerator()) {
console.log(num)
}
})()
// 0
// 1
// 2
2.模板字符串(Template string)
ES9开始,模板字符串允许嵌套支持常见转义序列,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。
不过,非法转义序列在"cooked"当中仍然会体现出来。它们将以undefined元素的形式存在于"cooked"之中,代码如下:
function latex(str) {
return { "cooked": str[0], "raw": str.raw[0] }
}
latex`\unicode` // { cooked: undefined, raw: "\\unicode" }
3.对象扩展操作符
ES6中添加了数组的扩展操作符,让我们在操作数组时更加简便,美中不足的是并不支持对象扩展操作符,但是在ES9开始,这一功能也得到了支持,例如:
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }
上面便是一个简便的浅拷贝。这里有一点小提示,就是Object.assign() 函数会触发 setters,而展开语法则不会。所以不能替换也不能模拟Object.assign() 。
如果存在相同的属性名,只有最后一个会生效。
4.Promise.prototype.finally()
finally()方法会返回一个Promise,当promise的状态变更,不管是变成rejected或者fulfilled,最终都会执行finally()的回调。
例子如下:
fetch(url)
.then((res) => {
console.log(res)
})
.catch((error) => {
console.log(error)
})
.finally(() => {
console.log('结束')
})
ES10(ES2019)
1.Array.prototype.flat() / flatMap()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
flatMap()与 map() 方法和深度为1的 flat() 几乎相同.,不过它会首先使用映射函数映射每个元素,然后将结果压缩成一个新数组,这样效率会更高。
例子如下:
var arr1 = [1, 2, 3, 4]
arr1.map(x => [x * 2]) // [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]) // [2, 4, 6, 8]
// 深度为1
arr1.flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]
flatMap()可以代替reduce() 与 concat(),例子如下:
var arr = [1, 2, 3, 4]
arr.flatMap(x => [x, x * 2]) // [1, 2, 2, 4, 3, 6, 4, 8]
// 等价于
arr.reduce((acc, x) => acc.concat([x, x * 2]), []) // [1, 2, 2, 4, 3, 6, 4, 8]
但这是非常低效的,在每次迭代中,它创建一个必须被垃圾收集的新临时数组,并且它将元素从当前的累加器数组复制到一个新的数组中,而不是将新的元素添加到现有的数组中。
2.String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight()
在ES5中,我们可以通过trim()来去掉字符首尾的空格,但是却无法只去掉单边的,但是在ES10之后,我们可以实现这个功能。
如果我们要去掉开头的空格,可以使用trimStart()或者它的别名trimLeft(),
同样的,如果我们要去掉结尾的空格,我们可以使用trimEnd()或者它的别名trimRight()。
例子如下:
const Str = ' Hello world! '
console.log(Str) // ' Hello world! '
console.log(Str.trimStart()) // 'Hello world! '
console.log(Str.trimLeft()) // 'Hello world! '
console.log(Str.trimEnd()) // ' Hello world!'
console.log(Str.trimRight()) // ' Hello world!'
不过这里有一点要注意的是,trimStart()跟trimEnd()才是标准方法,trimLeft()跟trimRight()只是别名。
在某些引擎里(例如Chrome),有以下的等式:
String.prototype.trimLeft.name === "trimStart"
String.prototype.trimRight.name === "trimEnd"
3.Object.fromEntries()
Object.fromEntries() 方法把键值对列表转换为一个对象,它是Object.entries()的反函数。
例子如下:
const entries = new Map([
['foo', 'bar'],
['baz', 42]
])
const obj = Object.fromEntries(entries)
console.log(obj) // Object { foo: "bar", baz: 42 }
4.Symbol.prototype.description
description 是一个只读属性,它会返回Symbol对象的可选描述的字符串。与 Symbol.prototype.toString() 不同的是它不会包含Symbol()的字符串。例子如下:
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
Symbol().description; // undefined
// 具名 symbols
Symbol.iterator.toString(); // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"
//全局 symbols
Symbol.for('foo').toString(); // "Symbol(foo)"
Symbol.for('foo').description; // "foo"
5.String.prototype.matchAll
matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。并且返回一个不可重启的迭代器。例子如下:
var regexp = /t(e)(st(\d?))/g
var str = 'test1test2'
str.match(regexp) // ['test1', 'test2']
str.matchAll(regexp) // RegExpStringIterator {}
[...str.matchAll(regexp)] // [['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4], ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]]
6.Function.prototype.toString() 返回注释与空格
在以往的版本中,Function.prototype.toString()得到的字符串是去掉空白符号的,但是从ES10开始会保留这些空格,如果是原生函数则返回你控制台看到的效果,例子如下:
function sum(a, b) {
return a + b;
}
console.log(sum.toString())
// "function sum(a, b) {
// return a + b;
// }"
console.log(Math.abs.toString()) // "function abs() { [native code] }"
7.try-catch
在以往的版本中,try-catch里catch后面必须带异常参数,例如:
// ES10之前
try {
// tryCode
} catch (err) {
// catchCode
}
但是在ES10之后,这个参数却不是必须的,如果用不到,我们可以不用传,例如:
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
8.BigInt
BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。
可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()。
在以往的版本中,我们有以下的弊端:
// 大于2的53次方的整数,无法保持精度
2 ** 53 === (2 ** 53 + 1)
// 超过2的1024次方的数值,无法表示
2 ** 1024 // Infinity
但是在ES10引入BigInt之后,这个问题便得到了解决。
以下操作符可以和 BigInt 一起使用: +、*、-、**、% 。除 >>> (无符号右移)之外的位操作也可以支持。因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigInt。BigInt 不支持单目 (+) 运算符。
/ 操作符对于整数的运算也没问题。可是因为这些变量是 BigInt 而不是 BigDecimal ,该操作符结果会向零取整,也就是说不会返回小数部分。
BigInt 和 Number不是严格相等的,但是宽松相等的。
所以在BigInt出来以后,JS的原始类型便增加到了7个,如下:
•Boolean•Null•Undefined•Number•String•Symbol (ES6)•BigInt (ES10)
9.globalThis
globalThis属性包含类似于全局对象 this值。所以在全局环境下,我们有:
globalThis === this // true
ok 以上就是ES6到ES10常见的api,有不同意见的小伙伴可以友善讨论哦!