整理了ES7到ES14的新特性

1,295 阅读21分钟

ES7(ECMAScript2016)

Array.prototype.includes()

判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

该方法有两个参数,第一个参数为要查找的值,第二个参数(可选)是开始搜索的索引(默认从0开始)

第二个参数(下面简称fromIndex)有以下特点

  • fromIndex<0时,相当于从fromIndex + array.length开始搜索,还是从前往后进行搜索
  • fromIndex < -array.length时,相当于从0检索整个数组
  • fromIndex >= array.length,则不会搜索数组并返回 false
const arr1 = [1, 2, 3];
console.log(arr1.includes(1)); // true
console.log(arr1.includes(1, 1)); // false
console.log(arr1.includes(2, 1)); // true
console.log(arr1.includes(2, -1)); // false
console.log(arr1.includes(2, -2)); // true
console.log(arr1.includes(2, -100)); // true
console.log(arr1.includes(2, 3)); // false
console.log(arr1.includes(2, 100)); // false
const arr2 = ["a", "b", "c"];
console.log(arr2.includes("a")); // true
console.log(arr2.includes("d")); // false

幂运算符

返回第一个操作数取第二个操作数的幂的结果,即 2 ** 3 表示求2的3次方,等价于 Math.pow(),不同之处在于,它还接受BigInt作为操作数。

console.log(2 ** 3); // 8
console.log(BigInt(9007199254740991) ** BigInt(2)); 
// 81129638414606663681390495662081n

ES8(ECMAScript2017)

async、await

asyncawait 关键字是基于 Generator 函数的语法糖,使得我们可以更简洁地编写基于 promise 的异步代码。

async function 声明创建一个绑定到给定名称的新异步函数。每次调用该异步函数时,都会返回一个新的Promise对象。该函数可以包含零个或者多个await表达式。await 用于等待一个异步方法执行完成,只有当异步完成后才会继续往后执行。

const fn1 = () =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(1);
    }, 500);
  });

const fn2 = () =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(2);
    }, 500);
  });

const asyncFn = async () => {
  let res1 = await fn1();
  console.log(res1);
  let res2 = await fn2();
  console.log(res2);
  console.log(3);
};

asyncFn();
// 结果会依次输出1,2,3

Async 函数总是返回一个 promise。如果其返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。

async function foo() {
  return 1;
}

// 上面的函数类似于下面的函数

function foo() {
  return Promise.resolve(1);
}

Object.values()

返回一个给定对象的自有可枚举字符串键属性值组成的数组。

const obj = {
  a: 123,
  b: "something",
  c: true,
};

console.log(Object.values(obj)); 
// [ 123, 'something', true ]

Object.entries()

返回一个数组,包含给定对象自有的可枚举字符串键属性的键值对。

const obj = {
  a: 123,
  b: "something",
  c: true,
};

console.log(Object.entries(obj)); 
// [ [ 'a', 123 ], [ 'b', 'something' ], [ 'c', true ] ]

Object.getOwnPropertyDescriptors()

返回给定对象的所有自有属性(包括不可枚举属性)的属性描述符。

const obj = {
  a: 123,
};

console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
  a: {
    value: 123, // 与属性关联的值
    writable: true, // 值可以更改时,为 true
    enumerable: true, // 当且仅当此属性在相应对象的属性枚举中出现时,为 true
    configurable: true, // 当且仅当此属性描述符的类型可以更改且该属性可以从相应对象中删除时,为 true
  },
};
*/

String.prototype.padStart()

用指定字符串从当前字符串的头部填充(如果需要会重复填充),直到达到给定的长度。返回新字符串。

该方法有两个参数

  • 第一个参数表示填充后字符串的长度。
  • 第二个参数为可选(默认为空格),表示用于填充当前 str 的字符串。
