百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

2025 年每个 JavaScript 开发者都应该了解的功能

ztj100 2025-05-28 21:44 6 浏览 0 评论

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。

1.Iterator helpers

开发者经常会对同一个数组执行多次链式转换,例如:

const arr = [];
arr
  .slice(10, 20)
  .filter((el) => el < 10)
  .map((el) => el + 5);

这种方式非常低效,因为每次转换都需要分配一个新数组。JS 引入了迭代器方法,其工作原理与常规数组转换类似。不同之处在于其不会创建临时数组,而是创建新的迭代器,这些迭代器会在其他迭代器上进行迭代。

  • Iterator.prototype.drop():返回一个新的 Iterator Helper 对象,该对象会跳过此迭代器开头指定数量的元素,其作用大致与常规数组的 Array.prototype.slice(n) 相同
  • Iterator.prototype.take():返回一个新的 Iterator Helper 对象,该对象从此迭代器的开头获取指定数量的元素,其作用与常规数组的 Array.prototype.slice(0, n) 相同
  • Iterator.prototype.some():类似于 Array.prototype.some(),其测试迭代器生成的元素是否至少有一个通过了所提供函数实现的测试元素
  • Iterator.prototype.every():类似于 Array.prototype.every(),其测试迭代器生成的所有元素是否都通过了开发者所提供函数的测试
  • Iterator.prototype.filter():类似于 Array.prototype.filter(),其返回一个基于筛选值的迭代器
  • Iterator.prototype.find():类似于 Array.prototype.find(),其返回迭代器生成的第一个满足所提供测试函数的元素,否则返回 undefined
function* fibonacci() {
  let current = 1;
  let next = 1;
  while (true) {
    yield current;
    [current, next] = [next, current + next];
  }
}
const isEven = (x) => x % 2 === 0;
console.log(fibonacci().find(isEven));
// 输出 2
const isNegative = (x) => x < 0;
console.log(fibonacci().take(10).find(isNegative));
// undefined
console.log(fibonacci().find(isNegative));
// Never completes
  • Iterator.prototype.flatMap():类似于 Array.prototype.flatMap(),其返回一个基于扁平化值的迭代器
// 展平可迭代对象
const map1 = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3],
]);
const map2 = new Map([
  ["d", 4],
  ["e", 5],
  ["f", 6],
]);
const merged = new Map([map1, map2].values().flatMap((x) => x));
console.log(merged.get("a"));
// 1
console.log(merged.get("e"));
// 5

该方法可以避免创建任何 map 内容的临时副本。但需要注意的是,数组 [map1, map2] 必须先通过. values() 转换为迭代器,因为 Array.prototype.flatMap() 只能展平数组,而不能展平可迭代对象。

  • Iterator.prototype.forEach():类似于 Array.prototype.forEach(),其对迭代器生成的每个元素执行一次提供的函数
  • Iterator.prototype.map():类似于 Array.prototype.map(),其返回一个由映射函数转换后的值的迭代器
  • Iterator.prototype.reduce():类似于 Array.prototype.reduce(),其对迭代器生成的每个元素执行用户提供的 reducer 回调函数,并传入前一个元素计算的返回值
// 该示例创建迭代器,该迭代器产生斐波那契数列中的项,然后对前十个项求和
function* fibonacci() {
  let current = 1;
  let next = 1;
  while (true) {
    yield current;
    [current, next] = [next, current + next];
  }
}
console.log(
  fibonacci()
    .take(10)
    .reduce((a, b) => a + b),
);
// 输出 143
  • Iterator.prototype.toArray():使用填充的生成值创建一个数组

创建可迭代对象的常用方法是通过静态方法 Iterator.from() 以及 Array、NodeList、Set 和许多其他容器的 values() 方法。因此,上面转换链示例的更节省内存的版本是:

const arr = [];
arr
  .values()
  .drop(10)
  .take(10)
  .filter((el) => el < 10)
  .map((el) => el + 5)
  .toArray();

需要注意的是,这是一个相对较新的功能,最后一个开始支持此功能的主流浏览器是 Safari,其从 2025 年 3 月 31 日才开始支持。

2. 数组的 at() 方法

Array.prototype.at() 是访问第 n 个元素的另一种方法,其还支持负索引,即从最后一个元素开始计数。

例如,[10,20,30].at(-1) 将返回 30,[10,20,30].at(-2) 将返回 20 等等。这种负索引使得访问最后一个元素变得非常容易。在此之前,开发者则必须编写丑陋的样板代码 arr[arr.length - 1]。

3.Promise.withResolvers()

在异步调用场景,开发者经常会编写如下代码:

let resolve, reject;
const promise = new Promise((resolver, rejector) => {
  resolve = resolver;
  reject = rejector;
});
// 在特定场景下调用 resolve 或者 reject 方法

