情侣看完这些 ES2015+ 常用新特性永远不分手
es2015 也叫 es6,区别只是一个是以发布的年份来命名,一个是以版本号来命名
从那以后组织每年都会发布一个新版本,根据这个规则,es2016 === es7... es2020 === es11
但通常我习惯将 es2015 及其后续版本统称为 es2015+,这不是一份完整的特性总结
变量声明
es2015 增加了两个声明变量标识符的关键字,let
和 const
,两者都支持块级作用域,并且在声明之前不能访问
凡是不需要重新赋值的变量标识符都可以使用 const
关键字来声明
其余需要重新赋值的变量就使用 let
关键字来声明,像循环计数器之类的
{ const arr = ['a', 'b', 'c', 'd', 'e'] const length = arr.length for (let i = 0; i < length; i++) { // } } // 块之外无法访问
对象字面量
对象字面量的简写形式以及计算属性
const foo = 1 const obj = { foo, // 属性简写,等同于 foo: foo, bar() { // 方法简写 }, // 计算属性 ['na' + 'me']: 'by.genesis', __proto__: 原型 }
这些特性都可以简化原本的代码
__proto__
用来 get/set 原型,不过并不推荐使用,应该使用 object.getprototypeof(o)
和 object.setprototypeof(o, proto)
箭头函数
箭头(=>)就是一种函数简写方式,同时提供一些有用的特性
// 当函数有且仅有一个参数的时候可以省略参数的圆括号 ;[1, 2, 3].foreach(item => { console.log(item) }) // 当函数体内只有一条语句的时候可以省略函数体的花括号,同时隐式返回该条语句 const sum = (x, y) => 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)就是传统的构造函数基于原型继承的语法糖
class person { constructor(name, age) { this.name = name this.age = age } update() { // 方法 } } // 继承 class student extends person { constructor(name, age, grade) { super(name, age) this.grade = grade } update() { // 调用父类的方法 super.update() } get foo() { // getter } set foo() { // setter } static baz() { // 静态方法通过 student.baz() 调用 } } const s1 = new student('by.genesis', 20, 2)
同函数一样,类也可以作为表达式赋值给一个变量,或者作为参数传给函数,甚至从函数中返回
const person = class { // }
无论是用类声明还是表达式,都需要先定义,然后再使用,不会提升,不会提升
symbol
符号(symbol)是一种新的原始类型,其没有字面量形式
符号可以分为3类,普通符号,全局符号和众所周知的符号(well-known symbol)
// 创建 symbol,不需要 new // 传入的参数作为该 symbol 的描述符 const name = symbol('name') // 将 symbol 用作对象的 key // 只能使用可计算属性名的方式 const o = { [name]: 'by.genesis' } // 通过 typeof 操作符判断值类型 typeof symbol('name') === 'symbol' // 符号值是唯一的,就算在创建时传入了相同的参数,得到的符号也不是同一个 symbol('name') !== symbol('name') // 不过在全局符号注册表中同一个 key 返回的是同一个符号 symbol.for('name') === symbol.for('name') // 获取符号描述符 symbol('name').description === 'name'
对象的符号属性无法通过传统的方法遍历出来,需要的时候可以使用 object.getownpropertysymbols()
方法获取参数对象中所有符号属性组成的数组
object.getownpropertysymbols(o) // [symbol(name)]
除此之外,还有一些 众所周知的符号(well-known symbol),这类符号的作用是暴露一些 javascript 内部操作
// 当数组作为 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]
promise
promise
主要用来表示一个未来值
// 创建一个 promise const p = new promise((resolve, reject) => { settimeout(resolve, 3000, 'by.genesis') }) // promise resolve 时执行 p.then(res => { console.log(res) // 'by.genesis' }) // 总是会执行,无论 resolve 还是 reject p.finally(() => console.log('finally')) // 立即创建一个 fulfilled 的 promise const p2 = promise.resolve('101真狗') // 立即创建一个 rejected 的 promise const p3 = promise.reject(404) // promise reject 时执行 p3.catch(err => { console.log(err) // 404 }) // 等待一组 promise 全部 resolve // 一旦有一个为 rejected 则立即 reject promise.all([p2, p3]) // 获取一组 promise 中最快的那一个,无论 resolve 还是 reject promise.race([p2, p3]) // 等待一组 promise 全部 settled,无论 resolve 还是 reject promise.allsettled([p2, p3]) // 获取一组 promise 中最快 resolve 的那一个 // 只有当全部都为 rejected 的时候 reject promise.any([p2, p3])
迭代器和生成器
当一个对象拥有一个 next
方法,并且调用该方法时可以得到一个包含 done
和 value
两个属性的结果对象,那么这就是一个迭代器(iterator)
iterator = { next() { return { done: false, value: 10 } } }
其中 done
为 boolean 类型,表示该迭代器是否已经迭代完毕
生成器(generator)是一种特殊函数,声明的时候在 function
关键字和函数名中间多了个星号(*)
生成器内通过 yield
关键字返回值
function *g() { yield 1 yield 2 yield 3 } // 调用生成器可以得到一个迭代器 iterator = g() // 调用迭代器的 next 方法执行生成器内部代码并得到结果对象 iterator.next() // { value: 1, done: false }
当一个对象具有特殊的符号 [symbol.iterator]
方法,并且该方法返回一个迭代器的时候,那么这个对象就是一个可迭代对象(iterable)
iterable = { *[symbol.iterator]() { // 这里同时使用了对象方法简写,计算属性以及生成器 yield 1 yield 2 yield 3 } }
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 fn() { const x = await promise.resolve(101) return x } // 执行异步函数也返回一个 promise fn().then(res => console.log(res)) // 101
异步函数就是生成器和 promise 语法糖
异步迭代
当一个迭代器的 next
方法返回一个 promise,并且该 promise resolve 后可以得到一个包含 done
和 value
两个属性的结果对象,那么这个迭代器就是一个异步迭代器(async iterator)
asynciterator = { next() { return promise.resolve({ done: false, value: 10 }) } }
将异步函数和生成器结合到一起,就是异步生成器(async generator),其内部可以同时使用 await
和 yield
关键字
async function *g() { yield 1 const a = await new promise(resolve => { settimeout(resolve, 3000, 2) }) yield a yield promise.resolve(a + 1) } // 执行异步生成器返回一个异步迭代器 asynciterator = g()
当一个对象具有特殊的符号 [symbol.asynciterator]
方法,并且该方法返回一个异步迭代器的时候,那么这个对象就是一个异步可迭代对象(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) } }
异步可迭代对象使用 for await of
语法遍历
;(async () => { for await (let v of asynciterable) { console.log(v) // 1 2 3 } })()
await
应该放到异步函数中
map & set
map 是包含键值对(key-value)的有序集合,其中 key 可以是 任意类型 (是任意类型,包括引用类型甚至 dom 元素都可以作为 map 的 key)
// 创建一个 map const m = new map([['a', 1], ['b', 2]]) // 添加值,如果已存在就是修改 m.set('c', 3) // 获取值 m.get('b') // 2 // 判断值 m.has('b') // true // 获取长度 m.size // 3 // 删除值 m.delete('b') m.has('b') // false m.size // 2 // 清空 m.clear() m.size // 0
set 就是一组不重复值的有序集合
// 创建一个 set const s = new set([1, 2]) // 添加值 s.add(3) s.add(1) // 该值已存在,集合保持不变 // 除了没有获取值的方法,剩下的和 map 一致
map 和 set 都是可迭代对象,既可以通过自身的 foreach
方法遍历其中的值,也可以使用 for of
语法
s.foreach(v => { console.log(v) }) 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 都生效 let [a, b, c] = [1, 2] a === 1 b === 2 c === undefined // 交换值 ;[a, b] = [b, a] // a = 2, b = 1 // 数组可以解构任意可迭代对象,包括字符串 // 解构时也可以跳过一些不需要的值 ;[, , c] = '123' // c = '3' // 对象属性解构 { x, y, z: { w } } = { x: 3, y: 4, z: { w: 5 } } x === 3 y === 4 w === 5
默认值
在声明函数参数或者解构的时候都可以指定一个默认值,当对应的值为 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 o2 = object.assign({ 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
逻辑赋值
// 逻辑或赋值 a ||= b // 当 a 为假值时赋值 a || a = b // 逻辑与赋值 a &&= b // 当 a 为真值时赋值 a && a = b // 逻辑空赋值 a ??= b // 当 a 为空值时赋值 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
bigint
大整数用来表示安全整数范围之外的整数,数字字面量后面添加一个 n
const num = 9007199254740992n typeof num === 'bigint'
字符串方法
// 重复几次 'xyz'.repeat(3) // 'xyzxyzxyz' // 判断开头 'http://xyz.io/'.startswith('http') === true // 判断结尾 'avator.jpg'.endswith('.jpg') === true // 判断包含 'xyz'.includes('yz') === true // 首尾填充,第一个参数为填充后长度,第二个参数为填充字符串 '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'
数组方法
静态方法
// 创建只有一个数字值的数组 array.of(3) // [3] // 构造函数只会创建长度为传入数字的稀疏数组 new array(3) // [empty × 3] // 将类数组或者可迭代对象转换为数组 array.from($('.modal')) array.from({ length: 5 }).map((item, index) => index + 1) // [1, 2, 3, 4, 5]
实例方法
// 填充数组,可以传入一个起始索引 new array(3).fill('x', 1) // [empty, 'x', 'x'] // 包含判断,可以传入一个起始索引 [nan, 1, 2].includes(nan) === true [nan, 1, 2].includes(nan, 1) === false // 数组中查找元素,返回找到的元素 [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]
其它方法
// 对象比较 object.is(nan, nan) // true object.is(0, -0) // false // object.keys() 补充方法 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, ... } }