const str1 = "abc";
const str2 = str1.padStart(5, "-");
const str3 = str1.padStart(5);
const str4 = str1.padStart(2, "-"); // 如果第一个参数小于或等于字符串的长度,则会直接返回当前字符串
const str5 = str1.padStart(4, "ab"); // 第二个参数过长无法适应给定的长度,则会被截断
console.log(str1); // 'abc'
console.log(str2); // '--abc'
console.log(str3); // '  abc'
console.log(str4); // 'abc'
console.log(str5); // 'aabc'

String.prototype.padEnd()

与String.prototype.padStart()类似,用指定字符串从当前字符串的尾部填充(如果需要会重复填充),直到达到给定的长度。返回新字符串。

该方法有两个参数

  • 第一个参数表示填充后字符串的长度。
  • 第二个参数为可选(默认为空格),表示用于填充当前 str 的字符串。
const str1 = "abc";
const str2 = str1.padEnd(5, "-");
const str3 = str1.padEnd(5);
const str4 = str1.padEnd(2, "-"); // 如果第一个参数小于或等于字符串的长度,则会直接返回当前字符串
const str5 = str1.padEnd(4, "ab"); // 第二个参数过长无法适应给定的长度,则会被截断
console.log(str1); // 'abc'
console.log(str2); // 'abc--'
console.log(str3); // 'abc  '
console.log(str4); // 'abc'
console.log(str5); // 'abca'

ES9(ECMAScript2018)

for await...of

for await...of 语句创建一个循环,该循环遍历异步可迭代对象以及同步可迭代对象。该语句只能在可以使用 await的上下文中使用。

const fn = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value);
    }, 1000);
  });
};

const arr = [fn(1), fn(2), fn(3)];

async function asyncFn() {
  for await (let item of arr) {
    console.log(item);
    console.log("--");
    if (item === 2) {
      break; // 关闭迭代器,触发返回
    }
  }
}

asyncFn();
// 控制台打印如下
// 1
// --
// 2
// --
// 同步可迭代也可以
const arr = [1, 2, 3];

async function asyncFn() {
  for await (let item of arr) {
    console.log(item);
  }
}

asyncFn();
// 由于异步生成器函数的返回值符合异步可迭代协议,因此可以使用 for await...of 来迭代它们。
async function* asyncGenerator() {
  let i = 0;
  while (i < 3) {
    yield i++;
  }
}

(async () => {
  for await (const num of asyncGenerator()) {
    console.log(num);
  }
})();
// 0
// 1
// 2

Symbol.asyncIterator 符号可以指定一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环。

const obj = {
  count: 0,
  [Symbol.asyncIterator]() {
    const self = this;
    return {
      next() {
        self.count++;
        return Promise.resolve({
          done: self.count > 3,
          value: self.count,
        });
      },
    };
  },
};

(async () => {
  for await (const item of obj) {
    console.log(item);
  }
})();
// 1
// 2
// 3
const myAsyncIterable = new Object();
myAsyncIterable[Symbol.asyncIterator] = async function* () {
  yield "hello";
  yield "async";
  yield "iteration!";
};

(async () => {
  for await (const x of myAsyncIterable) {
    console.log(x);
  }
})();
//  "hello"
//  "async"
//  "iteration!"

展开语法 (Spread syntax)

const obj1 = {
  a: 1,
  b: 2,
};

const obj2 = {
  ...obj1,
  c: 3,
};

console.log(obj2); // { a: 1, b: 2, c: 3 }

剩余语法 (Rest syntax)

const fn = (a, ...rest) => {
  console.log(a); // 1
  console.log(rest); // [ 2, 3, 4 ]
};

fn(1, 2, 3, 4);
const obj1 = {
  a: 1,
  b: 2,
  c: 3,
};

const { a, ...rest } = obj1;
console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }

Promise.prototype.finally()

Finally 可以注册一个方法并返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected都会执行finally指定的回调函数。可以避免在 promise 的 then()catch() 处理器中重复编写代码。

function checkMail() {
  return new Promise((resolve, reject) => {
    if (Math.random() > 0.5) {
      resolve('Mail has arrived');
    } else {
      reject(new Error('Failed to arrive'));
    }
  });
}