但是,借助于最新的 Promise.withResolvers(),开发者可以轻松实现同样的功能。

const {promise, resolve, reject} = Promise.withResolvers();

Promise.withResolvers() 静态方法返回一个对象,其中包含一个新的 Promise 对象和两个用于 resolve 或 reject 的函数,对应于传递给 Promise() 构造函数执行器的两个参数。

4.String.prototype.replace() / String.prototype.replaceAll() 回调

这是个老生常谈的问题,很多开发者不知道 String.prototype.replace() 或
String.prototype.replaceAll() 的第二个参数可以传入回调函数而不仅仅是字符串。例如:

let counter = 0;
console.log(
  "NUMBER, NUMBER, NUMBER".replaceAll(
    "NUMBER",
    (match) => match + "=" + ++counter
  )
); // NUMBER=1, NUMBER=2, NUMBER=3

replace() 和 replaceAll() 是一个非常强大的功能,其允许一次性完成多次替换。而且,从性能和内存角度来看都非常高效。

5. 交换变量的最新方法

很多开发者经常使用下面的方式来交换变量:

let a = 1,
  b = 2;
console.log(a, b);
// 当前输出 1, 2
const temp = a;
a = b;
b = temp;
console.log(a, b);
// 交换后的输出 2, 1

现在 JavaScript 引擎提供了更加简单的方式来实现同样的功能:

let a = 1,
  b = 2;
console.log(a, b);
// 交换前 1, 2
[a, b] = [b, a];
console.log(a, b);
// 交换后 2, 1

6. 结构化克隆 structuredClone()

大部分浏览器都已经支持 structuredClone() API,其可以深度复制大多数常规对象从而替换 JSON.stringify() 和 JSON.parse() 进行深度复制的功能。因为后者在很多场景可能并不符合预期:

  • JSON.stringify() 不支持某些值,例如: NaN 或 undefined,这些值会被跳过或转换为 null。同时,对于某些数据类型,例如: BigInt,该方法甚至会抛出异常。
  • JSON.stringify() 无法处理包含循环引用的对象:
const obj = {};
obj.selfReference = obj;
console.log(JSON.stringify(obj));
// 直接抛出异常
  • 对于较大的对象来说,该方法效率不高且浪费大量内存

因此总体来看,开发者应尽可能优先使用 structuredClone(),其还会循环引用对象。

const obj = {};
obj.selReference = obj;
const clonedObj = structuredClone(obj);
console.log(obj === clonedObj);
// 输出 false
console.log(clonedObj.selReference === clonedObj);
// 注意:输出 true

7.Tagged templates

大多数开发者都熟悉模板字面量 "`",但很多人并不知道标记模板,标记模板允许开发者使用函数解析模板字面量。标记函数的第一个参数包含一个字符串值数组,其余参数与表达式相关。当想对插值,甚至整个字符串,进行一些自动转换时,标记模板非常有用。

`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`

tagFunction`string text ${expression} string text`

例如,下面代码示例会在进行插值时自动转义 HTML 文本:

function escapeHtml(strings, ...arguments) {
  const div = document.createElement("div");
  let output = strings[0];
  for (let i = 0; i < arguments.length; ++i) {
    div.innerText = arguments[i];
    output += div.innerHTML;
    output += strings[i + 1];
  }
  return output;
}
console.log(escapeHtml`<br> ${"<br>"}`);
// 输出结果 <br> <br>

8.WeakMap / WeakSet

除了 Map 和 Set 之外,JavaScript 还支持 WeakMap 和 WeakSet。

WeakMap 和 WeakSet 与 Map 和 Set 类似,不同之处在于其不允许将原始值作为键,并且不提供迭代器。这样做的原因是,当指向某个键的所有引用丢失时,该键以及可能关联的值必须能够从 Map/Set 中释放并被垃圾回收。

const set = new WeakSet();
const map = new WeakMap();
{
  const key1 = new Date();
  const key2 = new Date();
  console.log(set.has(key1));
  // false
  set.add(key1);
  console.log(set.has(key1));
  // true
  console.log(map.get(key2));
  // undefined
  map.set(key2, 10);
  console.log(map.get(key2));
  // 10
}
// 注意:此处丢失了对 key1 和 key2 的引用,因此键和值会将被垃圾收集

因此,如果开发者希望将某事物与某个对象关联而不产生副作用,可以考虑使用 WeakMap 或 WeakSet。

9.Set 操作

最近,JavaScript 增加了对 Set 对象的布尔运算支持。

Set.prototype.difference()

集合差运算:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
console.log(set1.difference(set2));
// 输出 Set(2) {1, 2}

Set.prototype.intersection()

执行集合交运算:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
console.log(set1.intersection(set2));
// 输出 Set(2) {3, 4}

Set.prototype.union()

执行集合并操作:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
console.log(set1.union(set2));
// 输出 Set(6) {1, 2, 3, 4, 5, 6}

