JavaScript拾遗(4) | 青训营笔记

97 阅读8分钟

这是我参与「第四届青训营 」笔记创作活动的的第18天

ES7

指数运算符**

新增指数运算符**,也叫幂运算符,与Math.pow()有着一样的功能。

console.log(2**3) // 8

Array.prototype.includes()

数组原型上增加了includes()方法,该方法用于判断一个数组中是否包含指定的值,返回一个布尔值

const arr = [-0, 2, 3, 4, 5, NaN]
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes(NaN)) // true
console.log(arr.indexOf(+0)) // 0
console.log(arr.includes(+0)) // true

ES8

async/await语法糖

Promise的出现虽然解决了回调地狱的问题,但是如果链式调用特别多的话可读性还是会变差,在ES8中新增了async/await语法糖解决了这个问题。

;(async function () {
  function promise(v) {
    return new Promise((resolve, reject) => {
      resolve(v)
    })
  }
  const r1 = await promise(1)
  const r2 = await promise(r1)
  const res = await promise(r2)
  console.log(res)
})()

注意:ES13中可以单独使用await,而不需要配到async了

对象扩展

Object扩展了三个静态方法

  • Object.values():返回一个给定对象自身的所有可枚举属性值的数组;
  • Object.entries():返回一个给定对象自身可枚举属性的键值对数组;
  • Object.getOwnPropertyDescriptors():返回给定对象所有自有属性的属性描述符。

字符串扩展

String.prototype新增了两个方法

  • padStart():在字符串开头填充空格;
  • padEnd():在字符串结尾填充空格;
const str = 'abc'
console.log(str.padStart(10)) /*        abc */
console.log(str.padEnd(10))   /* abc        */

ES9

异步迭代

新增了for await...of语句,该用于可以遍历异步可迭代对象

const p1 = Promise.resolve().then(() => 1).finally(() => console.log(4));
const p2 = Promise.resolve().then(() => 2);
const p3 = Promise.resolve().then(() => 3);

;(async () => {
var p_arr = [p1, p2, p3]
for await (const p of p_arr) {
    console.log(p); //1,2,3,
}
for (const p of p_arr) {
    console.log(p); //p1, p2, p3
}
})();

Promise.prototype.finally()

finally()方法会返回一个Promise对象,当promise的状态变更,不管是变成rejected或者fulfilled,最终都会执行finally()的回调。

fetch('')
  .then(res => {
    console.log(res)
  })
  .catch(error => {
    console.log(error)
  })
  .finally(() => {
    console.log('结束')
  })

对象展开运算符

ES6中新增数组的展开运算符,在ES9中将这一特性加入到了对象中

const a = {
    a: 'a'
};
const b = {
    b: 'b'
};
const c = {...a, ...b};
console.log(c.a, c.b); // a b

正则的扩展

正则表达式分组命名:

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
 
const matchObj = RE_DATE.exec('2022-08-17')
const year = matchObj.groups.year // 2022
const month = matchObj.groups.month // 02
const day = matchObj.groups.day // 22

ES10

Object.fromEntries()

Object.fromEntries()方法把键值对列表转换为一个对象。是Object.entries()方法的逆操作。

const person = {
  name: 'name',
  age: 20,
}
const e = Object.entries(person)
/*
(2) [Array(2), Array(2)]
    0: (2) ['name', 'name']
    1: (2) ['age', 20]
*/
 
const p = Object.fromEntries(e)
// {name: 'name', age: 20}

trimStart()/trimLeft()和trimEnd()/trimRight()

  • String.prototype.trimStart:用于去除字符串左边的空格
  • String.prototype.trimLeft:它是trimStart的别名。
  • String.prototype.trimEnd:用于去除字符串右边的空格
  • String.prototype.trimRight:它是trimEnd的别名。

数组的扩展

在ES2019中扩展了两个数组方法,分别是:

  • Array.prototype.flat():该方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回;简单的说就是实现数组的扁平化。
const arr = [1, 2, [3, 4, [5, [6, 7]]]];
console.log(arr.flat(0)); // [1, 2, [3, 4, [5, [6, 7]]]]
console.log(arr.flat(1)); // [1, 2, 3, 4, [5, [6, 7]]]
console.log(arr.flat(2)); // [1, 2, 3, 4, 5, [6, 7]]
console.log(arr.flat(3)); // [1, 2, 3, 4, 5, 6, 7]
  • Array.prototype.flatMap():该方法映射且扁平化数组,返回新数组(只能展开一层数组)。

ES11

BigInt数据类型

BigInt的出现时解决JavaScript中允许的最大数字是2**53-1的问题,BigInt 可以表示任意大的整数。

const big = 111111111111111111111111111111111111111n;
const bigg = new BigInt(111111111111111111111111111111111111111);
// ↪ 111111111111111111111111111111111111111n

动态导入

动态导入,也就是我们需要该模块的时候才会进行加载,这可以减少开销和页面加载时间。使用import()方法,它返回一个Promise。

import('module.js').then(module => {
  // Do something with the module.
})

同时还为import增加一个meta对象,该对象给JavaScript模块暴露了特定上下文的元数据属性的对象。

GlobalThis

globalThis是对全局对象的引入。在Node是的全局对象是Global,而浏览器环境是Window

// 浏览器中尝试
console.log(globalThis === window); // true

空值合并运算符

空值合并运算符是由两个问号来表示,该运算符也是一个逻辑运算符,该运算符与逻辑或运算符类似。 其计算规则为,只要左运算元为null或者undefined,则返回右运算元,否则返回左运算元。而逻辑或运算符只有左运算元转换为boolean类型后为false,就返回右运算元。

示例代码如下:

console.log(null ?? 10) // 10
console.log(undefined ?? 10) // 10
console.log(false ?? 10) // false
console.log(null || 10) // 10
console.log(undefined || 10) // 10
console.log(false || 10) // 10

该运算符用于为没有值的变量赋值很有用,例如:如果这个数没有值,就为其赋值,否则不赋值,

示例代码如下:

var value = false
 
// 不存在value,才会赋值
value = value ?? 10
console.log(value) // 10
var value = false

// 只要类型转换后为false就会赋值
value = value || 10
console.log(value) // 10
undefined

值得注意的是空值合并运算符与逻辑与和逻辑或不能同时使用,否则会抛出异常,解决方案是通过使用()来表明优先级

可选链操作符

可选链 ?. 是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。

例如我们想要访问A.a.b这个属性时,我们首先需要确保A存在,然后需要确保A.a存在,才可以访问A.a.b这个属性,不然就会报错。

使用可选链操作符就不会出现这样的问题,当我们访问某个属性时,只要有一处不存在,就会返回undefind,不会报错。

不要过度使用可选链

我们应该只将 ?. 使用在一些东西可以不存在的地方。

例如,如果根据我们的代码逻辑,user 对象必须存在,但 address 是可选的,那么我们应该这样写 user.address?.street,而不是这样 user?.address?.street

那么,如果 user 恰巧为 undefined,我们会看到一个编程错误并修复它。否则,如果我们滥用 ?.,会导致代码中的错误在不应该被消除的地方消除了,这会导致调试更加困难。

?. 前的变量必须已声明

如果未声明变量 user,那么 user?.anything 会触发一个错误:

// ReferenceError: user is not defined
user?.address;

?. 前的变量必须已声明(例如 let/const/var user 或作为一个函数参数)。可选链仅适用于已声明的变量。

可选链 ?. 语法有三种形式:

  1. obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined
  2. obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined
  3. obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined

ES12

String.prototype.replaceAll()

replaceAll()方法返回一个新字符串,新字符串的内容是经过替换的

const str = 'aabbbcccddd'
const newStr = str.replaceAll('a', 'e')
const newStr = str.replaceAll(/c/g, 'f') // 可以配合全局正则使用
console.log(newStr) // aabbbfffddd

注意

如果不使用全局正则,则会报错:Uncaught TypeError: String.prototype.replaceAll called with a non-global RegExp argument

数值分隔符

使数值更便于阅读。用_标识。

const x = 10_0_0_0000_0;
const y = 100_000_000;
x === 100000000; // true
y === x; // true

逻辑赋值操作符

  • &&=
  • ||=
  • ??=

ES13

类(Class)

constructor

ES13之前类(class)的声明需要依靠constructor来基本定义和生成实例,而在ES13后就不需要这个方法了

class C {
  myName = 'name'
}
/* 两者是一致的 */
class C {
  constructor() {
    myName = 'name'
  }
}

私有字段

private修饰符只能在 TypeScript 文件中使用。

而在ES13后,使用#开头的变量则被认为使私有变量。

class a {
  #a = 1;
  getA() {
      return this.#a;
  }
}

new a().#a; // 属性 "#a" 在类 "a" 外部不可访问,因为它具有专用标识符。
new a().getA(); // 1

正则表达式

正则表达式增加了一个/d修饰符,当使用正则表达式的exec()方法时,如果有/d修饰符,那么结果会多返回一个indices属性,用来表示匹配的结果的在原字符串中的起始index值。

const str = 'test1testtttt2'
const re= /\d/d
const res = re.exec(str)
console.log(res.indices[0]) // [4,5] 

await在顶层使用

允许在顶层使用await,在顶层可以不使用async函数进行包裹。

Array.prototype.at()

类似python中用负数下标。

// 返回数组倒数第二项
const arr = [1,2,3,4,5,6,7]
// 之前
arr[arr.length - 2];
// 如果使用FP,前面的arr可能是特别长的表达式,这时候需要使用slice来实现
arr.join('').split('').slice(-2, -1)[0];

// 有了新方法后
arr.at(-2)

Object.hasOwn()

判断某个对象上是否具有某个属性。

const person = {
  name: 'name',
  age: 20,
}

// 过去
person.name && console.log(person.name)

// 现在
Object.hasOwn(person, 'name') && console.log(person.name)

总结

从ES6开始JavaScript新增了大量新规范、语法糖和有用的方法。每一年的新规范都包含了全球各地JSer在长期code中的心酸苦楚。了解ESNext历程中的种种新规范,理清楚这里面的脉络,对我们的JS学习也是大有脾益。