checkMail()
  .then((mail) => {
    console.log(mail);
  })
  .catch((err) => {
    console.error(err);
  })
  .finally(() => {
    console.log('Experiment completed');
  });

正则表达式具名捕获组

(?<Name>x),具名捕获组,匹配"x"并将其存储在返回的匹配项的 groups 属性中,该属性位于<Name>指定的名称下。尖括号 (<>) 用于组名。

const reg = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
const match = reg.exec("2021-02-23");
console.log(match.groups["year"]); // 2021
console.log(match.groups["month"]); // 02
console.log(match.groups["day"]); // 23

ES10(ECMAScript2019)

String.prototype.trimStart()

StringtrimStart() 方法会从字符串的开头移除空白字符,并返回一个新的字符串,而不会修改原始字符串。

trimLeft() 是该方法的别名。目前已被标为弃用。

const str = " hello world ";
console.log(str.trimStart());// "hello world "

String.prototype.trimEnd()

StringtrimEnd() 方法会从字符串的结尾移除空白字符,并返回一个新的字符串,而不会修改原始字符串。trimRight() 是该方法的别名。目前已被标为弃用。

const str = " hello world ";
console.log(str.trimEnd()); // " hello world"

Array.prototype.flat()

flat() 方法创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中。

该方法有一个可选参数depth,指定要提取嵌套数组的结构深度,默认值为 1。

const arr = [1, [2, 3], 4, 5];
console.log(arr.flat()); // [ 1, 2, 3, 4, 5 ]
const arr2 = [1, [2, [3, 4]], 5, 6];
console.log(arr2.flat()); // [ 1, 2, [ 3, 4 ], 5, 6 ]
console.log(arr2.flat(2)); // [ 1, 2, 3, 4, 5, 6 ]
const arr3 = [1, [2, [3, 4]], [5, [6, [7, [8, [9]]]]]];
console.log(arr3.flat(Infinity)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

Array.prototype.flatMap()

flatMap() 方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。

它等价于在调用map()方法后再调用深度为 1 的 flat()arr.map(...args).flat()),但比分别调用这两个方法稍微更高效一些。

const arr = [1, [2, 3], 4, 5];
function flatMapArr(list) {
  return list.flatMap((e) => {
    if (e.length) {
      return flatMapArr(e);
    } else {
      return e * 2;
    }
  });
}
console.log(flatMapArr(arr)); // [ 2, 4, 6, 8, 10 ]

const arr = [1, 2, 3, 4];
console.log(
  arr.flatMap((e) => {
    if (e % 2 === 0) {
      return [e, e * 2];
    } else {
      return e;
    }
  })
); // [ 1, 2, 4, 3, 4, 8 ]

Function.prototype.toString()

Function也是对象,对象有toString() 的方法(Object.prototype.toString()),Function对象重写了toString() 方法,而没有继承toString

对于用户定义的 Function 对象,toString 方法返回一个字符串,其中包含用于定义函数的源文本段。

function fn(a, b) {
  // 注释文本
  return a + b;
}

console.log(fn.toString());
/**
function fn(a, b) {
  // 注释文本
  return a + b;
}
 */


const fn2 = (a, b) => {
  // 我是注释
  return a + b;
};
console.log(fn2.toString());
/**
(a, b) => {
  // 我是注释
  return a + b;
}
 */

Object.fromEntries()

与上面的Object.entries()相反,Object.fromEntries() 静态方法将键值对列表转换为一个对象。

const arr = [
  ["a", 1],
  ["b", 2],
  ["c", 3],
];
console.log(Object.fromEntries(arr)); // { a: 1, b: 2, c: 3 }

const set = new Set([
  ["a", 1],
  ["b", 2],
  ["c", 3],
]);
console.log(Object.fromEntries(set)); // { a: 1, b: 2, c: 3 }

const map = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3],
]);
console.log(Object.fromEntries(map)); // { a: 1, b: 2, c: 3 }
const obj = {
  a: 1,
  b: 2,
  c: 3,
};
const obj2 = Object.fromEntries(
  Object.entries(obj).map(([key, value]) => [key, value * 2])
);
console.log(obj2); // { a: 2, b: 4, c: 6 }

Symbol.prototype.description

description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

console.log(Symbol("test").description); // test
console.log(Symbol.iterator.description); // Symbol.iterator
console.log(Symbol.for("desc").description); // desc

可选的 Catch 绑定

try {
	// try code
} catch (error) {
	// catch code
}

try {
  // try code
} catch {
	// catch code
}
// 现在catch中可以不添加参数

ES11(ECMAScript2020)

String.prototype.matchAll()

matchAll() 方法返回一个迭代器,该迭代器包含了检索字符串与正则表达式进行匹配的所有结果。

const str = "test1 apple test2 orange test3";
for (const match of str.matchAll(/test\d/g)) {
  console.log(
    `找到了${match[0]},起始位置为:${match.index}, 结束位置为:${
      match.index + match[0].length
    }`
  );
}
// 找到了test1,起始位置为:0, 结束位置为:5
// 找到了test2,起始位置为:12, 结束位置为:17
// 找到了test3,起始位置为:25, 结束位置为:30

BigInt

一个内置对象,可以表示任意大的整数。

Js 中 Number类型只能安全的表示-(2^53-1)至 2^53-1 范的值,BigInt 可以超出2的53次方。

可以用在一个整数字面量后面加 n 的方式定义一个 BigInt,或者调用函数 BigInt()

const num1 = 9007199254740991n;
const num2 = BigInt(9007199254740991);
const num3 = BigInt(0b1111111111111111111);
const num4 = BigInt(0xfffffffffffff);

BigInt在某些方面类似于 Number,但是也有几个关键的不同点:不能用于Math对象中的方法;不能和任何 Number实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number变量时可能会丢失精度。

BigInt可以用于这些运算符+*-**%,以及除 >>> (无符号右移)之外的位运算符,因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigIntBigInt 不支持单目 (+) 运算符。

当使用 BigInt 时,带小数的运算会被取整。

console.log(BigInt(5) / BigInt(2)); // 2n

Promise.allSettled()

Promise.all()接受一个 Promise 可迭代对象作为输入,并返回一个单独的promise,但是该方法输入的Promise中有任何Promise被reject,则返回的 Promise 将进入reject状态,并带有第一个被拒绝的原因。

而**Promise.allSettled()** 则当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

const p1 = Promise.resolve(1);
const p2 = 2;
const p3 = Promise.reject(3);

Promise.all([p1, p2, p3])
  .then((data) => {
    console.log("Promise.all then:", data);
  })
  .catch((err) => {
    console.log("Promise.all catch:", err);
  });

Promise.allSettled([p1, p2, p3])
  .then((data) => {
    console.log("Promise.allSettled then:", data);
  })
  .catch((err) => {
    console.log("Promise.allSettled catch:", err);
  });

// 打印结果如下
/*
Promise.allSettled then: [
  { status: 'fulfilled', value: 1 },
  { status: 'fulfilled', value: 2 },
  { status: 'rejected', reason: 3 }
]
Promise.all catch: 3
*/

globalThis

不同的 JavaScript 环境中获取全局对象需要不同的语句。

  • 在 Web 中,可以通过 windowself 或者 frames 取到全局对象。
  • 但是在 Web Workers中,只有 self 可以。
  • 在 Node.js 中,必须使用 global

globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。

可选链运算符(?.)

通过连接的对象的引用或函数可能是 undefinednull 时,可选链运算符提供了一种方法来简化被连接对象的值访问。

在引用为空(null或者undefined) 的情况下不会引起错误,可选链表达式短路返回值是 undefined

const obj = {
  name: "jack",
  cat: {
    name: "mimi",
    age: 6,
  },
};

console.log(obj.dog?.age); // undefined
console.log(obj.run?.()); // undefined

空值合并运算符(??)

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

与逻辑或运算符不同的是,当左侧的操作数为假值(即在布尔上下文中认定为false的值,比如''0)时,会返回其右侧操作数。

const valA = false ?? 1;
const valB = 0 ?? 1;
const valC = null ?? 1;
const valD = undefined ?? 1;

console.log(valA, valB, valC, valD); // false 0 1 1

Dynamic Import()

ES Module是一套静态的模块系统,在之前,import/export 声明只能出现在顶层作用域,不支持按需加载、懒加载。但是现在已经支持动态加载模块了。

const btn = document.querySelector(".button");
btn.addEventListener("click", () => {
  import("./resources.js").then((res) => {
    console.log(res);
  });
});

import.meta

import.meta是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的 URL。

console.log(import.meta.url)

export-as-from

聚合模块,导入并导出

export * as module from "module-name";

// 相当于

import * as module from "module-name";
export { module };
export * from "module-name";
export * as name1 from "module-name";
export { name1, /* …, */ nameN } from "module-name";
export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name";
export { default, /* …, */ } from "module-name";
export { default as name1 } from "module-name";

ES12(ECMAScript2021)

String.prototype.replaceAll()

与replace类似,该方法有两个参数replaceAll(pattern, replacement)replaceAll() 方法返回一个新字符串,其中所有匹配 pattern 的部分都被替换为 replacementpattern 可以是一个字符串或一个 RegExpreplacement 可以是一个字符串或一个在每次匹配时调用的函数。原始字符串保持不变。

const str = "My dog is bigger than your dog";
const newStr = str.replaceAll("dog", "cat");
console.log(newStr); // My cat is bigger than your cat

Promise.any()

Promise.any()将一个 Promise 可迭代对象作为输入,返回第一个成功的值。与Promise.race()不同的是,Promise.race()是第一个promise敲定(resolve或reject)时返回。当所有输入 Promise 都被reject时,会以一个包含拒绝原因数组的AggregateError拒绝。

const p1 = Promise.reject(1);
const p2 = Promise.reject(2);
const p3 = Promise.resolve(3);

Promise.race([p1, p2, p3])
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err); // 1
  });

Promise.any([p1, p2, p3])
  .then((data) => {
    console.log(data); // 3
  })
  .catch((err) => {
    console.log(err);
  });

Promise.any([p1, p2])
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err); // [AggregateError: All promises were rejected] { [errors]: [ 1, 2 ] }
  });

逻辑赋值

&&=

逻辑与赋值x &&= y)运算仅在 x 为真值(布尔转换后为true的值)时为其赋值。

let a = 0;
let b = 1;

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

||=

逻辑或赋值(x ||= y)运算仅在 x 为假值(布尔转换后为false的值)时为其赋值。

let a = 0;
let b = 1;

a ||= b;
console.log(a); // 1
b ||= a;
console.log(b); // 1

??=

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

let a = 0;
let b = undefined;
let c = 1;

a ??= 2;
console.log(a); // 0
b ??= 2;
console.log(b); // 2
c ??= 2;
console.log(c); // 1

数值分隔符

定义number数据时,可以使用_分割符,让数据更美观清晰。

const num1 = 10_00_000_000;
console.log(num1); // 1000000000
const num2 = 1_1n;
console.log(num2); // 11n
const num3 = 0b11011_101;
console.log(num3); // 221

WeakRef

WeakRef 对象允许你保留对另一个对象的弱引用。

对象的弱引用是指该引用不会阻止 GC 回收这个对象。而与此相反的,一个普通的引用(或者说强引用)会将与之对应的对象保存在内存中。只有当该对象没有任何的强引用时,JavaScript 引擎 GC 才会销毁该对象并且回收该对象所占的内存空间。如果上述情况发生了,那么你就无法通过任何的弱引用来获取该对象。

使用deref() 可以返回当前实例的 WeakRef 对象所绑定的 target 对象,如果该 target 对象已被 GC 回收则返回undefined

let obj = { a: 1 };
const weakRef = new WeakRef(obj);
console.log(weakRef.deref()); // { a: 1 }

ES13(ECMAScript2022)

Class声明式的类字段

在之前,如果要声明类字段,只能在constructor 中通过向 this 赋值实现