Set.prototype.symmetricDifference()

除去两个集合的公共元素:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
console.log(set1.symmetricDifference(set2));
// Set(4) {1, 2, 5, 6}

Set.prototype.isDisjointFrom()

返回一个布尔值,指示该集合与给定集合没有共同元素:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
const set3 = new Set([5, 6]);
console.log(set1.isDisjointFrom(set2));
// 输出 false
console.log(set1.isDisjointFrom(set3));
// 输出 true

Set.prototype.isSubsetOf()

返回一个布尔值,指示该集合的所有元素是否都在给定集合中:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
const set3 = new Set([5, 6]);
console.log(set1.isSubsetOf(set2));
// 输出 false
console.log(set3.isSubsetOf(set2));
// 输出  true

Set.prototype.isSupersetOf()

返回一个布尔值,指示给定集合的所有元素都在此集合中:

const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
const set3 = new Set([5, 6]);
console.log(set2.isSupersetOf(set1));
// 输出 false
console.log(set2.isSupersetOf(set3));
// 输出 true

参考资料

https://waspdev.com/articles

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/flatMap

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

https://medium.com/@emre.deniz/es2025-the-evolution-of-javascript-93d9f6fd5f5b

https://blog.bitsrc.io/javascript-iterables-vs-iterators-009162379a15?gi=7c4c7a926564

https://www.youtube.com/watch?v=8p9lPsui7Co

相关推荐

Spring IoC Container 原理解析

IoC、DI基础概念关于IoC和DI大家都不陌生,我们直接上martinfowler的原文,里面已经有DI的例子和spring的使用示例...

SQL注入:程序员亲手打开的潘多拉魔盒,如何彻底封印它?

一、现象:当你的数据库开始"说话",灾难就来了场景还原:...

Java核心知识3:异常机制详解

1什么是异常异常是指程序在运行过程中发生的,由于外部问题导致的运行异常事件,如:文件找不到、网络连接失败、空指针、非法参数等。异常是一个事件,它发生在程序运行期间,且中断程序的运行。...

MyBatis常用工具类三-使用SqlRunner操作数据库

MyBatis中提供了一个非常实用的、用于操作数据库的SqlRunner工具类,该类对JDBC做了很好的封装,结合SQL工具类,能够很方便地通过Java代码执行SQL语句并检索SQL执行结果。SqlR...

爆肝2W字梳理50道计算机网络必问面试题

1.说说HTTP常用的状态码及其含义?思路:这道面试题主要考察候选人,是否掌握HTTP状态码这个基础知识点。...

SpringBoot整合Vue3实现发送邮箱验证码功能

1.效果演示2.思维导图...

最全JAVA面试题及答案(200+)

Java基础1.JDK和JRE有什么区别?JDK:JavaDevelopmentKit的简称,Java开发工具包,提供了Java的开发环境和运行环境。JRE:JavaRunti...

Java程序员找工作翻车现场!你的项目描述踩了这几个坑?

Java程序员找工作翻车现场!你的项目描述踩了这几个坑?噼里啪啦敲了三年代码,简历一投石沉大海?兄弟,问题可能出在项目描述上!知道为什么面试官看你的项目像看天书吗?因为你写了三个致命雷区:第一,把项目...

2020最新整理JAVA面试题附答案,包含19个模块共208道面试题

包含的模块:本文分为十九个模块,分别是:Java基础、容器、多线程、反射、对象拷贝、JavaWeb、异常、网络、设计模式、Spring/SpringMVC、SpringBoot/Spring...

底层原理深度解析:equals() 与 == 的 JVM 级运作机制

作为Java开发者,你是否曾在集合操作时遇到过对象比较的诡异问题?是否在使用HashMap时发现对象丢失?这些问题往往源于对equals()和==的误解,以及实体类中这两个方法的不当实...

雪花算法,什么情况下发生 ID 冲突?

分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的...

50个Java编程技巧,免费送给大家

一、语法类技巧1.1.使用三元表达式普通:...

如何规划一个合理的JAVA项目工程结构

由于阿里Java开发手册对于工程结构的描述仅限于1、2节简单的概述,不能满足多样的实际需求,本文根据多个项目中工程的实践,分享一种较为合理实用的工程结构。工程结构的原则有依据、实用。有依据的含义是指做...

Java 编程技巧之单元测试用例编写流程

温馨提示:本文较长,同学们可收藏后再看:)前言...

MyBatis核心源码解读:SQL执行流程的奇妙之旅

MyBatis核心源码解读:SQL执行流程的奇妙之旅大家好呀!今天咱们要来一场既烧脑又有趣的旅程——探索MyBatis这个强大框架的核心秘密。你知道吗?当你在项目里轻轻松松写一句“select*f...

取消回复欢迎 发表评论: