前端新特性分享

1.类字段声明

在 ES13 之前,类字段只能在构造函数中声明。与许多其他语言不同,我们不能在类的最外层范围内声明或定义它们。

class Car {  
  constructor() {    
    this.color = 'blue';    
    this.age = 2;  
  }
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

ES13 消除了这个限制。现在我们可以编写如下代码:

class Car {  
  color = 'yellow';  
  age = 2;
}
const car = new Car();
console.log(car.color); // yellow
console.log(car.age); // 2

2.私有方法和字段

以前,不能在类中声明私有成员。成员通常以下划线 (_) 为前缀,表示它是私有的,但仍然可以从类外部访问和修改。

class Person {  
  _firstName = 'Joseph'; 
   _lastName = 'Stevens';  
  get name() {    
    return `${this._firstName} ${this._lastName}`;  
  }
}
const person = new Person();
console.log(person.name); // Joseph Stevens
// Members intended to be private can still be accessed
// from outside the class
console.log(person._firstName); // Joseph
console.log(person._lastName); // Stevens
// They can also be modified
person._firstName = 'Robert';
person._lastName = 'Becker';
console.log(person.name); // Robert Becker

使用 ES13,我们现在可以将私有字段和成员添加到类中,方法是在其前面加上井号 (#)。试图从类外部访问它们会导致错误:

class Person {  
  #firstName = 'Joseph';  
  #lastName = 'Stevens';  
  get name() {    
    return `${this.#firstName} ${this.#lastName}`;  
  }
}
const person = new Person();
console.log(person.name); // Joseph Stevens
// SyntaxError: Private field '#firstName' must be declared //in an enclosing class
console.log(person.#firstName);
console.log(person.#lastName);

请注意,这里抛出的错误是语法错误,发生在编译时,因此没有部分代码运行。编译器甚至不希望您尝试从类外部访问私有字段,因此它假定您正在尝试声明一个。

3.顶层等待操作符

在 JavaScript 中,await 运算符用于暂停执行,直到 Promise 被解决(履行或拒绝)。
以前,我们只能在 async 函数中使用此运算符 - 使用 async 关键字声明的函数。我们无法在全局范围内这样做。

function setTimeoutAsync(timeout) {  
  return new Promise((resolve) => {    
    setTimeout(() => {      
      resolve();    
    }, timeout);  
  });
}// SyntaxError: await is only valid in async functions
await setTimeoutAsync(3000);

使用 ES13,现在我们可以:

function setTimeoutAsync(timeout) {  
  return new Promise((resolve) => {    
    setTimeout(() => {   
      console.log(111)   
      resolve();    
    }, timeout);  
  });
}
// Waits for timeout - no error thrown
await setTimeoutAsync(3000);

好处:
1.代码更简练,不用多写一层function。
2.在父级调用该子模块时,会根据await执行同步命令,暂停父级执行。

4.静态类字段和静态私有方法

我们现在可以在 ES13 中为类声明静态字段和静态私有方法。静态方法可以使用 this 关键字访问类中的其他私有/公共静态成员,实例方法可以使用 this.constructor 访问它们。

class Person {    
  static #count = 0;  
  static getCount() {    
    return this.#count; 
  }  
  constructor() {    
    this.constructor.#incrementCount();  
  }  
  static #incrementCount() {    
    this.#count++;  
  }
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2

5.类静态块

ES13 允许在创建类时定义只执行一次的静态块。这类似于其他支持面向对象编程的语言(如 C# 和 Java)中的静态构造函数。
一个类的类主体中可以有任意数量的静态 {} 初始化块。它们将与任何交错的静态字段初始值设定项一起按照声明的顺序执行。我们可以在静态块中使用超属性来访问超类的属性。

class Vehicle {  
  static defaultColor = 'blue';
}
class Car extends Vehicle {  
  static colors = [];  
  static {    
    this.colors.push(super.defaultColor, 'red');  
  }  
  static {    
    this.colors.push('green');  
  }
}
console.log(Car.colors); // [ 'blue', 'red', 'green' ]

6. 私有字段检查

我们可以使用这个新特性来检查
一个对象中是否有一个特定的私有字段,使用 in 运算符。

class Car { 
  #color;  
  hasColor() {    
    return #color in this;  
  }
}
const car = new Car();
console.log(car.hasColor()); // true;
class Car {  
  #color;  
  hasColor() {    
    return #color in this;  
  }
}
class House {  
  #color;  
  hasColor() {    
    return #color in this;  
  }
}
const car = new Car();
const house = new House();
console.log(car.hasColor()); // true;
console.log(car.hasColor.call(house)); // false
console.log(house.hasColor()); // true
console.log(house.hasColor.call(car)); // false

7. at() 方法进行索引

我们通常在 JavaScript 中使用方括号 ([]) 来访问数组的第 N 个元素,这通常是一个简单的过程。我们只访问数组的 N - 1 属性。

const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b

但是,如果我们想使用方括号访问数组末尾的第 N 个项目,我们必须使用 arr.length - N 的索引。

const arr = ['a', 'b', 'c', 'd'];
// 1st element from the end
console.log(arr[arr.length - 1]); // d
// 2nd element from the end
console.log(arr[arr.length - 2]); // c

新的 at() 方法让我们可以更简洁、更有表现力地做到这一点。要访问数组末尾的第 N 个元素,我们只需将负值 -N 传递给 at()。

const arr = ['a', 'b', 'c', 'd'];
// 1st element from the end
console.log(arr.at(-1)); // d
// 2nd element from the end
console.log(arr.at(-2)); // c

除了数组,字符串和 TypedArray 对象现在也有 at() 方法。

const str = 'Coding Beauty';
console.log(str.at(-1)); // y
console.log(str.at(-2)); // t

const typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48

8.RegExp 匹配索引

这个新功能允许我们指定我们想要获取给定字符串中 RegExp 对象匹配的开始和结束索引。
以前,我们只能在字符串中获取正则表达式匹配的起始索引。

const str = 'sun and moon';
const regex = /and/;
const matchObj = regex.exec(str);
// [ 'and', index: 4, input: 'sun and moon', groups: undefined ]
console.log(matchObj);

我们现在可以指定一个d正则表达式标志来获取匹配开始和结束的两个索引。
设置 d 标志后,返回的对象将具有包含开始和结束索引的 indices 属性。

const str = 'sun and moon';
const regex = /and/d;
const matchObj = regex.exec(str);
//[  'and',  index: 4,  input: 'sun and moon',  groups: undefined,  indices: [ [ 4, 7 ], groups: undefined ]] 
console.log(matchObj);

9. Object.hasOwn() 方法

在 JavaScript 中,我们可以使用Object.prototype.hasOwnProperty() 方法来检查对象是否具有给定的属性。

class Car {  
  color = 'green';  
  age = 2;
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // true
console.log(car.hasOwnProperty('name')); // false

但是这种方法存在一定的问题。一方面,
Object.prototype.hasOwnProperty() 方法不受保护 - 它可以通过为类定义自定义hasOwnProperty() 方法来覆盖,该方法可能具有Object.prototype.hasOwnProperty() 完全不同的行为。

class Car {  
  color = 'green';  
  age = 2;  
  hasOwnProperty() {    
    return false;  
  }
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // false
console.log(car.hasOwnProperty('name')); // false

另一个问题是,对于使用 null 原型创建的对象(使用 Object.create(null)),尝试对其调用此方法会导致错误。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('color'));

解决这些问题的一种方法是使用调用Object.prototype.hasOwnProperty Function 属性上的 call() 方法,如下所示:

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () =>false;
console.log(Object.prototype.hasOwnProperty.call(obj, 'color')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // false

这不是很方便。我们可以编写一个可重用的函数来避免重复:

function objHasOwnProp(obj, propertyKey) {  
  return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(objHasOwnProp(obj, 'color')); // true
console.log(objHasOwnProp(obj, 'name')); // false

不过没有必要,因为我们可以使用新的内置 Object.hasOwn() 方法。与我们的可重用函数一样,它接受对象和属性作为参数,如果指定的属性是对象的直接属性,则返回 true。否则,它返回 false。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.hasOwn(obj, 'color')); // true
console.log(Object.hasOwn(obj, 'name')); // false

10. 错误原因

错误对象现在有一个 cause 属性,用于指定导致即将抛出的错误的原始错误。这有助于为错误添加额外的上下文信息并帮助诊断意外行为。我们可以通过在作为第二个参数传递给 Error() 构造函数的对象上设置 cause 属性来指定错误的原因。

function userAction() {  
  try {    
    apiCallThatCanThrow();  
  } 
  catch (err) {    
    throw new Error('New error message', { cause: err });  
  }
}
try {  
  userAction();
} catch (err) {  
  console.log(err);  
  console.log(`Cause by: ${err.cause}`);
}

11.从最后一个数组查找

在 JavaScript 中,我们已经可以使用 Array find() 方法在数组中查找通过指定测试条件的元素。同样,我们可以使用 findIndex() 来查找此类元素的索引。虽然 find() 和 findIndex() 都从数组的第一个元素开始搜索,但在某些情况下,最好从最后一个元素开始搜索。
在某些情况下,我们知道从最后一个元素中查找可能会获得更好的性能。例如,这里我们试图在数组中获取值 等于 y 的项目。使用 find() 和 findIndex():

const letters = [  { value: 'v' },  { value: 'w' },  { value: 'x' }, 
 { value: 'y' },  { value: 'z' },];
const found = letters.find((item) => item.value === 'y');
const foundIndex = letters.findIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

这行得通,但是由于目标对象更靠近数组的尾部,如果我们使用 findLast() 和 findLastIndex() 方法从末尾搜索数组,我们可以让这个程序运行得更快。

const letters = [  { value: 'v' },  { value: 'w' },  { value: 'x' },  
{ value: 'y' },  { value: 'z' },];
const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

另一个用例可能需要我们专门从末尾搜索数组以获取正确的项目。例如,如果我们想在数字列表中找到最后一个偶数, find() 和 findIndex() 会产生错误的结果:

const nums = [7, 14, 3, 8, 10, 9];
// gives 14, instead of 10
const lastEven = nums.find((value) => value % 2 === 0);
// gives 1, instead of 4
const lastEvenIndex = nums.findIndex((value) => value % 2 === 0);
console.log(lastEven); // 14
console.log(lastEvenIndex); // 1

我们可以在调用 find() 和 findIndex() 之前调用数组的 reverse() 方法来反转元素的顺序。但是这种方法会导致数组不必要的突变,因为 reverse() 将数组的元素反转到位。避免这种突变的唯一方法是制作整个数组的新副本,这可能会导致大型数组出现性能问题。
此外, findIndex() 仍然无法在反转数组上工作,因为反转元素也意味着更改它们在原始数组中的索引。要获得原始索引,我们需要执行额外的计算,这意味着编写更多代码。

const nums = [7, 14, 3, 8, 10, 9];
const lastEven = nums.findLast((num) => num % 2 === 0);
const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0);
console.log(lastEven); // 10
console.log(lastEvenIndex); // 4

12.导入断言

使用这种方式导入,我们就不需要手动解析它了。
在 import…from… 后面加 assert { type: ‘json’ }

import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42

1. String.replaceAll()

replaceAll()方法会返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。
原始字符串保持不变。

let string = 'hello world, hello ES12'
string.replace(/hello/g,'hi')    // hi world, hi ES12
string.replaceAll('hello','hi')  // hi world, hi ES12

注意的是,replaceAll 在使用正则表达式的时候,如果非全局匹配(/g),会抛出异常:

let string = 'hello world, hello ES12'
string.replaceAll(/hello/,'hi') 
// Uncaught TypeError: String.prototype.replaceAll called with a non-global

2. 数字分隔符

数字分隔符可以在数字之间创建可视化分隔符,通过 _下划线来分割数字,使数字更具可读性:

let budget = 1_000_000_000_000;
budget === 10 ** 12 // true

这个数值分隔符没有指定间隔的位数,也就是说,可以每三位添加一个分隔符,也可以每一位、每两位、每四位添加一个。

123_00 === 12_300 // true
12345_00 === 123_4500 // true

小数和科学计数法也可以使用数值分隔符。

0.000_001 // 小数
1e10_000 // 科学计数法

数值分隔符有几个使用注意点。

  • 不能放在数值的最前面(leading)或最后面(trailing)。
  • 不能两个或两个以上的分隔符连在一起。
  • 小数点的前后不能有分隔符。
  • 科学计数法里面,表示指数的e或E前后不能有分隔符
    下面的写法都会报错。
3_.141
3._141
1_e12
1e_12
123__456
_1464301
1464301_

3.Promise.any

方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

const promise1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise1");
      //  reject("error promise1 ");
    }, 3000);
  });
};
const promise2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise2");
      // reject("error promise2 ");
    }, 1000);
  });
};
const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise3");
      // reject("error promise3 ");
    }, 2000);
  });
};
Promise.any([promise1(), promise2(), promise3()])
  .then((first) => {
    // 只要有一个请求成功 就会返回第一个请求成功的
    console.log(first); // 会返回promise2
  })
  .catch((error) => {
    // 所有三个全部请求失败 才会来到这里
    console.log("error", error);// err:AggregateError: All promises were rejected
    console.log(error.message)// All promises were rejected
    console.log(error.name) // AggregateError
    console.log(err.errors) // ["error promise1", "error promise2", "error promise3"]
  });

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
即:只要其中的一个 promise 成功,就返回那个已经成功的 promise 如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

4.逻辑运算符和赋值表达式(&&=,||=,??=)

&&=
逻辑与赋值 x &&= y等效于:
x && (x = y);
上面的意思是,当x为真时,x=y。具体请看下面的示例:

let a = 1;
let b = 0;
a &&= 2;
console.log(a); // 2
b &&= 2;
console.log(b);  // 0

||=
逻辑或赋值(x ||= y)运算仅在 x 为false时赋值。
x ||= y 等同于:x || (x = y);

const a = { duration: 50, title: '' };
a.duration ||= 10;
console.log(a.duration); // 50
a.title ||= 'title is empty.';
console.log(a.title); // "title is empty"

??=
逻辑空赋值运算符 (x ??= y) 仅在 x 是 null 或 undefined 时对其赋值。

const a = { duration: 50 };
a.duration ??= 10;
console.log(a.duration); // 50
a.speed ??= 25;
console.log(a.speed); // 25

5.FinalizationRegistry

FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调
你可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值

let obj = { name: "why", age: 18 }
let info = { name: "kobe", age: 30 }
const finalRegistry = new FinalizationRegistry((value) => {
  console.log("某一个对象被回收了:", value)
})
finalRegistry.register(obj, "why")
finalRegistry.register(info, "kobe")
// obj = null
info = null

6.WeakRefs

新的类 WeakRefs。允许创建对象的弱引用。这样就能够在跟踪现有对象时不会阻止对其进行垃圾回收。对于缓存和对象映射非常有用。
必须用 new关键字创建新的 WeakRef,并把某些对象作为参数放入括号中。当你想读取引用(被引用的对象)时,可以通过在弱引用上调用 deref() 来实现。
如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用
如果我们希望是一个弱引用的话,可以使用WeakRef

let info = { name: "why", age: 18 }
let obj = new WeakRef(info)
let obj2 = new WeakRef(info)
const finalRegistry = new FinalizationRegistry(() => {
  console.log("对象被回收~")
})
finalRegistry.register(info, "info")
setTimeout(() => {
  info = null
}, 2000)
setTimeout(() => {
  console.log(obj.deref().name, obj.deref().age)
}, 4000)

7.Intl.ListFormat

Intl.ListFormat 用来处理和多语言相关的对象格式化操作
Intl.ListFormat 参数/用法

new Intl.ListFormat([locales[, options]])

输出处理过后的字符串
示例
//不允许存在数字,如有空格,不会忽略空格

let strArr = ["xiaoming", "小明", "小红", "XXMM"]; 
    let EnForm = new Intl.ListFormat("en",{
      localeMatcher:'lookup',
      style:'short',
      type:'disjunction'
    }).format(strArr);
    let cnForm = new Intl.ListFormat("zh-cn",{
      localeMatcher:'lookup',
      style:'short',
      type:'disjunction'
    }).format(strArr);
    // lookup/best fit + long + conjunction  输出:
    // console.log("en", EnForm); // xiaoming, 小明, 小红, and XXMM
    // console.log("cn", cnForm); // xiaoming、小明、小红和XXMM

    // lookup/best fit + short/long + disjunction   输出:
    // console.log("en", EnForm); // xiaoming, 小明, 小红, or XXMM
    // console.log("cn", cnForm); // xiaoming、小明、小红和XXMM

    // lookup/best fit + short + conjunction   输出:
    // console.log("en", EnForm); // xiaoming, 小明, 小红, & XXMM
    // console.log("cn", cnForm); // xiaoming、小明、小红和XXMM

    // lookup/best fit + narrow + unit  输出:
    //  console.log("en", EnForm); // xiaoming 小明 小红 XXMM
    // console.log("cn", cnForm); // xiaoming小明小红XXMM

8. Intl.DateTimeFormat() 是什么?

可以用来处理多语言下的时间日期格式化函数。与 moment.js 时间插件相似
Intl.DateTimeFormat 参数/用法

new Intl.DateTimeFormat([locales[, options]])

示例

 let date = new Date(); //获取本机当前时间
// 粗暴用法 获取 年月日+时间
 let dateShort = { timeStyle: "medium", dateStyle: "short" };
 let dateMedium = { timeStyle: "medium", dateStyle: "medium" };
 console.log(new Intl.DateTimeFormat('zh-cn',dateShort).format(date)); // 2022/8/5 15:27:17
 console.log(new Intl.DateTimeFormat('zh-cn',dateMedium).format(date)); // 2022年8月5日 15:27:28

 // 精确用法 获取 公元+星期+年月日+时间
 // 不传,取默认的时间格式
 console.log(new Intl.DateTimeFormat().format(date)); // 2022/8/5
  let options1 = {
    year: "numeric",
    month: "numeric", 
    hour: "numeric",
    minute: "numeric",
    second: "numeric",
    hour12: false, //是否为 12 小时制
  }
  console.log(new Intl.DateTimeFormat("de-DE", options1).format(date)); // de-DE(德语) Freitag, 5. August 2022

  let options2 = {
      era: "long", // 公元  locales如果为中文,切换任何参数都是公元,反之,才会有长短显示的区别
      weekday: "long", // 星期几
      year: "numeric",
      month: "numeric", // long 打印: 5. August 2022 um 14:31:44, short 打印:5. Aug. 2022, 14:32:37, narrow 打印:5. A 2022, 14:35:58
      day: "numeric",
      hour: "numeric",
      minute: "numeric",
      second: "numeric",
      hour12: false,
    };
    console.log(new Intl.DateTimeFormat("zh-cn", options2).format(date)); // 公元2022年8月5日星期五 15:31:32

    let options3 = {
      timeStyle: "medium",
      dateStyle: "short",
    };
    console.log(new Intl.DateTimeFormat("zh-cn", options3).format(date)); // 2022/8/5 14:27:59

9. Intl.RelativeTimeFormat()

处理时间格式,转换为昨天/前天/一天前/季度等
Intl.RelativeTimeFormat() 参数/用法

new Intl.RelativeTimeFormat([locales[, options]])

const tf = new Intl.RelativeTimeFormat(“zh-cn”);
console.log( tf.format(-10, “year”) );
// output :: 10年前

示例

// options 不写表示默认项
 const tf = new Intl.RelativeTimeFormat("zh-cn");
    // output :: 10年前
    console.log(tf.format(-10, "year"));
    // output :: 5个月前
    console.log(tf.format(-5, "month"));
    // output :: 2个季度后
    console.log(tf.format(2, "quarter"));
    // output :: 2周后
    console.log(tf.format(2, "week"));
    // output :: 2天前
    console.log(tf.format(-2, "day"));
     // output :: 8小时后
    console.log(tf.format(8, "hour"));
    // output :: 15分钟前
    console.log(tf.format(-15, "minute"));
    // output :: 2秒钟后
    console.log(tf.format(2, "second"));

1.空值合并运算符

空值合并操作符( ?? )是一个逻辑操作符,当左侧的操作数为 null或者undefined时,返回其右侧操作数,否则返回左侧操作数。

const foo = undefined ?? "foo"
const bar = null ?? "bar"
console.log(foo) // foo
console.log(bar) // bar

与逻辑或操作符(||)不同,逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如'',0,NaN,false)时。见下面的例子

const foo = "" ?? 'default string';
const foo2 = "" || 'default string';
console.log(foo); // ""
console.log(foo2); // "default string"

const baz = 0 ?? 42;
const baz2 = 0 || 42;
console.log(baz); // 0
console.log(baz2); // 42

2.可选链

可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为 null 或者 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。
当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。

const user = {
    address: {
        street: 'xx街道',
        getNum() {
            return '80号'
        }
    }
}

在之前的语法中,想获取到深层属性或方法,不得不做前置校验,否则很容易命中 Uncaught TypeError: Cannot read property... 这种错误,这极有可能让你整个应用挂掉。

const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)