现在则可以直接在最外层书写声明式的类字段

// old
class Person {
  constructor() {
    this.name = "John";
  }
}

// new
class Person {
  name = "John";
}

Class 私有属性

我们可以通过添加#前缀来创建私有属性,私有属性在类的外部无法合法地引用。

class Animal {
  #name = "mimi";

  #takeName(val) {
    this.#name = val;
  }

  get name() {
    return this.#name;
  }

  set name(val) {
    this.#takeName(val);
  }
}

const animalOne = new Animal();
console.log(animalOne.name); // mimi
animalOne.name = "doudou";
console.log(animalOne.name); // doudou

console.log(ClassOne.#name); // 报错
console.log(ClassOne.#takeName); // 报错

私有静态属性,一样添加# 前缀创建

class Animal {
  static #name = "mimi";

  static #takeName(val) {
    this.#name = val;
  }

  static get name() {
    return this.#name;
  }

  static set name(val) {
    this.#takeName(val);
  }
}

console.log(Animal.name); // mimi
Animal.name = "doudou";
console.log(Animal.name); // doudou

console.log(Animal.#name); // 报错
console.log(Animal.#takeName); // 报错

扩展了in操作符对于私有属性的检查

class Animal {
  #name = "mimi";

  hasName() {
    return #name in this;
  }
}

const myAnimal = new Animal();
console.log(myAnimal.hasName()); // true

static静态初始化块

static 关键字除了定义静态方法和字段外,现在还可以定义静态初始化块。

在类中可以通过static关键字定义一系列静态代码块,这些代码块会在类被创造时执行一次

class Animal {
  static name = "mimi";

  static {
    this.name = "doudou";
  }
}

console.log(Animal.name); // doudou

await可以在模块最外层使用

await除了在async函数中使用,还可以在模块最外层使用。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
</html>
<script type="text/javascript">
  const fn = () => Promise.resolve(1);
  await fn();
</script>

尝试上面代码打开页面会发现,页面报错了,报错如下

image-20240531172821492.png

这是因为要在模块的最外层(或者叫顶级主体)才有效,注意是 模块。

修改 script 标签为 module 后,发现代码正常生效了。

<script type="module">
  const fn = () => Promise.resolve(1);
  await fn();
</script>

Array.prototype.at()

at() 方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。

const arr = [1, 2, 3, 4, 5, 6];

console.log(arr.at(2)); // 3
console.log(arr.at(-2)); // 5

String.prototype.at()

与上面类似,接收一个整数值并返回一个新的字符串,该字符串由位于指定偏移量处的单个 UTF-16 码元组成。允许正整数和负整数。负整数从数组中的最后一个元素开始倒数。

const str = "abcdefghijk";

console.log(str.at(2)); // c
console.log(str.at(-2)); // j

Object.hasOwn()

该方法接收两个参数,第一个是js对象,第二个是测试的属性,string或Symbol。

如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false

let obj = {
  a: 1,
};
console.log(Object.hasOwn(obj, "a")); // true
console.log(Object.hasOwn(obj, "toString")); // false

let arr = ["a", "b", "c"];
console.log(Object.hasOwn(arr, 2)); // true
console.log(Object.hasOwn(arr, 3)); // false

Object.hasOwn() 旨在取代 Object.prototype.hasOwnProperty()。在支持 Object.hasOwn的浏览器中,建议使用 Object.hasOwn(),而非 hasOwnProperty()

因为Object.prototype.hasOwnProperty()会有以下问题

1、JavaScript 并不保护属性名称 hasOwnProperty;具有此名称(hasOwnProperty)属性的对象可能会返回不正确的结果:

const obj = {
  hasOwnProperty() {
    return false;
  },
  a: 1,
};
console.log(obj.hasOwnProperty("a")); // false
console.log(Object.hasOwn(obj, "a")); // true

2、因为hasOwnPropertyObject.Object.prototype中,使用Object.create(null) 创建的对象不从 Object.prototype 继承,使得 hasOwnProperty() 不可访问。

const obj = Object.create(null);
obj.a = 1;
console.log(obj.hasOwnProperty("a")); // 报错,TypeError: obj.hasOwnProperty is not a function
console.log(Object.hasOwn(obj, "a")); // true

Error: cause

Error实例中的 cause 数据属性指示导致该错误的具体原始原因。

它通过 options.cause 参数被传入Error()构造函数,并且有可能不存在。

function isNumber(val) {
  if (Object.prototype.toString.call(val) !== "[object Number]") {
    throw new Error("error", {
      cause: { message: "该参数类型不是数值", input: val },
    });
  }
}

try {
  isNumber("a");
} catch (error) {
  console.log(error.cause); // { message: '该参数类型不是数值', input: 'a' }
}

Array.prototype.findLast()

findLast() 方法反向迭代数组,也就是从尾部开始查找并返回满足提供的测试函数的第一个元素的值。如果没有找到对应元素,则返回 undefined

find方法查找方向相反,find是从头部开始查找。

const arr = [1, 2, 3, 4, 5, 6];
console.log(arr.findLast((e) => e % 2 === 0)); // 6

Array.prototype.findLastIndex()

findLastIndex() 方法反向迭代数组,也就是从尾部开始查找并返回满足所提供的测试函数的第一个元素的索引。若没有找到对应元素,则返回 -1。

findIndex方法查找方向相反,findIndex是从头部开始查找。

const arr = [1, 2, 3, 4, 5, 6];
console.log(arr.findLastIndex((e) => e % 2 === 0)); // 5

RegExp扩展

通过给正则表示式添加d标签,RegExp.prototype.exec()String.prototype.match 等的结果会附加了一个 indices 属性。

indices 属性本身是一个索引数组,其中包含每个捕获的子字符串的一对开始索引和结束索引。

const text = "apple";
const reg = /pp/d;
console.log(reg.exec(text));
/*
[
  'pp',
  index: 1,
  input: 'apple',
  groups: undefined,
  indices: [ [ 1, 3 ], groups: undefined ]
]
*/

另外,索引数组本身将具有一个 groups 属性,其中包含每个具名捕获组的开始索引和结束索引。

let users = `名单:姓氏:李,名字:雷`;
let regexpNames = /姓氏:(?<first>.+),名字:(?<last>.+)/d;
const res = regexpNames.exec(users);
console.log(res);
/*
[
  '姓氏:李,名字:雷',
  '李',
  '雷',
  index: 3,
  input: '名单:姓氏:李,名字:雷',
  groups: [Object: null prototype] { first: '李', last: '雷' },
  indices: [
    [ 3, 12 ],
    [ 6, 7 ],
    [ 11, 12 ],
    groups: [Object: null prototype] { first: [Array], last: [Array] }
  ]
]
*/
console.log(users.slice(res.indices[0][0], res.indices[0][1])); // 姓氏:李,名字:雷
console.log(users.slice(res.indices[1][0], res.indices[1][1])); // 李
console.log(users.slice(res.indices[2][0], res.indices[2][1])); // 雷
console.log(res.indices.groups["first"]); // [6, 7]
console.log(res.indices.groups["last"]); // [11, 12]

ES14(ECMAScript2023)

Array.prototype.toSorted()

Array实例的 toSorted() 方法是sort()方法的复制方法版本。它返回一个新数组,而sort()会修改原数组。

sort()toSorted()参数一样。

const arr1 = [2, 1, 4, 5, 3];
const arr2 = arr1.toSorted();
console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
// 原数组: [ 2, 1, 4, 5, 3 ]
// 返回的数组:[1, 2, 3, 4, 5]

const arr3 = [2, 1, 4, 5, 3];
const arr4 = arr3.sort();
console.log("原数组:", arr3, "\n", "返回的数组:", arr4);
// 原数组: [ 1, 2, 3, 4, 5 ]
// 返回的数组: [ 1, 2, 3, 4, 5 ]

Array.prototype.toReversed()

Array 实例的toReversed()方法是reverse()方法对应的复制版本。它返回一个元素顺序相反的新数组,而reverse()会修改原数组。

const arr1 = [1, 2, 3, 4];
const arr2 = arr1.toReversed();
console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
// 原数组: [ 1, 2, 3, 4 ]
// 返回的数组: [ 4, 3, 2, 1 ]

const arr3 = [1, 2, 3, 4];
const arr4 = arr3.reverse();
console.log("原数组:", arr3, "\n", "返回的数组:", arr4);
// 原数组: [ 4, 3, 2, 1 ]
// 返回的数组: [ 4, 3, 2, 1 ]

Array.prototype.toSpliced()

Array实例的toSpliced()方法是splice()的复制版本。它返回一个新数组,新数组为删除/添加/替换后的新数组。不会修改原数组。

splice()会修改原数组,该方法返回的是包含了删除的元素的数组。

const arr1 = ["jack", "mike", "jay", "lucy"];
const arr2 = arr1.toSpliced(1, 2, "depp");
console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
// 原数组: [ 'jack', 'mike', 'jay', 'lucy' ]
// 返回的数组: [ 'jack', 'depp', 'lucy' ]

const arr3 = ["jack", "mike", "jay", "lucy"];
const arr4 = arr3.splice(1, 2, "depp");
console.log("原数组:", arr3, "\n", "返回的数组:", arr4);
// 原数组: [ 'jack', 'depp', 'lucy' ]
// 返回的数组: [ 'mike', 'jay' ]

Array.prototype.with()

Array实例的 with() 方法是使用方括号表示法修改指定索引值的复制方法版本。它会返回一个新数组,其指定索引处的值会被新值替换。

该方法接收两个参数,第一个参数为要修改的数组索引(负数索引会从数组末尾开始计数——即当 index < 0 时,会使用 index + array.length),第二个参数为分配给指定索引的值。

const arr1 = ["jack", "mike", "jay", "lucy"];
const arr2 = arr1.with(1, "depp");
console.log("原数组:", arr1, "\n", "返回的数组:", arr2);
// 原数组: [ 'jack', 'mike', 'jay', 'lucy' ]
// 返回的数组: [ 'jack', 'depp', 'jay', 'lucy' ]

const arr3 = ["jack", "mike", "jay", "lucy"];
arr3[1] = "depp";
console.log(arr3); // [ 'jack', 'depp', 'jay', 'lucy' ]

Symbol 作为 WeakMap 键

WeakMaps 仅允许使用对象作为键,这是 WeakMaps 的一个限制。新功能扩展了 WeakMap API,允许使用唯一的 Symbol 作为键。

并在WeakRefFinalizationRegistry 中支持 Symbol。

const weakMap = new WeakMap();
const SymbolKey = Symbol("a");
weakMap.set(SymbolKey, "b");
console.log(weakMap.get(SymbolKey)); // b

es14开始,Symbol 类型的值也可以作为弱引用了。但是并不是所有的 Symbol 类型都可以作为弱引用。例如Symbol.for()创建的 Symbol 是不可以作为弱引用的。

因为Symbol.for()创建的 symbol会被放入symbol注册表中。Symbol.for(key)根据给定的键 key从运行时的symbol注册表中查找对应的symbol,找到则返回,否则就是返回新创建的 symbol。

const symbolA = Symbol("aaa");
const symbolB = Symbol("aaa");
console.log(symbolA === symbolB); // false

const symbolC = Symbol.for("bbb");
const symbolD = Symbol.for("bbb");
console.log(symbolC === symbolD); // true

因为Symbol.for()创建的symbol随时有可能被找回。所以其不可以作为弱引用。

const weakMap = new WeakMap();
const SymbolKey = Symbol.for("a");
weakMap.set(SymbolKey, "b"); // 报错,TypeError: Invalid value used as weak map key

Symbol.iterator也可以作为Symbol创建的键

const weakMap = new WeakMap();
weakMap.set(Symbol.iterator, "a");
console.log(weakMap.get(Symbol.iterator)); // a