据说情侣学完这些 ES2015+ 常用新特性永远不分手
ES2015 也叫 ES6,区别只是一个是以发布的年份来命名,一个是以版本号来命名
从那以后组织每年都会发布一个新版本,根据这个规则,ES2016 === ES7... ES2020 === ES11
但通常我习惯将 ES2015 及其后续版本统称为 ES2015+,也可以叫 ESNext
变量声明
ES2015 增加了两个声明变量标识符的关键字,let
和 const
,两者都支持块级作用域,并且在声明之前不能访问
凡是不需要重新赋值的变量标识符都可以使用 const
关键字来声明
其余需要重新赋值的变量就使用 let
关键字来声明,像循环计数器之类的
{
// 声明之前无法访问
const arr = ['a', 'b', 'c', 'd', 'e']
const length = arr.length
for (let i = 0; i < length; i++) {
//
}
}
// 块之外无法访问
使用 const 声明变量可以防止变量重复声明或者重新赋值
const fn = () => {}
const fn = () => {} // SyntaxError
使用 const 声明变量的时候必须声明的同时赋值
对象字面量扩展和原型相关
对象字面量的简写形式以及计算属性
const foo = 1
const obj = {
foo, // 属性简写,等同于 foo: foo,
bar() {
// 方法简写
console.log(this.name)
},
// 计算属性
['na' + 'me']: 'by.Genesis',
__proto__: 原型 // 访问原型
}
这些特性都可以简化原本的代码
__proto__
属性用来访问原型,不过并不推荐使用,应该使用 Object.getPrototypeOf(o)
和 Object.setPrototypeOf(o, proto)
方法
const obj = Object.create({ name: 'by.Genesis' })
// 设置新的原型
Object.setPrototypeOf(obj, { age: 18 })
// 获取原型
Object.getPrototypeOf(obj) // { age: 18 }
箭头函数
箭头(=>)就是一种函数简写方式,同时提供一些有用的特性
// 当函数有且仅有一个参数的时候可以省略参数的圆括号
;[1, 2, 3].forEach(item => { console.log(item) })
// 当函数体内只有一条语句的时候可以省略函数体的花括号,同时隐式返回该条语句
const sum = (x, y) => x + y
// 等同于
const sum2 = (x, y) => {
return x + y
}
// 如果隐式返回的是一个对象字面量,为了消除歧义,可以使用一对圆括号包裹对象字面量
const pos = (x, y) => ({
x: x + 1,
y: y * 2
})
// 词法 this
const obj = {
name: 'by.Genesis',
showName() {
setTimeout(() => {
console.log(this.name) // obj.showName() this === obj
}, 300)
}
}
立即执行箭头函数表达式
;(() => {
alert(101)
})()
类
使用类(class)代替构造函数,避免被直接调用
class Person {}
Person() // TypeError
同函数一样,类也可以作为表达式赋值给一个变量,或者作为参数传给函数,甚至从函数中返回
const Person = class {}
私有属性和方法,
class Person {
#name
#getName() {
return this.#name
}
}
无论是用类声明还是表达式,都需要先定义,然后再使用,不会提升,不会提升
符号
将符号用作对象的 key,可以防止意外的命名冲突
// 创建 Symbol,不需要 new
// 传入的参数作为该 Symbol 的描述符
const name = Symbol('name')
const o = {
[name]: 'by.Genesis'
}
通过 typeof 操作符就可以判断符号类型
typeof name === 'symbol'
对象的符号属性无法通过传统的方法遍历出来,需要的时候可以使用 Object.getOwnPropertySymbols()
方法获取参数对象中所有符号属性组成的数组
Object.getOwnPropertySymbols(o) // [Symbol(name)]
修改数组 concat 方法的行为
// 当数组作为 concat 参数时,默认会被展开
const arr = [4, 5, 6]
;[1, 2, 3].concat(arr) // [1, 2, 3, 4, 5, 6]
// 可以修改此行为让数组参数不展开
arr[Symbol.isConcatSpreadable] = false
;[1, 2, 3].concat(arr) // [1, 2, 3, [4, 5, 6]]
// 也可以让类数组对象展开
;[1, 2, 3].concat({
[Symbol.isConcatSpreadable]: true,
length: 3,
0: 4,
1: 5,
2: 6
}) // [1, 2, 3, 4, 5, 6]
承诺
// 立即创建一个 fulfilled 的 promise
const p1 = Promise.resolve('101真狗')
// 立即创建一个 rejected 的 promise
const p2 = Promise.reject(404)
合并一组异步,全部 resolve
Promise.all([p1, p2])
一组异步竞速,最快那个
Promise.race([p1, p2])
一组异步竞速,最快 resolve 那个
Promise.any([p1, p2])
合并一组异步,无所谓 resolve 还是 reject
Promise.allSettled([p1, p2])
迭代器和生成器
创建一个可迭代对象(Iterable)
iterable = {
*[Symbol.iterator]() { // 这里同时使用了对象方法简写,计算属性以及生成器
yield 1
yield 2
yield 3
}
}
可迭代对象具有特殊的符号 [Symbol.iterator]
方法,并且该方法返回一个迭代器
String,Array,Map,Set,NodeList 等等都是可迭代对象,迭代器自身也是可迭代对象,迭代器的 [Symbol.iterator]
方法返回自身
生成器可以通过 yield*
委托给其它可迭代对象
iterable = {
*[Symbol.iterator]() {
yield 1
yield* [2, 3]
}
}
可迭代对象可以使用 for of
语法遍历
for (let v of iterable) {
console.log(v) // 1 2 3
}
异步函数
异步函数(Async function)在函数前面添加一个 async
关键字,其内部可以使用 await
关键字
await
表达式可以将其后面的 Promise resolve 的值提取出来
// 通过函数声明的方式定义
async function foo() {
const x = await Promise.resolve(101)
return x
}
// 表达式
const bar = async () => {
const x = await Promise.resolve(101)
return x
}
// 对象方法
const o = {
async fn() {
const x = await Promise.resolve(101)
return x
}
}
// 执行异步函数会返回一个 Promise,就算本来是原始值也会包装成一个 Promise
foo().then(res => console.log(res)) // 101
异步函数对比承诺
;(async () => {
let loading = true
try {
const response = await fetch('/api')
const data = await response.json()
执行业务
} catch (err) {
捕获错误
} finally {
loading = false
}
})()
let loading = true
fetch('/api').then(response => {
return response.json()
}).then(data => {
执行业务
}).catch(err => {
捕获错误
}).finally(() => {
loading = false
})
顶级 await
从 ES2022 开始,await
关键字不再必须用于异步函数中了
const x = await Promise.resolve(101)
异步迭代
创建一个异步迭代器(Async Iterator)
asyncIterator = {
next() {
return Promise.resolve({
done: false,
value: 10
})
}
}
异步迭代器的 next
方法返回一个 Promise,并且该 Promise resolve 后可以得到一个包含 done
和 value
两个属性的结果对象
创建一个异步生成器(Async Generator)
async function *g() {
yield 1
const a = await new Promise(resolve => {
setTimeout(resolve, 3000, 2)
})
yield a
yield Promise.resolve(a + 1)
}
// 执行异步生成器返回一个异步迭代器
asyncIterator = g()
异步生成器内部可以同时使用 await
和 yield
关键字
创建一个异步可迭代对象(Async Iterable)
asyncIterable = {
async *[Symbol.asyncIterator]() {
yield 1
const a = await new Promise(resolve => {
setTimeout(resolve, 3000, 2)
})
yield a
yield Promise.resolve(a + 1)
}
}
异步可迭代对象具有特殊的符号 [Symbol.asyncIterator]
方法,并且该方法返回一个异步迭代器
异步可迭代对象使用 for await of
语法遍历
;(async () => {
for await (let v of asyncIterable) {
console.log(v) // 1 2 3
}
})()
在 ES2022 之前,await
应该放到异步函数中
Map & Set
利用 Set 的唯一性给数组去重
const arr = [...new Set([1, 2, 2, 3])]
Map 和 Set 都是可迭代对象,既可以通过自身的 forEach
方法遍历其中的值,也可以使用 for of
语法
const s = new Set([1, 2, 2, 3])
s.forEach(v => {
console.log(v)
})
const m = new Map([['a', 1], ['b', 2]])
for (let [key, val] of m) {
console.log(key, val)
}
可以把 Set 看作是 key 和 value 为同一个值的特殊 Map,也可以认为 Set 是只有 key
Map 和 Set 遍历顺序和添加时的顺序是一致的,因此都是有序集合
WeakSet & WeakMap
弱版本只能用来存放引用类型
WeakMap 只对其 key 有类型要求,而 value 可以是任意类型
弱版本不是可迭代对象,不能遍历,也没有 size 属性,也不能用 clear 方法清空集合,只具备最基本的添加,删除等方法
弱版本是弱引用,其优势就是利于垃圾回收
解构
通过对可迭代对象解构可以一次性对多个变量赋值
let [a, b, c] = [1, 2]
a === 1
b === 2
c === undefined
通过数组结构交换两个变量的值
;[a, b] = [b, a] // a = 2, b = 1
解构时可以跳过一些不需要的值
;[, , c] = '123' // c = '3'
结构时可以指定别名,防止变量重复
const a = 1
const { a: b } = { a: 2 } // b = 2
默认值
在声明函数参数或者解构的时候都可以指定一个默认值,当对应的值为 undefined
的时候,就会使用这个默认值
// 函数参数默认值
const sum = (x, y = 4) => x + y
sum(3) === 7
// 迭代器解构的默认值
const [a, b = 2] = [1]
a === 1
b === 2
// 对象解构的默认值
const { name = 'by.Genesis' } = { age: 18 }
name === 'by.Genesis'
// 函数参数和对象解构一起使用
const fn = ({ height = 18, width = 36 } = {}) => {}
fn() // height = 18, width = 36
fn({ height: 36 }) // height = 36, width = 36
fn({ height: 36, width: 18 }) // height = 36, width = 18
Spread & Rest
可迭代对象均可使用展开(Spread)运算符(...)展开为独立的值,这些值可以作为函数的参数或放到数组中
// 展开可迭代对象作为函数参数
Math.max(...[5, 20, 10]) === 20
// 展开可迭代对象到一个数组中
const arr = [...new Set([1, 2, 2, 3])] // [1, 2, 3]
// 展开可迭代对象到一个数组中
const newArr = [1, ...[2, 3], ...'45'] // [1, 2, 3, '4', '5']
而普通对象也可以展开其属性,放到另一个对象中,这和 Object.assign
方法作用类似
// 展开对象属性到另一个对象中
const o = {
a: 1,
...{
b: 2,
c: 3
}
} // o = { a: 1, b: 2, c: 3 }
// 等同于
const o = Object.assign({
a: 1
},
{
b: 2,
c: 3
}) // o = { a: 1, b: 2, c: 3 }
和展开相反,多个值可以使用收集(Rest)运算符(...)打包成一个数组,或者多个对象属性打包成一个对象
// 函数剩余参数打包成一个数组
const fn = (x, ...y) => y.length
fn(2, 5, 7, 11) === 3 // x = 2, y = [5, 7, 11]
// 可迭代对象剩余值打包成一个数组
const [a, ...b] = new Set([2, 5, 7, 11])
a === 2
// b = [5, 7, 11]
// 对象剩余属性打包成一个对象
const { a, ...o } = {
a: 1,
b: 2,
c: 3
} // o = { b: 2, c: 3 }
收集运算符只能用于最后一个标识符
模板字符串
模板字符串就是功能更强大的字符串,它支持多行以及插值
在模板字符串中插值使用 ${}
花括号里面可以插入表达式,表达式甚至可以是另一个模板字符串
const str = `
<ul>
${lists.map(item => {
return `<li>${item.user} is ${item.age} years old.</li>`
}).join('')}
</ul>
`
标签模板
const username = 'by.Genesis'
const age = 18
const str = tag`${username} is ${age} years old.`
// tag 就是一个函数
// 第一个参数为字符串按插值分割而成的数组
// 后面的参数为插值表达式的值
// 可以自行处理字符串逻辑
function tag(template, ...substitutions) {
console.log(template) // ['', ' is ', ' years old.']
console.log(substitutions) // ['by.Genesis', 18]
return substitutions[0] + template[1] + 'handsome'
}
str === 'by.Genesis is handsome'
代理和反射
代理(Proxy)就是为一个目标对象生成一个代理,当对这个代理对象执行一些操作的时候,就会触发对应的拦截器,在拦截器中可以自行定义操作和返回的值,或者用反射(Reflect)执行元操作,每个代理方法都有对应的反射方法
const obj = {}
const proxy = new Proxy(obj, {
get(target, key) {
// 属性取值
if (key === 'name') {
// 自定义返回值
return 'by.Genesis'
} else {
// 用反射还原操作
return Reflect.get(target, key)
}
},
set() { 属性赋值 },
has() { in 操作符 },
deleteProperty() { 删除属性 },
getPrototypeOf() { 获取原型 },
setPrototypeOf() { 设置原型 },
defineProperty() { Object.defineProperty },
getOwnPropertyDescriptor() { Object.getOwnPropertyDescriptor },
preventExtensions() { Object.preventExtensions },
isExtensible() { Object.isExtensible },
ownKeys() { Object.keys, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.assign },
enumerable() { for in 循环 },
apply() { 函数普通调用 },
construct() { new 方式调用函数 }
})
proxy.name === 'by.Genesis'
obj.name === undefined
以上是这些拦截器以及对应的触发条件
Nullish coalescing Operator
JavaScript 里面的假值(Falsy)有 null, undefined, 0, '', NaN, false
,除假值外都为真值(Truthy)
而空值(Nullish)只有 null
和 undefined
,当该运算符左侧为空值时返回右侧
null ?? 1 // 1
undefined ?? 1 // 1
0 ?? 1 // 0
0 || 1 // 1
Optional chaining
链式操作时,当中间某个值是 null
或者 undefined
就会报错,而这个操作符可以让链式操作更安全
const o = {}
o.p.q // Uncaught TypeError: Cannot read property 'q' of undefined
o.p?.q // undefined
计算属性也适用,需要额外添加 .
o?.['p']?.['q']
逻辑赋值
// 逻辑或赋值
a ||= b // 当 a 为假值时赋值
a || a = b
// 逻辑与赋值
a &&= b // 当 a 为真值时赋值
a && a = b
// 逻辑空赋值
a ??= b // 当 a 为 null 或者 undefined 时赋值
a ?? a = b
模块
在 ES 模块(Modules) 问世之前,已经有各种定义模块的规范了,比如 AMD,CommonJS 等,ES 模块提供语言层面的支持
使用 export
关键字导出模块,使用 import
关键字导入模块
// 可以同时导出多个具名模块
export const sum = (x, y) => x + y
export const name = 'by.Genesis'
// 导入具名模块时名称必须和导出时一致
// 另外可以使用 as 关键字指定别名
import { sum, name as username } from './example.js'
// 也可以先声明再导出,导出时也可以使用 as 关键字指定别名
// 指定别名后,导入的时候就需要使用这个别名了
export { sum, name as username }
// 一个模块只允许有一个默认导出
export default { name: 'by.Genesis' }
// 导入默认模块可以任意命名
import o from './example.js'
// 同时导入默认模块和具名模块
import o, { sum } from './example.js'
// 全部导入并指定一个别名,所有模块都会成为这个别名的属性
import * as m from './example.js'
m.default // 默认模块是 default 属性
m.sum // 具名模块就是自己的名字
// 从另一个模块中导入再导出
export * from './another.js'
// 直接导入,不指定任何命名
import './example.js'
// 甚至还可以不导出任何东西,仅仅只是执行一些代码而已
数字
// 非无穷
Number.isFinite(101) // true
Number.isFinite(NaN) // false
// 安全整数
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) === true
// 二进制数字 0b 开头(binary),八进制数字 0o 开头(octonary)
// 前面是数字零,后面是字母,大小写都可以,但是为了便于区别,建议使用小写
0b1001 === 0o11
// 新增指数运算符(两个乘号),主要是给指数运算一个正儿八经的运算符,而不是去调用方法
2 ** 3 === Math.pow(2, 3)
2 ** 3 === 2 * 2 * 2
// 数字中任意位置可以添加下划线分割数字,增加数字的可读性
123_4567_8889 === 12345678889 // 123亿4567万8889
1_000_000 === 1000000 // 一百万
BigInt
大整数用来表示安全整数范围之外的整数,数字字面量后面添加一个 n
const num = 9007199254740992n
typeof num === 'bigint'
字符串方法
// 重复几次
'xyz'.repeat(3) // 'xyzxyzxyz'
// 判断开头
'http://xyz.io/'.startsWith('http') === true
// 判断结尾
'avator.jpg'.endsWith('.jpg') === true
// 判断包含,和 indexOf 的区别
'xyz'.includes('yz') === true // includes 方法返回布尔值
'xyz'.indexOf('yz') === 1 // indexOf 方法返回数字索引
// 首尾填充,第一个参数为填充后长度,第二个参数为填充字符串
'2'.padStart(2, '0') // '02'
// 字符串已经达到长度则不填充
'12'.padStart(2, '0') // '12'
// 常见用于时间展示
// 首尾去空白
' xyz '.trimStart() === 'xyz '
' xyz '.trimLeft() === 'xyz '
' xyz '.trimEnd() === ' xyz'
' xyz '.trimRight() === ' xyz'
// 字符串全部替换
'xyx'.replaceAll('x', 'z') === 'zyz'
// replace 方法只会替换一次
'xyx'.replace('x', 'z') === 'zyx'
// 多次替换需要使用全局正则
'xyx'.replace(/x/g, 'z') === 'zyz'
Object.is
可以判断 NaN
Object.is(NaN, NaN) // true
NaN === NaN // false
可以判断 0 和 -0
Object.is(0, -0) // false
0 === -0 // true
对象方法
// Object.keys() 的补充方法
// 获取对象的 value 组成一个数组
Object.values({ x: 1, y: 2 }) // [1, 2]
// 获取对象的键值对组成一个数组
Object.entries({ x: 1, y: 2 }) // [['x', 1], ['y', 2]]
// 根据键值对数组生成一个对象
Object.fromEntries([['x', 1], ['y', 2]]) // { x: 1, y: 2 }
// 获取对象全部自身属性描述
Object.getOwnPropertyDescriptors({ x: 1, y: 2 }) // { x: { value: 1, writable: true, enumerable: true, configurable: true }, y: { value: 2, ... } }
Array.of
// 创建只有一个数字值的数组
Array.of(3) // [3]
// 构造函数只会创建长度为传入数字的稀疏数组
new Array(3) // [empty × 3]
Array.from
将类数组或者可迭代对象转换为数组
Array.from($('.modal'))
Array.from({
length: 5
}).map((item, index) => index + 1) // [1, 2, 3, 4, 5]
Array#fill
填充数组
new Array(3).fill('x', 1) // [empty, 'x', 'x']
Array#includes
判断数组是否包含某个值
[NaN, 1, 2].includes(NaN) === true
[NaN, 1, 2].indexOf(NaN) === -1
Array#find
数组中查找元素
[1, 2, NaN].find(item => item !== item) // NaN
['x', 'y', NaN].findIndex(item => item !== item) === 2
// 复制到指定位置
// 这是一个变异方法,直接在原数组上进行修改
// Array#copyWithin(target, start, ?end)
[1, 2, 3, 4, 5, 6].copyWithin(3, 0, 3) // [1, 2, 3, 1, 2, 3]
// 扁平化
[1, [2, [3, [4]]]].flat(2) // [1, 2, 3, [4]]
// 不知道到底有多少层?那就无限大好了
[1, [2, [3, [4]]]].flat(Infinity) // [1, 2, 3, 4]
// flatMap 相当于 map + flat(1)
// 会自动扁平化一层
// 这个方法可以让返回的数组变长,这是普通 map 无法合理办到的
[1, 2, 3, 4].flatMap(x => [x, x * x]) // [1, 1, 2, 4, 3, 9, 4, 16]
本文来自博客园,作者:by.Genesis,转载请注明原文链接:https://www.cnblogs.com/xyzhanjiang/p/13530369.html