用了 Optional Chaining ,上面代码会变成

const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)

常见用法

// 对象中使用
  let obj = {
    name: "jimmy",
    age: "18",
  };
  let property = "age";
  let name = obj?.name;
  let age = obj?.age;
  let ages = obj?.[property];
  let sex = obj?.sex;
  console.log(name); // jimmy
  console.log(age); // 18
  console.log(ages); // 18
  console.log(sex); // undefined
  // 数组中使用
  let arr = [1,2,2];
  let arrayItem = arr?.[42]; // undefined
  // 函数中使用
  let obj = {
   func: function () {
     console.log("I am func");
   },
  };
  obj?.func(); // I am func

与空值合并操作符一起使用

let customer = {
  name: "jimmy",
  details: { age: 18 }
};
let customerCity = customer?.city ?? "成都";
console.log(customerCity); // "成都"

注意点:可选链不能用于赋值
let object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

3.globalThis

在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 window、self 取到全局对象,在 Node.js 中,它们都无法获取,必须使用 global。
在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。
以前想要获取全局对象,可通过一个全局函数

const getGlobal = () => {
    if (typeof self !== 'undefined') {
        return self
    }
    if (typeof window !== 'undefined') {
        return window
    }
    if (typeof global !== 'undefined') {
        return global
    }
    throw new Error('无法找到全局对象')
}
const globals = getGlobal()
console.log(globals)

现在globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。

为便于记忆,你只需要记住,全局作用域中的 this 就是globalThis。以后就用globalThis就行了。
image.png

4.BigInt

BigInt 是一种内置对象,它提供了一种方法来表示大于 2的53次方 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。
使用 BigInt 有两种方式:
方式一:数字后面增加n

const bigInt = 9007199254740993n
console.log(bigInt)
console.log(typeof bigInt) // bigint
// `BigInt` 和 [`Number`]不是严格相等的,但是宽松相等的。
console.log(1n == 1) // true
console.log(1n === 1) // false
// `Number` 和 `BigInt` 可以进行比较。
1n < 2 //  true
2n > 1 //  true

方式二:使用 BigInt 函数

const bigIntNum = BigInt(9007199254740993)
console.log(bigIntNum)

运算

let number = BigInt(2);
let a = number + 2n; 
let b = number * 10n;
let c = number - 10n; 
console.log(a);// 4n
console.log(b); // 20n
console.log(c);// -8n

注意点
BigInt不能用于 [Math] 对象中的方法;不能和任何 [Number] 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 [Number] 变量时可能会丢失精度。

5.String.prototype.matchAll()

matchAll()
方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);  // ["test1", "e", "st1", "1"]
console.log(array[1]); // ["test2", "e", "st2", "2"]

6.Promise.allSettled()

我们都知道 Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入reject 状态。
场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据,我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态,这就是Promise.allSettled的作用

const promise1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise1");
      //   reject("error promise1 ");
    }, 3000);
  });
};
const promise2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise2");
      //   reject("error promise2 ");
    }, 1000);
  });
};
const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      //   resolve("promise3");
      reject("error promise3 ");
    }, 2000);
  });
};
//  Promise.all 会走到catch里面
Promise.all([promise1(), promise2(), promise3()])
  .then((res) => {
    console.log(res); 
  })
  .catch((error) => {
    console.log("error", error); // error promise3 
  });
// Promise.allSettled 不管有没有错误,三个的状态都会返回
Promise.allSettled([promise1(), promise2(), promise3()])
  .then((res) => {
    console.log(res);  
    // 打印结果 
    // [
    //    {status: 'fulfilled', value: 'promise1'}, 
    //    {status: 'fulfilled',value: 'promise2'},
    //    {status: 'rejected', reason: 'error promise3 '}
    // ]
  })
  .catch((error) => {
    console.log("error", error); 
  });

7.Dynamic Import(按需 import)

import()可以在需要的时候,再加载某个模块。

button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});

上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。

1.Object.fromEntries()

方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和Object.entries() 相对的。

Object.fromEntries([
    ['foo', 1],
    ['bar', 2]
])
// {foo: 1, bar: 2}

案例1:Object 转换操作

const obj = {
    name: 'jimmy',
    age: 18
}
const entries = Object.entries(obj)
console.log(entries)
// [Array(2), Array(2)]
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// {name: "jimmy", age: 18}

案例2:Map 转 Object

const map = new Map()
map.set('name', 'jimmy')
map.set('age', 18)
console.log(map) // {'name' => 'jimmy', 'age' => 18}
const obj = Object.fromEntries(map)
console.log(obj)
// {name: "jimmy", age: 18}

案例3:过滤
course表示所有课程,想请求课程分数大于80的课程组成的对象:

const course = {
    math: 80,
    english: 85,
    chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res) // [ [ 'english', 85 ], [ 'chinese', 90 ] ]
console.log(Object.fromEntries(res)) // { english: 85, chinese: 90 }

案例4:url的search参数转换

// let url = "https://www.baidu.com?name=jimmy&age=18&height=1.88"
// queryString 为 window.location.search
const queryString = "?name=jimmy&age=18&height=1.88";
const queryParams = new URLSearchParams(queryString);
const paramObj = Object.fromEntries(queryParams);
console.log(paramObj); // { name: 'jimmy', age: '18', height: '1.88' }
console.log([...queryParams.entries()]);// [ ['name', 'jimmy'], ['age', '18'], ['height', '1.88']]

2.Array.prototype.flat()

语法

let newArray = arr.flat([depth])

depth 可选
指定要提取嵌套数组的结构深度,默认值为 1。
示例
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());  //  [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));  //  [0, 1, 2, [3, 4]]
//使用 Infinity,可展开任意深度的嵌套数组
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// `flat()` 方法会移除数组中的空项:
var arr5 = [1, 2, , 4, 5];
arr5.flat(); // [1, 2, 4, 5]

3.Array.prototype.flatMap()

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。

var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
    // 返回新数组的元素
}[, thisArg])

callback
可以生成一个新数组中的元素的函数,可以传入三个参数:
currentValue:当前正在数组中处理的元素
index:可选 数组中正在处理的当前元素的索引。
array:可选 被调用的 map 数组
thisArg可选
执行 callback 函数时 使用的this 值。
示例

const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]

这个示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下下面的示例:

let arr = ['今天天气不错', '', '早上好']
arr.map(s => s.split(''))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]

flatMap 方法与 map 方法和深度depth为1的 flat 几乎相同.

4.String.prototype.trimStart()

trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。

let str = '   foo  '
console.log(str.length) // 8
str = str.trimStart() // 或str.trimLeft()
console.log(str.length) // 5

5.String.prototype.trimEnd()

trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。

let str = '   foo  '
console.log(str.length) // 8
str = str.trimEnd() // 或str.trimRight()
console.log(str.length) // 6

6.可选的Catch Binding

在 ES10 之前我们都是这样捕获异常的:

try {
    // tryCode
} catch (err) {
    // catchCode
}

在这里 err 是必须的参数,在 ES10 可以省略这个参数:

try {
    console.log('Foobar')
} catch {
    console.error('Bar')
}

应用
验证参数是否为json格式
这个需求我们只需要返回true或false,并不关心catch的参数。

const validJSON = json => {
    try {
        JSON.parse(json)
        return true
    } catch {
        return false
    }
}

7.Symbol.prototype.description

我们知道,Symbol 的描述只被存储在内部的 Description ,没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:

const name = Symbol('es')
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === 'Symbol(es)') // false
console.log(name.toString() === 'Symbol(es)') // true

现在可以通过 description 方法获取 Symbol 的描述:

const name = Symbol('es')
console.log(name.description) // es
name.description = "es2" // 只读属性 并不能修改描述符
console.log(name.description === 'es') // true
// 如果没有描述符 输出undefined
const s2 = Symbol()
console.log(s2.description) // undefined

8.JSON.stringify() 增强能力

JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。

// \uD83D\uDE0E  emoji 多字节的一个字符
console.log(JSON.stringify('\uD83D\uDE0E')) // 打印出笑脸
// 如果我们只去其中的一部分  \uD83D 这其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify('\uD83D')) // "\ud83d"

9.修订 Function.prototype.toString()

以前函数的toString方法来自Object.prototype.toString(),现在的 Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。以前只会返回这个函数,不包含注释、空格等。

function foo() {
    // es10新特性
    console.log('imooc')
}
console.log(foo.toString()) 
// 打印如下
// function foo() {
//  // es10新特性
//  console.log("imooc");
// }

将返回注释、空格和语法等详细信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容