首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐
2025年5月15日 星期四 9:25am

姚期智提出的"百万富翁"难题被破解? 多方安全计算MPC到底是个什么鬼?

  • 24-03-05 03:41
  • 3660
  • 6181
blog.csdn.net

640?wx_fmt=gif

640?wx_fmt=jpeg

作者 | 章磊

责编 | Aholiab

出品 | CSDN、ARPA



在越来越多对数据隐私的担忧声中,政府开始行动制定数据使用合规法案。而另一方面,对数据的保护,却产生了一个矛盾:大量的数据因为需要依法保护而无法被联合在一起计算。


反过来试想一下,如果全世界的基因数据能够联合在一起分析,人类或许可以更快找到癌症的解药。这让我们大胆地去思考,是否存在一种保护数据安全而又能够有效利用数据的方式?


1980年代,姚期智院士提出了「百万富翁」问题:两个百万富翁街头邂逅,他们都想炫一下富,比比谁更有钱,但是出于隐私,都不想让对方知道自己到底拥有多少财富,如何在不借助第三方的情况下,让他们知道他们之间谁更有钱?


640?wx_fmt=jpeg

在这个经典问题之下,诞生了「多方安全计算」(Multiparty Computation, MPC)这门密码学分支。MPC技术能够让数据在不泄露的情况下联合多方的数据进行联合计算并得到明文计算结果,最终实现数据的所有权和数据使用权的分离。


今天我们就来介绍一下MPC的出现背景和应用场景。在开始前,我们先来看看如今数据对于我们的意义。



我们正生活在数据保护的时代


个人隐私和数据隐私


2018 年 5 月 25 日正式生效的欧盟通用数据保护条例(GDPR)引起全球广泛关注,这部被称为「史上最严」的数据保护法案将对科技行业和个人生活产生深远影响,因为它是人类历史上第一个定义个人数据所有权的规则,它在法律上明确规定了个人数据是个人所有的数据资产。


这项法律将保障人们对个人数据有更多的掌控权。举例说明,社交网络公司在使用你的数据前必须征得你的同意。此项法律对创Facebook等科技巨头无疑影响很大,因为这些公司就指望着用户数据赚钱。


商业利益中的数据保护


数据是现代商业与个人的核心价值与重要资产。数据正在重新塑造人类生活的方方面面,包括 金融、广告、零售、医疗、物流、能源和工业等。


随着人工智能时代的到来,数据在现代商业活动中也成为了最重要的竞争资源。巨头公司利用数据垄断的优势建筑起了行业壁垒。


例如,打车软件公司拥有人们每天出行的数据,包括乘客的起点与终点,他们可以利用这些数据来优化自己的产品和业务,甚至是用这些数据来进行一些预测,比如一个房地产价格指数或者一个政府道路优化方案。


数据的融合可提高其价值,数据的交叉使用可产生协同作用。但因为数据本身的可复制性和易传播性,一经分享无法追踪使用情况,数据资产的分享与协同开发受到严重制约。


既然如此,那数据保护的价值又从何说起呢?



被保护的数据如何产生价值?


虽然个人对隐私的保护、商业公司的数据保护,都是正当的利益诉求,但却产生了一个个数据孤岛。拥有数据源的中小型公司无法安全的将数据共享或变现。


对于数据使用者,大数据公司、开发者和科学家仅能接触到有限的数据集,并且费用高昂。与运营商等大数据源的合作需要开发人员现场部署模型于数据源的服务器上,模型算法存在泄露风险,且效率低下。


一方面,数据需要得到保护和隔离;另一方面,数据对人类社会的价值在于联合在一起的计算和分析。这是否是一个不可破解的矛盾?


理想情况下,我们可以委托一个安全可信的第三方对数据进行计算。然而,现实中,要么数据太重要而没有第三方,要么第三方会因为有了数据而拥有过多的权利,例如信用卡公司和电商公司,如果乙方能有对方的数据,会非常可怕。


要解决这个问题,就回到了本文最初提到的「多方安全计算技术」(MPC)。通过MPC,我们可以实现联合多方的隐私数据,在没有一个可信第三方的情况下,一起计算并得到分析的结果,而不担心各自的数据被泄露。


MPC是一套基于现代密码学的协议组,这个工具组里面有很多组件组成。


简单的来说,这套工具组里面有零知识证明(ZKP)、概率加密、信息理论消息认证码(MAC),各种分布式沟通协议和不经意的转移(OT),以及最重要的基础技术:秘密共享和秘密分片计算是实现安全多方计算的基础。


特别是,在被动对手的情况下,Shamir的多项式秘密共享是多方计算的基石,而Chor、Goldwasser、Micali和Awerbuch的可验证秘密共享在拜占庭对手问题中起着类似的作用。


在过去35年中,MPC算法和工程设计得到了实质性的改进,并且已经达到性能上不需要考虑协议性能视为使用的主要障碍的程度。 


MPC社区采用了事实上的基准,即在两个参与者之间执行AES加密,一个带有加密消息,另一个带有密钥。 AES包含各种算术和布尔运算符,因此非常适合直接在硬件和MPC中进行计算。 在过去十年中,安全计算提高了4-5个数量级。


出于比较目的和考虑摩尔定律的影响,下图显示了在相同时间段内本机AES计算的性能。


640?wx_fmt=jpeg

通过MPC实现的性能提升 


接下来,我们再来通过一个例子,更清晰的理解MPC的实现原理。请看下图:


640?wx_fmt=png

 

根据上图所示,假设我们的目标是联合计算所有各方秘密数据的总和,这可以通过秘密共享来实现。


首先,每一方将其秘密号码随机分成三部分,并将其中两部分别分享给其他部分。


然后,每个方在本地对来自其他对等方及其自身的所有三个共享进为了公开最终结果,每个方的本地总和(local sum)都会公开给同行(Peers)。


最后,任何一方都可以通过将所有三个公共本地总和相加来知道最终结果。


秘密共享的关键点在于,通过了解秘密共享,一方不会获知有关私有数据的信息。例如,在通过揭示秘密共享5的三方计算中,秘密数据可以是10、79、-11这样的随机数字。即使知道秘密共享,该方也可以猜测私人数据,而不是猜测随机数。


由于在整个过程中没有显示隐私数据,因此秘密共享计算可以保护隐私。对手方不能发现秘密信息。


正式因为拥有这样的特性,MPC在现实世界中受到越来越多的重视,也被更多领域所采用。比如以下3类场景。


联合征信 

MPC可赋能金融、保险企业对客户的负债率等风险指标进行联合分析。目前各家金融、保险、资产管理机构只掌握客户部分数据,从而导致风险评估误差。联合分析不泄露各参与方数据,对客户的风险有整体评估,在多头借贷等场景下能有效降低违约风险。 


多维度健康分析 

MPC赋能医疗机构对病人在多家医院的病历和智能硬件生物数据进行分析,从而在病人、医院和智能硬件厂商数据不泄露的情况下,对病人有更精准的诊断。同时,针对医疗机构的联合数据分析可以让药品研究机构对某特定地区特定病种有更全面的了解。 


联合精准营销

MPC赋能商户对潜在客户多维度信息进行分析,从而更精准的投放广告。广告投放机构可以从更多数据维度对客户购买意向建模,且数据源不泄露个人隐私数据。  


640?wx_fmt=png


MPC与现实世界


以上是几个例子过于简单,现实世界的情况比这更复杂。


例如,用于添加的MPC是容易的,因为可以在秘密共享上本地计算加法操作。但是,乘法更加困难,因为如果没有其他工具帮助,它不能单独在本地共享上计算。不过利用同态加密(Somewhat Homomorphic Encryption, SHE),有更复杂的MPC协议可以实现安全的乘法。


好消息是任何函数都可以转换为加法和乘法的组合,因此基于秘密共享的MPC能够进行任何类型的通用计算,就像现代PC一样。


另一个例子是主动恶意节点(Actively Malicious)。主动恶意被定义为节点将偏离协议,与被动恶意相反,其中节点试图学习其他对等方秘密数据但始终遵循协议。


在上述秘密共享示例中,虽然没有节点可以学习其他私有数据,但是恶意节点可以发布错误的本地共享总和,从而使所有其他对等体学习错误的最终结果。


有各种方法可以发现这种恶意行为,甚至可以防止这种行为的发生。最流行的一种称为消息验证代码(MAC),其中每个操作都与一个数字相关联,以验证其正确性。一旦节点发出错误的消息,这个错误将很容易被其他节点验证。


而伪造一个能通过验证的错误数据的难度将是极其困难,这个难度非常大以至于造假的成本大于数据的收益。



MPC与其他实现技术的对比


除了MPC之外,还有一些能够实现类似功能的技术,包括同态加密、零知识证明、可信执行环境等。


但这些技术与MPC相比,都有一定的不足,我们一个个的来看看。


同态加密

同态加密(HE)是一种加密形式,允许对密文进行计算,生成加密结果,加密后的结果与操作结果相匹配,就好像它们是在明文上执行一样。 


使用这样的工具,可以在不危及数据隐私的情况下外包存储和/或计算。 因为HE允许在保持加密的同时计算加密数据,所以它已被广泛研究作为安全计算的候选者。


640?wx_fmt=png

 

然而,即使最前沿同态加密方案仍然不能提供计有效运算深度算术电路。


首先,“bootstrapping”为已经非常繁重的过程增加了额外的成本。 目前,HE的实际应用主要集中在评估函数的优化上,这通过限制电路倍增深度来避免昂贵的过程。 


此外,根据该方案和目标安全级别,使用HE方案将导致巨大的密文扩展(从2,000到500,000甚至1,000,000倍的开销)。 这是因为同态方案必须是概率性的,以确保语义安全性和特定的基础数学结构。正如我们所看到的,SHE方案在HE变体中是最有希望的,它将在我们后面提到的安全计算程序中使用。


零知识证明

零知识证明(ZKP)是一种这样的方法:一方(证明者Peggy)可以向另一方(验证者Victor)证明她知道值x,而不传达任何信息,除了她知道值x(读起来好绕口)。


 最近很多的区块链项目在尝试利用ZKP作为可信的离线计算解决方案。在这些协议中,该运算模块被编译成电路并传输到第三方执行环境,在该环境中将使用该电路评估数据。


不过,与FHE方案类似,ZKP无法证明在远程环境中完成的实际工作量。 除此之外,ZKP也无法保证计算是从恶意方的黑客手中获得的。


可信执行环境

可信执行环境(TEE)是一种在防分离内核上运行的防篡改处理环境。理想的TEE保证了执行代码的真实性、运行时状态、寄存器、内存和敏感I / O的完整性、以及存储在持久内存中的代码、数据和运行时状态的机密性。此外,它应能够提供远程证明,证明其对第三方的可信赖性。


硬件制造商渴望提出他们自己的可信硬件解决方案,但缺乏不同平台的通用标准。 最杰出的工艺单元设计人员已将其硬件安全模块嵌入其产品中(例如英特尔软件保护扩展(SGX),ARM TrustZone,AMD安全加密虚拟化(SEV)和NVIDIA可信小内核(TLK)。


然而,最近的一些黑客攻击证明SGX还不能够承载协议级别的数据安全保护。 事实上,这种看似安全的协议并不安全。


远程鉴权不会阻止一个恶意云服务提供商首先忠实地响应远程证明查询,然后在enclave外部模仿远程鉴定协议(例如KeyGen和CSR)。


换句话说,SGX不是为“通用组合”(Universal Composition)设计的协议,其中协议的真实行为和理想世界定义(功能)在计算上对于每个对手控制的环境都是无法区分的。 简单来说,使用TEE,可以信任硬件,但不能信任控制硬件的人。 因此,SGX最好用于许可网络,其中所有节点都经过预先批准,环境经过认证和信任。


640?wx_fmt=png

 

在对即同态加密、零知识证明和可信执行环境有了基本了解之后,我们可以得出这样的结论:虽然某些技术具有计算效率等优势,但它们无法提供无先验网络(permissionless network)所需的安全性和功能。 


具体来说,一个好的技术解决方案,需要能够验证计算的安全性,正确性和隐私保护性:


  • 效率:计算的效率

  • 隐私保留:在这里指的是在不向任何节点透露细节的前提下,数据集上的函数计算能力。这是安全计算的核心。

  • 证明正确性:证明计算工作实际上是使用规定的函数。在无信任的网络中,证明以正确的方式执行某个函数是非常重要的。

  • 安全性证明:证明计算实际上是在安全环境中进行的。


那么从MPC与上述3中技术的对比中,我们可以得到如下结论:


640?wx_fmt=png

最后,MPC是一个庞大的密码学领域,在密码学和分布式系统中结合了许多概念和工具,它是一个不断发展的活跃研究领域。


希望这篇文章能带给你关于什么是MPC以及隐私计算为什么是可能的。在以后的文章中,我们将带来关于密码学更多更加深入的主题。小伙伴们敬请期待!



*关于作者:

章磊,ARPA联合创始人&首席科学家,美国乔治华盛顿大学金融工程硕士,拥有十年深度学习、AI算法和风险建模经验,并对密码学有深度钻研。曾于硅谷最大的股权众筹公司CircleUp担任资深数据科学家;此前其就职于世界银行、AIG、PineBridge等大型金融机构,精通人工智能和量化策略。同时章磊于2017年创立星尘数据,为AI行业提供数据赋能。


ARPA是一家专注于安全加密计算和区块链底层技术的研发的公司,其核心产品为基于安全多方计算的隐私计算平台,并提供全套区块链+安全计算解决方案。同时ARPA作为行业成员,参与起草了工信部中国信息通信研究院即将出台的安全多方计算标准。



 640?wx_fmt=gif

开课啦!以太坊Dapp高薪实战

10周核心学习 | 挑战高薪区块链工程师

640?wx_fmt=jpeg

推荐阅读:

  • 10分钟狂赚800枚比特币? 这个边玩游戏边赚钱的涂鸦少年做到了!

  • “V神给了我1000个ETH, 我用来招了两个程序员” 独立开发者做到极限就是Paul Hauner | 人物志

  • 中本聪的足球队,香吗?

  • 中国区块链职业发展现状: 30岁前不做开发; 平均薪资仅38.4万; 跳槽薪资涨三成

  • JavaScript 力压 Java 成最受欢迎编程语言,TypeScript 大涨!

  • NLP泰斗董振东老师与他的知网 | 纪念

  • 细说说傲腾与哈娜的那些事儿

  • 曝光!月薪 5 万的程序员面试题:73% 人都做错,你敢试吗?


猛戳"阅读原文"有惊喜哟!


老铁在看了吗??

重要提示: "不好"的定义有时是相对的,取决于上下文、团队规范和性能要求。这里的示例主要针对那些普遍认为会增加 Bug 风险、降低可读性、影响性能或难以维护的做法。


JavaScript 不良代码实践及优化建议

一、 变量与作用域

  1. 滥用 var 声明变量

    • 不良:

      javascript
      代码解读
      复制代码
      function exampleVar() { if (true) { var count = 10; // var 没有块级作用域 } console.log(count); // 输出 10,变量泄漏到函数作用域 for (var i = 0; i < 3; i++) { setTimeout(function timer() { // console.log(i); // 输出 3, 3, 3 (闭包问题) }, i * 100); } } exampleVar();
    • 优化: 使用 let 和 const

      javascript
      代码解读
      复制代码
      function exampleLetConst() { if (true) { const count = 10; // const/let 具有块级作用域 // console.log(count); // 在块内可见 } // console.log(count); // ReferenceError: count is not defined for (let i = 0; i < 3; i++) { // let 在每次循环创建新的绑定 setTimeout(function timer() { console.log(i); // 输出 0, 1, 2 }, i * 100); } } exampleLetConst();
    • 解释: var 存在变量提升和函数作用域,容易导致变量覆盖、循环闭包陷阱和意外的全局变量。let 和 const 具有块级作用域,更符合预期,const 还强制变量不可重新赋值(引用类型内容可变),有助于提高代码稳定性。

  2. 隐式创建全局变量

    • 不良:

      javascript
      代码解读
      复制代码
      function setGlobal() { userId = 123; // 没有使用 var/let/const,在非严格模式下会创建全局变量 // 'use strict'; // 在严格模式下会直接报错 ReferenceError } setGlobal(); // console.log(window.userId); // 浏览器环境下输出 123
    • 优化: 始终使用 let 或 const 声明变量。

      javascript
      代码解读
      复制代码
      'use strict'; // 推荐开启严格模式 function setLocal() { const userId = 123; // 显式声明为局部变量 console.log(userId); } setLocal(); // console.log(window.userId); // undefined (或 ReferenceError)
    • 解释: 隐式全局变量污染全局命名空间,可能导致难以追踪的冲突和 Bug。严格模式 ('use strict') 可以帮助捕获这类错误。

  3. 变量命名不清晰或过于简短

    • 不良:

      javascript
      代码解读
      复制代码
      let x = 10; // x 代表什么? let arr = [1, 2, 3]; // arr 是什么内容的数组? function proc(d) { // proc 做什么? d 是什么? // ... }
    • 优化: 使用有意义的、描述性的名称。

      javascript
      代码解读
      复制代码
      let maxRetryCount = 10; let userIds = [1, 2, 3]; function processUserData(userData) { // ... }
    • 解释: 清晰的命名是代码可读性的关键。好的命名能自解释代码的意图。

  4. 在循环外声明循环变量 (如果只在循环内使用)

    • 不良:

      javascript
      代码解读
      复制代码
      let i; // 在外部声明 const data = [1, 2, 3]; for (i = 0; i < data.length; i++) { console.log(data[i]); } // 循环结束后 i 仍然存在于外部作用域 console.log('Loop finished, i =', i); // i = 3
    • 优化: 使用 let 在 for 循环初始化语句中声明。

      javascript
      代码解读
      复制代码
      const data = [1, 2, 3]; for (let i = 0; i < data.length; i++) { // i 的作用域限制在循环内 console.log(data[i]); } // console.log('Loop finished, i =', i); // ReferenceError: i is not defined
    • 解释: 将变量的作用域限制在最小需要的范围内,减少变量泄漏和潜在冲突。

二、 数据类型与比较

  1. 使用 == 进行比较 (非严格等于)

    • 不良:

      javascript
      代码解读
      复制代码
      console.log(0 == false); // true (发生类型转换) console.log('' == false); // true (发生类型转换) console.log(null == undefined); // true (规范特例) console.log(1 == '1'); // true (发生类型转换) console.log([] == false); // true (复杂转换) console.log([] == ![]); // true (更复杂转换)
    • 优化: 使用 === (严格等于) 和 !== (严格不等于)。

      javascript
      代码解读
      复制代码
      console.log(0 === false); // false console.log('' === false); // false console.log(null === undefined); // false console.log(1 === '1'); // false console.log([] === false); // false // 仅在明确需要利用类型转换时才使用 ==,并加注释说明 // 例如,检查 null 或 undefined: let value = null; if (value == null) { // 等价于 value === null || value === undefined console.log('Value is null or undefined'); }
    • 解释: == 会进行隐式类型转换,其规则复杂且容易出错。=== 不进行类型转换,只有在类型和值都相同时才返回 true,更可预测、更安全。

  2. 不正确地检查 NaN

    • 不良:

      javascript
      代码解读
      复制代码
      let result = parseInt('abc'); // NaN if (result == NaN) { // 永远是 false,因为 NaN != NaN console.log('Result is NaN'); } if (result === NaN) { // 永远是 false console.log('Result is NaN'); }
    • 优化: 使用 isNaN() 或 Number.isNaN()。

      javascript
      代码解读
      复制代码
      let result = parseInt('abc'); // NaN if (isNaN(result)) { // true - isNaN 会尝试转换参数为数字 console.log('Result is NaN (using isNaN)'); } if (Number.isNaN(result)) { // true - Number.isNaN 更严格,不进行类型转换 console.log('Result is NaN (using Number.isNaN)'); } // isNaN 的陷阱 console.log(isNaN('hello')); // true, 因为 'hello' 无法转为数字,被认为是 NaN console.log(Number.isNaN('hello')); // false, 因为 'hello' 本身不是 NaN 类型 // 推荐使用 Number.isNaN() 来精确判断一个值是否真的是 NaN 类型
    • 解释: NaN 是唯一一个不等于自身的值。必须使用 isNaN() 或 Number.isNaN() 来检查。Number.isNaN() 通常更推荐,因为它不会对参数进行强制类型转换。

  3. 依赖隐式类型转换的运算

    • 不良:

      javascript
      代码解读
      复制代码
      let countStr = "5"; let total = countStr + 10; // "510" (字符串拼接) console.log(total); let isActive = "true"; // 字符串 if (isActive) { // 字符串 "true" 被转换为布尔值 true console.log("Active"); }
    • 优化: 显式进行类型转换。

      javascript
      代码解读
      复制代码
      let countStr = "5"; let total = parseInt(countStr, 10) + 10; // 15 (显式转为数字) // 或使用 Number() 或 + // let total = Number(countStr) + 10; // let total = +countStr + 10; console.log(total); let isActiveStr = "true"; let isActiveBool = isActiveStr === "true"; // 显式转为布尔值 if (isActiveBool) { console.log("Active"); }
    • 解释: 隐式类型转换可能导致意想不到的结果。显式转换使代码意图更清晰,减少错误。

  4. 使用 new Boolean(), new String(), new Number()

    • 不良:

      javascript
      代码解读
      复制代码
      let boolObject = new Boolean(false); if (boolObject) { // 对象总是被认为是 true! 即使它包装的是 false console.log("Boolean object is truthy!"); } console.log(typeof boolObject); // "object"
    • 优化: 直接使用字面量或相应的转换函数。

      javascript
      代码解读
      复制代码
      let boolLiteral = false; if (boolLiteral) { // 不会执行 } else { console.log("Boolean literal false is falsy"); } console.log(typeof boolLiteral); // "boolean" let numLiteral = 10; let strLiteral = "hello"; let numFromStr = Number("123"); // 使用转换函数
    • 解释: 使用构造函数创建的是包装对象,它们的类型是 object,在布尔上下文中总是 true,容易引起混淆。直接使用字面量或 Boolean(), Number(), String() (不带 new) 进行类型转换。

三、 控制流

  1. 深度嵌套的 if-else 或循环

    • 不良:

      javascript
      代码解读
      复制代码
      function processData(data, user, settings) { if (data) { if (user.isLoggedIn) { if (settings.isActive) { for (let item of data) { if (item.value > 10) { // ... 很多层嵌套 ... console.log('Processing item deep inside:', item); } } } else { console.log('Settings not active'); } } else { console.log('User not logged in'); } } else { console.log('No data'); } }
    • 优化: 使用卫语句 (Guard Clauses)、提前返回、函数抽取。

      javascript
      代码解读
      复制代码
      function processItem(item) { // 抽取处理单个 item 的逻辑 console.log('Processing item:', item); } function processDataOptimized(data, user, settings) { // 卫语句提前处理无效情况 if (!data) { console.log('No data'); return; } if (!user.isLoggedIn) { console.log('User not logged in'); return; } if (!settings.isActive) { console.log('Settings not active'); return; } // 核心逻辑,嵌套减少 for (let item of data) { if (item.value > 10) { processItem(item); // 调用抽取出的函数 } } } // 模拟数据调用 processDataOptimized([{value: 5}, {value: 15}], {isLoggedIn: true}, {isActive: true}); processDataOptimized(null, {isLoggedIn: true}, {isActive: true});
    • 解释: 过度嵌套的代码难以阅读和维护(形成“箭头型代码”)。卫语句可以快速处理掉异常或前置条件,使主逻辑更清晰。抽取函数可以降低单个函数的复杂度。

  2. 在 if 条件中使用赋值语句 (易错)

    • 不良:

      javascript
      代码解读
      复制代码
      let x = 0; if (x = 5) { // 实际上是赋值,且赋值表达式结果是 5 (truthy) console.log("x is 5? No, x was assigned 5."); // 这会执行 } console.log(x); // x 现在是 5
    • 优化: 明确使用比较运算符。

      javascript
      代码解读
      复制代码
      let y = 0; if (y === 5) { // 使用严格比较 console.log("y is 5."); } else { console.log("y is not 5."); } y = 5; // 如果需要赋值,单独进行 if (y === 5) { console.log("Now y is 5."); }
    • 解释: 在 if 中使用 = 极易与 == 或 === 混淆,导致逻辑错误。虽然在某些受控情况下(如循环中 while (node = node.next)) 会有意使用,但通常应避免。

  3. 使用魔术数字或魔术字符串

    • 不良:

      javascript
      代码解读
      复制代码
      function calculatePrice(quantity, type) { let price = 0; if (type === 1) { // 1 代表什么? price = quantity * 10.5; // 10.5 是什么单价? } else if (type === 2) { price = quantity * 8.0; } if (price > 1000) { // 1000 是什么阈值? price *= 0.9; // 0.9 是什么折扣? } return price; } console.log(calculatePrice(50, 1));
    • 优化: 使用具名常量或枚举。

      javascript
      代码解读
      复制代码
      const ProductType = { STANDARD: 1, PREMIUM: 2, }; const Price = { STANDARD: 10.5, PREMIUM: 8.0, }; const DISCOUNT_THRESHOLD = 1000; const DISCOUNT_RATE = 0.9; // 90% function calculatePriceOptimized(quantity, type) { let price = 0; if (type === ProductType.STANDARD) { price = quantity * Price.STANDARD; } else if (type === ProductType.PREMIUM) { price = quantity * Price.PREMIUM; } else { console.warn("Unknown product type:", type); return 0; // 或者抛出错误 } if (price > DISCOUNT_THRESHOLD) { price *= DISCOUNT_RATE; } return price; } console.log(calculatePriceOptimized(50, ProductType.STANDARD));
    • 解释: "魔术"值(直接写在代码里的、没有解释的字面量)使代码难以理解和维护。当这些值需要改变时,需要在多处修改,容易出错。使用常量提高了可读性,易于修改。

  4. 不必要的 else (在 if 中有 return 或 throw 时)

    • 不良:

      javascript
      代码解读
      复制代码
      function checkValue(value) { if (value < 0) { return "Negative"; } else { // 这个 else 是多余的 console.log("Processing non-negative value..."); return "Non-negative"; } }
    • 优化: 移除不必要的 else。

      javascript
      代码解读
      复制代码
      function checkValueOptimized(value) { if (value < 0) { return "Negative"; } // 如果执行到这里,说明 value >= 0 console.log("Processing non-negative value..."); return "Non-negative"; }
    • 解释: 当 if 分支包含 return, throw, continue, break 等中断后续执行的语句时,else 块是不必要的,移除它可以减少一层嵌套,使代码更简洁。

  5. 使用 for...in 遍历数组 (可能遍历原型链属性)

    • 不良:

      javascript
      代码解读
      复制代码
      Array.prototype.customMethod = function() {}; // 在原型上添加方法 const myArray = [1, 2, 3]; let sum = 0; for (const key in myArray) { console.log(`Key: ${key}, Type: ${typeof key}`); // key 是字符串 "0", "1", "2", "customMethod" // if (myArray.hasOwnProperty(key)) { // 需要手动检查 // sum += myArray[key]; // 如果不检查,可能会尝试加函数,导致 NaN 或错误 // } } // console.log(sum); // 结果可能不符合预期 delete Array.prototype.customMethod; // 清理
    • 优化: 使用 for...of (推荐), forEach, 或标准 for 循环。

      javascript
      代码解读
      复制代码
      const myArrayOptimized = [1, 2, 3]; let sumOptimized = 0; // 推荐: for...of (获取值) for (const value of myArrayOptimized) { console.log(`Value: ${value}, Type: ${typeof value}`); // value 是数字 1, 2, 3 sumOptimized += value; } console.log("Sum (for...of):", sumOptimized); // 6 // 或者: forEach (带索引) sumOptimized = 0; myArrayOptimized.forEach((value, index) => { console.log(`Index: ${index}, Value: ${value}`); sumOptimized += value; }); console.log("Sum (forEach):", sumOptimized); // 6 // 或者: 标准 for 循环 (需要索引时) sumOptimized = 0; for (let i = 0; i < myArrayOptimized.length; i++) { sumOptimized += myArrayOptimized[i]; } console.log("Sum (standard for):", sumOptimized); // 6
    • 解释: for...in 主要设计用于遍历对象的可枚举属性键 (key) ,它会遍历原型链上的属性,并且遍历顺序不确定。对于数组,应使用 for...of(获取值)、forEach(值和索引)或标准 for 循环(需要控制索引或中断)。

四、 函数

  1. 函数过长,职责过多 (违反单一职责原则)

    • 不良:

      javascript
      代码解读
      复制代码
      function handleUserData(userId) { // 1. 获取用户数据 console.log(`Fetching data for user ${userId}...`); const userData = { id: userId, name: 'Temp', email: 'temp@example.com' }; // 模拟 API 调用 // 2. 验证数据 console.log('Validating user data...'); if (!userData.email || !userData.email.includes('@')) { console.error('Invalid email'); // ...错误处理 return; } // 3. 格式化数据 console.log('Formatting user data...'); const formattedName = userData.name.toUpperCase(); // 4. 更新 UI console.log('Updating UI...'); // const userNameElement = document.getElementById('user-name'); // if (userNameElement) userNameElement.textContent = formattedName; // 5. 发送分析事件 console.log('Sending analytics event...'); // analytics.track('UserDataHandled', { userId }); console.log('User data handling complete.'); } handleUserData(1);
    • 优化: 将不同职责拆分成更小的、独立的函数。

      javascript
      代码解读
      复制代码
      function fetchUserData(userId) { console.log(`Fetching data for user ${userId}...`); // 模拟 API 调用 return { id: userId, name: 'Temp', email: 'temp@example.com' }; } function validateUserData(userData) { console.log('Validating user data...'); if (!userData || !userData.email || !userData.email.includes('@')) { console.error('Invalid user data or email'); return false; } return true; } function formatUserName(userData) { console.log('Formatting user name...'); return userData.name.toUpperCase(); } function updateUI(userName) { console.log('Updating UI with name:', userName); // const userNameElement = document.getElementById('user-name'); // if (userNameElement) userNameElement.textContent = userName; } function sendAnalytics(eventName, data) { console.log(`Sending analytics event: ${eventName}`, data); // analytics.track(eventName, data); } function handleUserDataOptimized(userId) { const userData = fetchUserData(userId); if (!validateUserData(userData)) { return; // 验证失败,提前退出 } const formattedName = formatUserName(userData); updateUI(formattedName); sendAnalytics('UserDataHandled', { userId }); console.log('User data handling complete (optimized).'); } handleUserDataOptimized(2);
    • 解释: 过长的函数难以理解、测试和维护。遵循单一职责原则,将函数拆分成更小、功能单一的部分,可以提高代码的模块化、可重用性和可测试性。

  2. 函数参数过多

    • 不良:

      javascript
      代码解读
      复制代码
      function createUser(username, password, email, firstName, lastName, age, country, isAdmin) { console.log(`Creating user ${username} in ${country}...`); // ... lots of parameters } createUser('john_doe', 'pass123', 'john@example.com', 'John', 'Doe', 30, 'USA', false);
    • 优化: 使用对象作为参数(参数对象模式)。

      javascript
      代码解读
      复制代码
      function createUserOptimized(options) { // 使用解构赋值和默认值 const { username, password, email, firstName, lastName, age = 18, // 可以设置默认值 country = 'Unknown', isAdmin = false } = options; // 校验必需参数 if (!username || !password || !email) { console.error("Username, password, and email are required."); return; } console.log(`Creating user ${username} in ${country} (age: ${age}, admin: ${isAdmin})...`); // ... } createUserOptimized({ username: 'jane_doe', password: 'securePassword', email: 'jane@example.com', firstName: 'Jane', lastName: 'Doe', // age, country, isAdmin 可以省略,会使用默认值 }); createUserOptimized({ username: 'admin_user', password: 'adminPassword', email: 'admin@example.com', isAdmin: true, age: 40, country: 'Canada' });
    • 解释: 过多的参数使函数调用变得困难且容易出错(参数顺序、可选参数处理)。使用参数对象可以:

      • 使参数传递更清晰(键值对)。
      • 参数顺序不再重要。
      • 更容易添加或删除参数,向后兼容性更好。
      • 方便使用解构赋值和设置默认值。
  3. 函数有副作用 (Side Effects) 且命名不清晰

    • 不良:

      javascript
      代码解读
      复制代码
      let globalCounter = 0; function getAndIncrement() { // 函数名像只获取,实际修改了全局状态 globalCounter++; return globalCounter - 1; // 返回的是增加前的值? 还是增加后的? 易混淆 } console.log(getAndIncrement()); // 0 console.log(getAndIncrement()); // 1 console.log(globalCounter); // 2
    • 优化: 分离查询和修改,或明确函数名体现副作用。

      javascript
      代码解读
      复制代码
      let globalCounterOptimized = 0; // 查询函数 (无副作用) function getCounter() { return globalCounterOptimized; } // 修改函数 (有副作用,命名清晰) function incrementCounter() { globalCounterOptimized++; console.log("Counter incremented to:", globalCounterOptimized); } // 或者,如果需要原子操作,函数名明确体现 function incrementAndGetPreviousCounter() { const previousValue = globalCounterOptimized; globalCounterOptimized++; console.log("Counter incremented to:", globalCounterOptimized); return previousValue; } console.log("Initial counter:", getCounter()); // 0 incrementCounter(); // Counter incremented to: 1 console.log("Counter after increment:", getCounter()); // 1 const prevVal = incrementAndGetPreviousCounter(); // Counter incremented to: 2 console.log("Previous value was:", prevVal); // 1 console.log("Final counter:", getCounter()); // 2
    • 解释: 函数的副作用(修改函数外部的状态,如全局变量、DOM 等)会增加代码的复杂性和不可预测性。尽量编写纯函数(给定相同输入总是返回相同输出,且无副作用)。如果必须有副作用,函数命名应清晰地反映其行为。

  4. 滥用 arguments 对象

    • 不良:

      javascript
      代码解读
      复制代码
      function sumAll() { let sum = 0; // arguments 不是真正的数组,没有 forEach, map 等方法 for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } console.log(sumAll(1, 2, 3, 4)); // 10
    • 优化: 使用剩余参数 (...rest)。

      javascript
      代码解读
      复制代码
      function sumAllOptimized(...numbers) { // numbers 是一个真正的数组 console.log("Received numbers:", numbers); return numbers.reduce((sum, current) => sum + current, 0); } console.log(sumAllOptimized(1, 2, 3, 4, 5)); // 15
    • 解释: arguments 对象是类数组对象,使用不便且在某些 JavaScript 引擎中可能影响优化。剩余参数 (...rest) 提供了一个真正的数组,可以使用所有数组方法,代码更简洁、更现代。

  5. 不正确地处理函数中的 this 指向

    • 不良:

      javascript
      代码解读
      复制代码
      const myObject = { value: 10, getValue: function() { // 在 setTimeout 的回调中,this 通常指向 window (非严格模式) 或 undefined (严格模式) setTimeout(function() { // console.log(this.value); // TypeError or undefined // console.log('Inside setTimeout, this is:', this); }, 100); } }; // myObject.getValue(); function MyConstructor() { this.data = 'some data'; // document.addEventListener('click', function() { // console.log(this.data); // this 指向 document,而不是 MyConstructor 实例 // }); } // const instance = new MyConstructor();
    • 优化: 使用箭头函数、bind 或保存 this 引用。

      javascript
      代码解读
      复制代码
      'use strict'; // 推荐严格模式 const myObjectOptimized = { value: 10, getValueArrow: function() { // 箭头函数不绑定自己的 this,它会捕获其词法上下文的 this setTimeout(() => { console.log("Arrow function this.value:", this.value); // 10 (this 指向 myObjectOptimized) console.log('Inside arrow function, this is:', this); }, 100); }, getValueBind: function() { // 使用 bind 显式绑定 this const callback = function() { console.log("Bind this.value:", this.value); // 10 console.log('Inside bind callback, this is:', this); }.bind(this); // 绑定当前的 this (myObjectOptimized) setTimeout(callback, 200); }, getValueSelf: function() { // 使用变量保存 this 引用 (旧方法) const self = this; setTimeout(function() { console.log("Self this.value:", self.value); // 10 console.log('Inside self callback, this is:', this, 'but self is:', self); // this 可能是 window/undefined }, 300); } }; myObjectOptimized.getValueArrow(); myObjectOptimized.getValueBind(); myObjectOptimized.getValueSelf(); function MyConstructorOptimized() { this.data = 'some data optimized'; // 使用箭头函数保持 this 指向实例 // document.addEventListener('click', () => { // console.log('Event listener this.data:', this.data); // this 指向 MyConstructorOptimized 实例 // }); // 或者使用 bind // document.addEventListener('click', function() { // console.log('Event listener (bind) this.data:', this.data); // }.bind(this)); } // const instanceOptimized = new MyConstructorOptimized();
    • 解释: JavaScript 中的 this 指向在函数调用时确定,规则复杂(取决于调用方式:普通函数、对象方法、构造函数、call/apply/bind、箭头函数)。普通函数回调(如 setTimeout, 事件监听器)中的 this 通常不指向预期对象。箭头函数是解决此问题的现代且简洁的方法,因为它继承外层作用域的 this。bind 或保存 this 到变量(如 self, that)是传统方法。

五、 数组与对象

  1. 直接修改传入函数的数组或对象参数 (产生副作用)

    • 不良:

      javascript
      代码解读
      复制代码
      function sortArrayInPlace(arr) { // 直接修改了原始数组 arr.sort((a, b) => a - b); return arr; // 返回的是修改后的原始数组引用 } const originalArray = [3, 1, 4, 1, 5]; console.log("Original array before:", originalArray); const sortedArrayRef = sortArrayInPlace(originalArray); console.log("Original array after:", originalArray); // [1, 1, 3, 4, 5] (被修改了!) console.log("Returned array ref:", sortedArrayRef); // [1, 1, 3, 4, 5] console.log(originalArray === sortedArrayRef); // true
    • 优化: 创建副本进行修改,或明确函数会修改原对象/数组。

      javascript
      代码解读
      复制代码
      function sortArrayImmutable(arr) { // 1. 创建副本 (使用 slice() 或扩展运算符 ...) const newArray = [...arr]; // 2. 在副本上排序 newArray.sort((a, b) => a - b); // 3. 返回新数组 return newArray; } const originalArray2 = [3, 1, 4, 1, 5, 9]; console.log("Original array 2 before:", originalArray2); const sortedNewArray = sortArrayImmutable(originalArray2); console.log("Original array 2 after:", originalArray2); // [3, 1, 4, 1, 5, 9] (未被修改) console.log("Returned new array:", sortedNewArray); // [1, 1, 3, 4, 5, 9] console.log(originalArray2 === sortedNewArray); // false // 如果确实需要修改原数组,函数命名应体现 (如 sortArrayInPlace) // 并在文档/注释中说明其副作用
    • 解释: 直接修改传入的对象或数组参数是一种副作用,可能导致调用者代码中出现意外行为,因为原始数据被改变了。对于期望无副作用的函数,应在副本上操作并返回新结果(不可变性原则)。如果函数设计为就地修改,命名和文档应清晰说明。

  2. 使用 delete 从数组中删除元素 (留下空位)

    • 不良:

      javascript
      代码解读
      复制代码
      const numbers = [10, 20, 30, 40, 50]; delete numbers[2]; // 删除索引为 2 的元素 (30) console.log(numbers); // [ 10, 20, <1 empty item>, 40, 50 ] console.log(numbers.length); // 5 (长度不变!) console.log(numbers[2]); // undefined // 遍历时可能遇到问题 numbers.forEach(n => console.log(n)); // 10, 20, 40, 50 (跳过了空位) for(let i=0; ilength; i++){ console.log(`Index ${i}: ${numbers[i]}`); // Index 2: undefined }
    • 优化: 使用 splice() 或 filter()。

      javascript
      代码解读
      复制代码
      const numbersSplice = [10, 20, 30, 40, 50]; // 从索引 2 开始,删除 1 个元素 const removedElements = numbersSplice.splice(2, 1); console.log("Using splice:", numbersSplice); // [ 10, 20, 40, 50 ] console.log("Splice removed:", removedElements); // [ 30 ] console.log("Splice length:", numbersSplice.length); // 4 const numbersFilter = [10, 20, 30, 40, 50]; // 创建一个不包含 30 的新数组 const filteredNumbers = numbersFilter.filter(n => n !== 30); console.log("Using filter:", filteredNumbers); // [ 10, 20, 40, 50 ] console.log("Filter length:", filteredNumbers.length); // 4 console.log("Original after filter:", numbersFilter); // [ 10, 20, 30, 40, 50 ] (filter 不修改原数组)
    • 解释: delete 操作符仅将数组指定索引处的值设为 undefined,并留下一个“空洞”(empty slot),数组长度不变,这通常不是期望的行为。splice() 可以直接在原数组上删除(或添加/替换)元素,并正确更新长度。filter() 创建一个满足条件的新数组,是实现不可变删除的好方法。

  3. 低效的数组查找 (对未排序大数组使用 indexOf 或 find)

    • 不良:

      javascript
      代码解读
      复制代码
      // 假设 data 是一个包含 100 万个对象的大数组,且未排序 const largeData = Array.from({ length: 1000000 }, (_, i) => ({ id: i, value: Math.random() })); const targetId = 999999; console.time('indexOfLarge'); // indexOf 对对象数组无效,除非是同一个对象引用 // const index = largeData.indexOf({ id: targetId }); // -1 // find 需要遍历 const foundItem = largeData.find(item => item.id === targetId); console.timeEnd('indexOfLarge'); // 时间取决于 targetId 的位置,最坏情况 O(n) console.log("Found item:", foundItem ? foundItem.id : 'Not Found');
    • 优化: 使用 Map 或 Set 进行快速查找 (如果需要频繁查找)。

      javascript
      代码解读
      复制代码
      const largeDataOptimized = Array.from({ length: 1000000 }, (_, i) => ({ id: i, value: Math.random() })); const targetIdOptimized = 999999; // 预处理:创建一个 Map 用于快速查找,空间换时间 console.time('createMap'); const dataMap = new Map(largeDataOptimized.map(item => [item.id, item])); console.timeEnd('createMap'); console.time('findInMap'); const foundItemMap = dataMap.get(targetIdOptimized); // O(1) 平均时间复杂度 console.timeEnd('findInMap'); // 非常快 console.log("Found item in Map:", foundItemMap ? foundItemMap.id : 'Not Found'); // 如果只需要检查是否存在,Set 更合适 // const idSet = new Set(largeDataOptimized.map(item => item.id)); // console.time('findInSet'); // const exists = idSet.has(targetIdOptimized); // O(1) // console.timeEnd('findInSet');
    • 解释: 对于大型数组,线性查找(如 find, findIndex, indexOf)的时间复杂度是 O(n),性能较差。如果需要频繁根据某个键(如 id)进行查找,可以先将数组转换为 Map(键值对存储)或 Set(仅存储唯一值),它们的查找操作平均时间复杂度接近 O(1),性能显著提升。但这需要额外的内存和预处理时间。

  4. 不必要的数组或对象创建

    • 不良:

      javascript
      代码解读
      复制代码
      // 在循环中创建不必要的数组/对象 function processCoords(coords) { for (const coord of coords) { const tempPoint = [coord.x, coord.y]; // 每次循环都创建新数组 // 如果只是读取,不需要创建 console.log(`Processing point: (${tempPoint[0]}, ${tempPoint[1]})`); } } processCoords([{x:1, y:2}, {x:3, y:4}]); // 返回一个每次都一样的新数组/对象 function getDefaultOptions() { return { theme: 'light', timeout: 5000 }; // 每次调用都创建新对象 } let options1 = getDefaultOptions(); let options2 = getDefaultOptions(); console.log(options1 === options2); // false
    • 优化: 重用对象/数组,或使用常量。

      javascript
      代码解读
      复制代码
      function processCoordsOptimized(coords) { for (const coord of coords) { // 直接访问属性,避免创建临时数组 console.log(`Processing point: (${coord.x}, ${coord.y})`); } } processCoordsOptimized([{x:1, y:2}, {x:3, y:4}]); // 如果默认选项是固定的,定义为常量 const DEFAULT_OPTIONS = Object.freeze({ theme: 'light', timeout: 5000 }); // Object.freeze 防止修改 function getDefaultOptionsOptimized() { return DEFAULT_OPTIONS; // 返回同一个常量对象的引用 } let optionsOpt1 = getDefaultOptionsOptimized(); let optionsOpt2 = getDefaultOptionsOptimized(); console.log(optionsOpt1 === optionsOpt2); // true // optionsOpt1.theme = 'dark'; // 在严格模式下会报错 TypeError,非严格模式下静默失败
    • 解释: 在循环或频繁调用的函数中创建不必要的对象或数组会增加内存分配和垃圾回收的压力。如果数据是只读的或可以重用,尽量避免重复创建。使用常量存储固定的默认值。

六、 异步编程

  1. 回调地狱 (Callback Hell)

    • 不良:

      javascript
      代码解读
      复制代码
      function asyncOperation1(data, callback) { console.log('Step 1 with', data); setTimeout(() => callback(null, data + '-step1'), 100); } function asyncOperation2(data, callback) { console.log('Step 2 with', data); setTimeout(() => callback(null, data + '-step2'), 100); } function asyncOperation3(data, callback) { console.log('Step 3 with', data); setTimeout(() => callback(null, data + '-step3'), 100); } asyncOperation1('start', function(err1, data1) { if (err1) { console.error(err1); return; } asyncOperation2(data1, function(err2, data2) { if (err2) { console.error(err2); return; } asyncOperation3(data2, function(err3, data3) { if (err3) { console.error(err3); return; } console.log('Callback Hell Final Result:', data3); // start-step1-step2-step3 // 嵌套越来越深... }); }); });
    • 优化: 使用 Promises 或 async/await。

      javascript
      代码解读
      复制代码
      function asyncOperationPromise(data, stepName) { console.log(`Step ${stepName} with`, data); return new Promise((resolve, reject) => { setTimeout(() => { // 模拟可能发生的错误 if (Math.random() < 0.1) { reject(new Error(`Error in step ${stepName}`)); } else { resolve(data + `-step${stepName}`); } }, 100); }); } // 使用 Promise Chaining asyncOperationPromise('start', '1') .then(data1 => asyncOperationPromise(data1, '2')) .then(data2 => asyncOperationPromise(data2, '3')) .then(data3 => { console.log('Promise Chain Final Result:', data3); }) .catch(error => { console.error('Promise Chain Error:', error.message); }); // 使用 async/await (更推荐,更同步化) async function runAsyncOperations() { try { console.log("\n--- Running with async/await ---"); const data1 = await asyncOperationPromise('start-async', 'A'); const data2 = await asyncOperationPromise(data1, 'B'); const data3 = await asyncOperationPromise(data2, 'C'); console.log('Async/Await Final Result:', data3); } catch (error) { console.error('Async/Await Error:', error.message); } } // runAsyncOperations(); // 调用 async 函数
    • 解释: 回调地狱导致代码难以阅读、理解和维护,错误处理也变得复杂。Promises 通过 .then() 链式调用和 .catch() 统一错误处理,改善了结构。async/await 是基于 Promise 的语法糖,让异步代码看起来更像同步代码,可读性最高,是现代 JavaScript 中处理异步的首选方式。

  2. 未处理的 Promise Rejections

    • 不良:

      javascript
      代码解读
      复制代码
      function mightReject() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() < 0.5) { reject(new Error("Something went wrong!")); } else { resolve("Success!"); } }, 100); }); } mightReject(); // 调用了 Promise,但没有 .catch() 或在 async 函数中 try...catch // 如果 Promise reject,会在控制台看到 "Uncaught (in promise) Error..." // 在 Node.js 环境中可能导致进程退出
    • 优化: 始终为 Promise 添加 .catch() 或在 async 函数中使用 try...catch。

      javascript
      代码解读
      复制代码
      function mightRejectHandled() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() < 0.5) { reject(new Error("Something went wrong (handled)!")); } else { resolve("Success (handled)!"); } }, 100); }); } // 使用 .catch() mightRejectHandled() .then(result => console.log(".catch() scenario:", result)) .catch(error => console.error(".catch() scenario Error:", error.message)); // 使用 async/await async function handleRejectionAsync() { try { const result = await mightRejectHandled(); console.log("async/await scenario:", result); } catch (error) { console.error("async/await scenario Error:", error.message); } } // handleRejectionAsync();
    • 解释: 未处理的 Promise rejection 是潜在的运行时错误,可能导致应用程序行为异常或崩溃。务必确保每个可能 reject 的 Promise 都有相应的错误处理逻辑。

  3. 在 async 函数中忘记使用 await

    • 不良:

      javascript
      代码解读
      复制代码
      async function fetchData() { console.log("Fetching..."); // 忘记 await,fetchUser 返回的是 Promise,而不是用户数据 const userPromise = asyncOperationPromise('user-data', 'fetch'); // 后续代码可能期望 user 是实际数据,导致错误 // console.log(userPromise.name); // undefined or error, userPromise is a Promise console.log("Fetch call initiated, received:", userPromise); // 输出 Promise 对象 return userPromise; // 返回的是 Promise } // fetchData();
    • 优化: 确保在调用返回 Promise 的函数时使用 await (如果需要等待结果)。

      javascript
      代码解读
      复制代码
      async function fetchDataOptimized() { console.log("Fetching optimized..."); try { // 使用 await 等待 Promise resolve const user = await asyncOperationPromise('user-data-optimized', 'fetch'); console.log("Fetch complete, received user:", user); // 输出 'user-data-optimized-stepfetch' // 可以安全使用 user 数据 return user; } catch (error) { console.error("Fetch optimized error:", error.message); return null; // 或抛出错误 } } // fetchDataOptimized().then(data => console.log("Final fetched data:", data));
    • 解释: 在 async 函数内部,调用另一个返回 Promise 的异步函数时,如果需要等待其结果才能继续执行,必须使用 await。否则,你将得到一个 Promise 对象,而不是它 resolve 后的值。

  4. 不必要地将同步代码包装在 async 函数中

    • 不良:

      javascript
      代码解读
      复制代码
      // 这个函数本身没有任何异步操作 async function getSyncData(a, b) { const result = a + b; // 同步计算 console.log("Calculating sync data:", result); return result; // async 函数总是隐式返回 Promise } const dataPromise = getSyncData(5, 10); console.log("Returned value is Promise:", dataPromise instanceof Promise); // true dataPromise.then(data => console.log("Resolved sync data:", data)); // 15
    • 优化: 如果函数是纯同步的,就定义为普通函数。

      javascript
      代码解读
      复制代码
      function getSyncDataOptimized(a, b) { const result = a + b; console.log("Calculating sync data optimized:", result); return result; // 直接返回结果 } const syncData = getSyncDataOptimized(5, 10); console.log("Returned value is number:", typeof syncData); // number console.log("Direct sync data:", syncData); // 15
    • 解释: async 关键字会使函数总是返回一个 Promise。如果函数内部完全是同步操作,使用 async 会带来不必要的开销(创建 Promise 对象),并迫使调用者使用 .then() 或 await 来获取结果。

  5. 并行执行异步任务时使用串行 await (如果任务间无依赖)

    • 不良:

      javascript
      代码解读
      复制代码
      async function fetchMultipleSerial() { console.time("fetchMultipleSerial"); console.log("Fetching data 1..."); const data1 = await asyncOperationPromise('data1', 'fetch1'); // 等待 100ms console.log("Fetching data 2..."); const data2 = await asyncOperationPromise('data2', 'fetch2'); // 再等待 100ms console.log("Fetching data 3..."); const data3 = await asyncOperationPromise('data3', 'fetch3'); // 再等待 100ms console.timeEnd("fetchMultipleSerial"); // 总耗时约 300ms+ return [data1, data2, data3]; } // fetchMultipleSerial().then(results => console.log("Serial results:", results));
    • 优化: 使用 Promise.all() 或 Promise.allSettled() 并行执行。

      javascript
      代码解读
      复制代码
      async function fetchMultipleParallel() { console.time("fetchMultipleParallel"); console.log("Initiating parallel fetches..."); // 同时启动所有异步操作 const promise1 = asyncOperationPromise('data1-p', 'fetchP1'); const promise2 = asyncOperationPromise('data2-p', 'fetchP2'); const promise3 = asyncOperationPromise('data3-p', 'fetchP3'); try { // 等待所有 Promise 完成 // Promise.all: 如果有任何一个 reject,整个 Promise.all 会立即 reject const results = await Promise.all([promise1, promise2, promise3]); console.timeEnd("fetchMultipleParallel"); // 总耗时约 100ms+ (取决于最慢的那个) console.log("Parallel results (all):", results); return results; // 或者使用 Promise.allSettled: 等待所有 Promise 完成(无论成功或失败) // const settledResults = await Promise.allSettled([promise1, promise2, promise3]); // console.timeEnd("fetchMultipleParallel"); // console.log("Parallel results (allSettled):", settledResults); // // settledResults 是 [{status: 'fulfilled', value: ...}, {status: 'rejected', reason: ...}] 形式的数组 // return settledResults; } catch (error) { // 只有在使用 Promise.all 时才需要 catch 这里,allSettled 不会 reject console.error("Parallel fetch error (Promise.all):", error.message); console.timeEnd("fetchMultipleParallel"); return []; } } // fetchMultipleParallel();
    • 解释: 如果多个异步任务之间没有依赖关系,可以并行执行以节省时间。使用 await 串行等待每个任务完成会不必要地增加总耗时。Promise.all() 接收一个 Promise 数组,并发启动它们,并在所有 Promise 都 fulfilled 时返回结果数组(或在任何一个 rejected 时立即 reject)。Promise.allSettled() 也是并发启动,但它总是等待所有 Promise 结束(无论成功或失败),并返回每个 Promise 的状态和结果/原因。

七、 DOM 操作 (前端相关)

  1. 频繁直接操作 DOM 导致多次重排/重绘

    • 不良:

      javascript
      代码解读
      复制代码
      function updateListBad(items) { const listElement = document.getElementById('my-list'); if (!listElement) return; listElement.innerHTML = ''; // 清空可能触发一次重排/重绘 items.forEach(item => { const li = document.createElement('li'); li.textContent = item.name; li.style.color = item.color; // 每次添加/修改样式都可能触发重排/重绘 li.style.fontWeight = 'bold'; listElement.appendChild(li); // 每次添加都可能触发重排/重绘 }); } // 假设页面有
        // updateListBad([{name: 'A', color: 'red'}, {name: 'B', color: 'blue'}]);
      • 优化: 使用文档片段 (DocumentFragment) 或批量更新样式。

        javascript
        代码解读
        复制代码
        function updateListOptimized(items) { const listElement = document.getElementById('my-list-optimized'); if (!listElement) return; // 使用 DocumentFragment 批量操作 const fragment = document.createDocumentFragment(); items.forEach(item => { const li = document.createElement('li'); li.textContent = item.name; // 批量设置样式 (例如通过 class,或者一次性设置 style.cssText) // li.style.color = item.color; // li.style.fontWeight = 'bold'; li.className = `list-item color-${item.color}`; // 通过 CSS 类控制样式更好 fragment.appendChild(li); // 添加到 fragment,不触发重排 }); // 一次性将 fragment 添加到 DOM listElement.innerHTML = ''; // 清空 listElement.appendChild(fragment); // 只触发一次重排/重绘 } // 假设页面有
          和对应的 CSS
          // updateListOptimized([{name: 'Opt A', color: 'red'}, {name: 'Opt B', color: 'blue'}]);
        • 解释: 浏览器对 DOM 的更改(特别是影响布局的更改,如添加/删除元素、改变尺寸/位置、修改 display 等)会触发重排 (reflow/layout),然后是重绘 (repaint)。重排是非常昂贵的操作。频繁触发会导致性能下降。使用 DocumentFragment 作为一个临时的、轻量级的 DOM 容器,可以在其上进行多次 DOM 操作,最后一次性将其内容插入到实际 DOM 中,从而大大减少重排次数。同样,应尽量合并样式更改(如通过添加/删除 CSS 类,或一次性设置 element.style.cssText)。

      • 滥用 innerHTML 插入不可信内容 (XSS 风险)

        • 不良:

          javascript
          代码解读
          复制代码
          function displayComment(userInput) { const commentDiv = document.getElementById('comment-display'); if (!commentDiv) return; // 如果 userInput 包含 ,这会被执行! commentDiv.innerHTML = userInput; } // 假设页面有
          // const maliciousInput = "Looks good! "; // displayComment(maliciousInput);
        • 优化: 使用 textContent 或 createElement + appendChild。

          javascript
          代码解读
          复制代码
          function displayCommentSafe(userInput) { const commentDiv = document.getElementById('comment-display-safe'); if (!commentDiv) return; // 使用 textContent,浏览器不会解析 HTML 标签 commentDiv.textContent = userInput; // 或者,如果需要创建结构,使用 DOM API // const p = document.createElement('p'); // p.textContent = userInput; // 仍然使用 textContent 设置内容 // commentDiv.innerHTML = ''; // 清空 // commentDiv.appendChild(p); } // 假设页面有
          const maliciousInputSafe = "Looks good! "; // displayCommentSafe(maliciousInputSafe); // 页面会显示字符串 ""
        • 解释: innerHTML 会将指定的字符串解析为 HTML 并插入到 DOM 中。如果字符串来源于用户输入或其他不可信来源,恶意脚本可能会被注入并执行,导致跨站脚本攻击 (XSS)。应优先使用 textContent,它会将内容作为纯文本插入。如果必须基于用户输入创建 HTML 结构,需要进行严格的清理和转义,或者使用安全的模板引擎/框架。

      • 未移除不再需要的事件监听器 (内存泄漏)

        • 不良:

          javascript
          代码解读
          复制代码
          function setupTemporaryListener() { const button = document.getElementById('temp-button'); if (!button) return; const handleClick = () => { console.log('Temporary button clicked!'); // ... 做一些事情 ... // 忘记移除监听器 }; button.addEventListener('click', handleClick); // 假设这个按钮或其父元素稍后会被从 DOM 中移除 // 但 handleClick 函数(及其闭包)仍然被按钮引用, // 按钮又被浏览器的事件监听器机制引用,导致无法被垃圾回收。 } // setupTemporaryListener(); // 假设稍后执行: document.getElementById('temp-button')?.remove();
        • 优化: 在元素销毁或不再需要监听时,使用 removeEventListener 移除监听器。

          javascript
          代码解读
          复制代码
          function setupTemporaryListenerOptimized() { const button = document.getElementById('temp-button-optimized'); if (!button) return; // 必须保存对同一个函数实例的引用才能移除 const handleClick = () => { console.log('Temporary button clicked (optimized)!'); // ... 做一些事情 ... // 在这里或在其他清理逻辑中移除监听器 cleanupListener(); }; const cleanupListener = () => { console.log("Removing temporary listener..."); button.removeEventListener('click', handleClick); // 如果按钮本身也要移除,可以在移除前调用 cleanup }; button.addEventListener('click', handleClick); // 模拟稍后的清理操作 (例如组件卸载时) setTimeout(() => { // cleanupListener(); // 可以在这里移除 // document.getElementById('temp-button-optimized')?.remove(); // 移除元素前最好先移除监听器 }, 5000); // 返回一个清理函数,让调用者负责清理 (常见于 React/Vue 等框架) return cleanupListener; } // const cleanup = setupTemporaryListenerOptimized(); // // 在适当的时候调用 cleanup() // // window.addEventListener('unload', cleanup); // 例如页面卸载时
        • 解释: 当 DOM 元素被移除时,如果它上面还绑定着事件监听器,而监听器函数又引用了其他对象(形成了闭包),这些对象可能无法被垃圾回收,导致内存泄漏。特别是在单页应用 (SPA) 中,组件频繁创建和销毁,这个问题尤为突出。务必在元素销毁前或不再需要监听时,使用 removeEventListener 移除对应的监听器。注意 addEventListener 和 removeEventListener 的第二个参数必须是同一个函数引用。

      八、 其他

      1. 使用 eval() 或 new Function() 执行动态代码 (安全风险和性能问题)

        • 不良:

          javascript
          代码解读
          复制代码
          const codeString = "console.log('Executed via eval:', 2 + 2)"; // eval(codeString); // 执行任意字符串代码,非常危险! const funcString = "a, b", funcBody = "return a * b;"; // const multiply = new Function(funcString, funcBody); // console.log("Executed via new Function:", multiply(5, 6)); // 30
        • 优化: 避免使用。寻找替代方案(如数据驱动、配置、安全的模板引擎)。

          javascript
          代码解读
          复制代码
          // 如果需要根据配置执行不同逻辑 const operations = { add: (a, b) => a + b, multiply: (a, b) => a * b, }; const opName = 'multiply'; const arg1 = 5, arg2 = 6; if (operations[opName]) { console.log("Executing via lookup:", operations[opName](arg1, arg2)); // 30 } else { console.error("Unknown operation:", opName); }
        • 解释: eval() 和 new Function() 可以执行包含在字符串中的任意 JavaScript 代码。这带来了巨大的安全风险,因为恶意代码可能被执行(特别是当字符串来自外部输入时)。此外,它们通常会阻止 JavaScript 引擎的优化,导致性能下降。绝大多数情况下都应该避免使用它们。

      2. 忽略错误处理 (try...catch 中捕获但不处理)

        • 不良:

          javascript
          代码解读
          复制代码
          try { const data = JSON.parse("{ invalid json "); console.log("Parsed data:", data); } catch (error) { // 捕获了错误,但什么也没做,错误被“吞掉”了 // console.log("An error occurred, but we ignored it."); } console.log("Program continues silently...");
        • 优化: 记录错误、向用户显示消息或执行适当的回退/恢复逻辑。

          javascript
          代码解读
          复制代码
          try { const data = JSON.parse("{ invalid json "); console.log("Parsed data (optimized):", data); } catch (error) { // 至少记录错误 console.error("Failed to parse JSON:", error.message); // 可以向用户显示友好的错误提示 // showErrorMessage("Sorry, there was an issue processing data."); // 或者执行备用逻辑 // useDefaultData(); } console.log("Program continues after handling error...");
        • 解释: 捕获错误但完全不处理(空的 catch 块)会导致问题被隐藏,使调试变得极其困难。至少应该记录错误信息,以便追踪问题。根据情况,可能还需要通知用户或尝试从错误中恢复。

      3. 在代码中留下 console.log 或 debugger 语句 (生产环境)

        • 不良:

          javascript
          代码解读
          复制代码
          function calculateComplexValue(input) { console.log("Debugging input:", input); // 用于调试,忘记删除 let result = input * 2; // debugger; // 用于断点调试,忘记删除 result += 5; console.log("Final result:", result); // 可能也是调试信息 return result; } calculateComplexValue(10);
        • 优化: 使用构建工具 (如 Terser, Babel 插件) 移除,或使用条件日志。

          javascript
          代码解读
          复制代码
          function calculateComplexValueOptimized(input) { // 使用条件日志,只在开发环境输出 if (process.env.NODE_ENV === 'development') { console.log("Dev Debug: Input is", input); } let result = input * 2; result += 5; // 生产构建时,构建工具会自动移除 console.log 和 debugger // 或者使用专门的日志库 (如 Winston, Pino, debug) return result; } // 需要配置 process.env.NODE_ENV (通常由 Webpack/Vite 等设置) // process.env.NODE_ENV = 'production'; // 模拟生产环境 calculateComplexValueOptimized(10);
        • 解释: console.log 输出可能暴露敏感信息或干扰控制台。debugger 语句会导致代码在浏览器开发者工具打开时暂停执行。这些调试语句应在提交到版本控制或部署到生产环境之前移除。使用构建工具自动化移除是最佳实践。

      4. 不写注释或写无用的注释

        • 不良:

          javascript
          代码解读
          复制代码
          // i加1 (无用的注释,代码本身很明显) i++; // 复杂且没有注释的逻辑 const magicValue = (x << 3) ^ (y >> 1) | z;
        • 优化: 为复杂的逻辑、重要的决策或“为什么”这样写添加注释。

          javascript
          代码解读
          复制代码
          // 计数器加 1 (如果变量名不清晰,注释可能有帮助,但最好是改名) retryCount++; // 使用位运算优化性能,计算哈希值 (解释了“为什么”和“做什么”) // x << 3: 快速乘以 8 // y >> 1: 快速除以 2 (取整) // ^: 异或操作 // |: 或操作 const hashCode = (x << 3) ^ (y >> 1) | z;
        • 解释: 代码应该尽可能自解释(通过好的命名和结构)。注释不应解释“代码做了什么”(除非代码非常晦涩),而应解释“为什么这样做”、业务逻辑背景、重要的假设或需要注意的陷阱。

      5. 代码格式不一致

        • 不良: (混合使用缩进、空格、括号风格等)

          javascript
          代码解读
          复制代码
          function badFormat (a,b){ if(a>b){ console.log( a); } else { console.log(b) ;} var c=a+b; return c ; }
        • 优化: 使用 Prettier、ESLint 等工具强制执行统一的代码风格。

          javascript
          代码解读
          复制代码
          // 使用 Prettier 格式化后 (示例) function goodFormat(a, b) { if (a > b) { console.log(a); } else { console.log(b); } const c = a + b; return c; }
        • 解释: 不一致的代码格式降低了可读性,增加了团队协作的难度。使用自动化格式化工具 (Prettier) 和代码检查工具 (ESLint) 可以轻松保持代码风格统一。

      6. 对 null 或 undefined 的属性进行深层访问 (导致 TypeError)

        • 不良: const cityName = user.address.city; (如果 user 或 user.address 是 null 或 undefined 则报错)
        • 优化: 使用可选链 (?.) 和/或空值合并 (??)。 const cityName = user?.address?.city ?? 'Unknown';
      7. 布尔值判断过于冗余

        • 不良: if (isValid === true) 或 if (isValid === false)
        • 优化: 直接使用布尔值 if (isValid) 或 if (!isValid)
      8. 使用 Array 构造函数创建数组 (除非指定长度)

        • 不良: const arr = new Array(1, 2, 3); (行为同 [1, 2, 3]) 或 const single = new Array(5); (创建长度为 5 的稀疏数组)
        • 优化: 使用数组字面量 const arr = [1, 2, 3];。如果需要预设长度的空数组,Array(5) 可以接受,但注意它是稀疏的。Array.from({ length: 5 }) 创建非稀疏数组。
      9. 使用 Object 构造函数创建空对象

        • 不良: const obj = new Object();
        • 优化: 使用对象字面量 const obj = {};
      10. 不必要的字符串包装对象

        • 不良: const strObj = new String("hello");
        • 优化: 使用字符串字面量 const str = "hello";
      11. 在 switch 语句中忘记 break (导致 case 穿透)

        • 不良:

          javascript
          代码解读
          复制代码
          let type = 1; switch (type) { case 1: console.log("Type 1"); // 输出 // 忘记 break case 2: console.log("Type 2"); // 也会输出! break; default: console.log("Default"); }
        • 优化: 确保每个 case 结束时有 break (除非有意利用穿透,并加注释)。

          javascript
          代码解读
          复制代码
          let typeOpt = 1; switch (typeOpt) { case 1: console.log("Type 1 Opt"); break; // 添加 break case 2: console.log("Type 2 Opt"); break; default: console.log("Default Opt"); }
      12. 使用 with 语句 (已被废弃,有性能和作用域问题)

        • 不良: with (myObject) { prop1 = 1; prop2 = 2; }
        • 优化: 明确访问对象属性 myObject.prop1 = 1; myObject.prop2 = 2; 或使用解构赋值(如果适用)。
      13. 修改内置对象的原型 (如 Array.prototype, Object.prototype)

        • 不良: Array.prototype.last = function() { return this[this.length - 1]; }; (可能与未来 JS 标准或第三方库冲突)
        • 优化: 创建独立的工具函数 function getLastElement(arr) { return arr[arr.length - 1]; } 或使用子类化。
      14. 在 try...catch...finally 中,finally 里的 return 会覆盖 try 或 catch 中的 return

        • 不良:

          javascript
          代码解读
          复制代码
          function testFinallyReturn() { try { console.log("Try block"); return "from try"; // 这个返回值会被 finally 覆盖 } catch (e) { console.log("Catch block"); return "from catch"; } finally { console.log("Finally block"); return "from finally"; // 最终返回这个值 } } console.log(testFinallyReturn()); // 输出 "from finally"
        • 优化: 不要在 finally 中使用 return (除非是刻意为之的特殊逻辑)。finally 主要用于清理资源。

      15. 创建不必要的闭包 (尤其在循环中)

        • 不良:

          javascript
          代码解读
          复制代码
          function createHandlersBad() { const elements = document.querySelectorAll('.my-elements'); for (let i = 0; i < elements.length; i++) { const element = elements[i]; const data = element.dataset.value; // 假设有 data-value // 每次循环都创建一个新的 handleClick 函数闭包,即使处理逻辑相同 element.addEventListener('click', function handleClick() { console.log(`Clicked element ${i} with data: ${data}`); }); } }
        • 优化: 将事件处理函数移到循环外,使用事件委托或通过 event.target 获取数据。

          javascript
          代码解读
          复制代码
          function handleClickOptimized(event) { // 使用事件委托时,event.target 是实际点击的元素 // const targetElement = event.target.closest('.my-element'); // 找到目标元素 // if (!targetElement) return; // const data = targetElement.dataset.value; // const index = Array.from(targetElement.parentNode.children).indexOf(targetElement); // 获取索引较麻烦 // 如果不用事件委托,只是提取函数 // 需要一种方式将 data 和 i 传递进来,或者在函数内部获取 // 例如,如果函数在循环外定义,可以通过 bind 传参,但这又创建了新函数 console.log(`Clicked element, data: ${this.dataset.value}`); // 使用 this (需要确保 this 指向 element) // 或者在 addEventListener 时使用 bind } function createHandlersOptimized() { const elements = document.querySelectorAll('.my-elements-optimized'); for (let i = 0; i < elements.length; i++) { const element = elements[i]; // 绑定同一个函数引用,但需要处理数据传递问题 // element.addEventListener('click', handleClickOptimized.bind(element)); // bind 会创建新函数,但比内联函数稍好 // 更好的方式通常是事件委托 } // 事件委托方式 const container = document.getElementById('container'); if (container) { container.addEventListener('click', function(event) { const targetElement = event.target.closest('.my-element-delegated'); if (!targetElement) return; const data = targetElement.dataset.value; console.log(`Delegated click on element with data: ${data}`); }); } }
        • 解释: 在循环中为每个元素创建内联事件处理函数会生成大量闭包,占用更多内存。如果处理逻辑相同,最好将函数定义在循环外部。事件委托是更优的方式,将监听器添加到父元素,利用事件冒泡来处理子元素的事件,只需一个监听器。

      16. 在 JavaScript 中模拟块级作用域 (ES6 之前使用 IIFE)

        • 不良: (ES5 及之前)

          javascript
          代码解读
          复制代码
          (function() { var blockScopedVar = 'I am kind of block scoped'; console.log(blockScopedVar); }()); // console.log(blockScopedVar); // ReferenceError
        • 优化: (ES6+) 直接使用 let 和 const 以及 {} 块。

          javascript
          代码解读
          复制代码
          { let blockScopedVar = 'I am truly block scoped'; const anotherBlockVar = true; console.log(blockScopedVar, anotherBlockVar); } // console.log(blockScopedVar); // ReferenceError
        • 解释: 在 ES6 之前,只能通过立即执行函数表达式 (IIFE) 来模拟块级作用域。ES6 引入了 let 和 const,它们本身就具有块级作用域,使得代码更简洁、更自然。

      17. 字符串拼接性能问题 (大量拼接时)

        • 不良:

          javascript
          代码解读
          复制代码
          let longString = ''; const iterations = 10000; console.time('stringConcat'); for (let i = 0; i < iterations; i++) { longString += 'Part ' + i + '; '; // 每次 + 都会创建新字符串 } console.timeEnd('stringConcat'); // console.log(longString.length);
        • 优化: 使用数组 join('') 或模板字面量 (如果结构简单)。

          javascript
          代码解读
          复制代码
          const parts = []; const iterationsOpt = 10000; console.time('arrayJoin'); for (let i = 0; i < iterationsOpt; i++) { parts.push('Part '); parts.push(i); parts.push('; '); } const longStringOptimized = parts.join(''); console.timeEnd('arrayJoin'); // console.log(longStringOptimized.length); // 对于简单拼接,模板字面量可读性好,性能通常也不错 // let str = ''; // for (let i = 0; i < 10; i++) { // str += `Item ${i}\n`; // }
        • 解释: 在旧的 JavaScript 引擎中,使用 + 或 += 进行大量字符串拼接性能较差,因为字符串是不可变的,每次拼接都会创建新的中间字符串。将各部分添加到数组中,最后使用 join('') 一次性合并通常更快。现代 JS 引擎对字符串拼接做了很多优化,性能差异可能不再那么显著,但 join 对于构建非常长的字符串仍然是一个可靠的选择。模板字面量在可读性和性能之间取得了很好的平衡。

      18. 使用 setTimeout 或 setInterval 传递字符串代码 (类似 eval)

        • 不良: setTimeout("console.log('Delayed eval-like execution')", 1000);
        • 优化: 传递函数引用或箭头函数。 setTimeout(() => console.log('Delayed safe execution'), 1000); 或 setTimeout(myFunction, 1000);
      19. 依赖 Date 对象的构造函数解析字符串 (行为不一致)

        • 不良: new Date('2023-10-26') (结果可能因浏览器/时区而异,特别是没有指定时间时)。
        • 优化: 使用 ISO 8601 格式 (带时区或 UTC) new Date('2023-10-26T00:00:00Z') 或使用可靠的日期库 (如 date-fns, Moment.js - 后者已不推荐新项目使用)。
      20. 在 Array.prototype.map 或 filter 中执行副作用

        • 不良:

          javascript
          代码解读
          复制代码
          const ids = [1, 2, 3]; let sideEffectCounter = 0; const processed = ids.map(id => { console.log(`Processing id ${id}`); // 副作用:日志 sideEffectCounter++; // 副作用:修改外部变量 return { id: id, processed: true }; });
        • 优化: map 应用于转换数据,副作用应使用 forEach 或分离。

          javascript
          代码解读
          复制代码
          const idsOpt = [1, 2, 3]; let sideEffectCounterOpt = 0; // 使用 forEach 处理副作用 idsOpt.forEach(id => { console.log(`Processing id ${id} (forEach)`); sideEffectCounterOpt++; }); // 使用 map 进行纯粹的转换 const processedOpt = idsOpt.map(id => ({ id: id, processed: true })); console.log(processedOpt); console.log("Side effect counter:", sideEffectCounterOpt);
        • 解释: map 的设计目的是根据原数组创建一个新的转换后的数组,它应该是一个纯函数操作。在 map 回调中执行副作用(如修改外部变量、DOM 操作、网络请求)会违反其设计意图,使代码更难理解和测试。如果需要遍历并执行副作用,应使用 forEach。

      21. 不使用 Array.isArray() 判断数组

        • 不良: if (typeof myArray === 'object' && myArray !== null && myArray.hasOwnProperty('length')) (不准确,很多对象也有 length) 或 if (myArray instanceof Array) (跨 frame/realm 可能失效)
        • 优化: if (Array.isArray(myArray))
      22. 对非数字使用位运算 (可能产生意外结果)

        • 不良: const intVal = ~~"123.45"; (虽然可以取整,但可读性差,且对非数字字符串结果为 0) console.log(~~"abc"); // 0
        • 优化: 使用 Math.floor(), Math.trunc(), parseInt()。 const intValOpt = Math.trunc(Number("123.45"));
      23. 使用 toFixed() 后期望得到数字类型

        • 不良: const num = 123.456; const fixed = num.toFixed(2); console.log(typeof fixed); // "string"
        • 优化: 如果需要数字,用 parseFloat() 或 Number() 转换回来。 const fixedNum = parseFloat(num.toFixed(2)); 或 const fixedNum = Number(num.toFixed(2));
      24. 在 JSON.stringify 中丢失 undefined, 函数, Symbol 值

        • 不良: const obj = { a: 1, b: undefined, c: function(){}, d: Symbol('s') }; console.log(JSON.stringify(obj)); // "{"a":1}"
        • 优化: 了解此行为,如有需要,在序列化前进行预处理或使用支持这些类型的库。
      25. 在 JSON.parse 前不进行错误处理

        • 不良: const data = JSON.parse(invalidJsonString); (如果字符串无效会抛错)
        • 优化: 使用 try...catch 包裹 JSON.parse。
      26. 正则表达式不转义特殊字符

        • 不良: const userInput = "1. Item"; const regex = new RegExp(userInput); (如果 userInput 含特殊字符如 . 会按正则含义解析)
        • 优化: 对用户输入或动态构建的正则部分进行转义。function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[]\]/g, '\$&'); } const regexOpt = new RegExp(escapeRegExp(userInput));
      27. 创建不必要的正则表达式对象 (在循环内)

        • 不良: for (const str of strings) { const matches = str.match(/pattern/g); /* 每次循环都创建正则对象 */ }
        • 优化: 在循环外创建一次正则表达式对象。 const regex = /pattern/g; for (const str of strings) { const matches = str.match(regex); }
      28. 未使用 const 声明不会被重新赋值的变量

        • 不良: let PI = 3.14159; let MAX_USERS = 100; (变量意图是常量,但用了 let)
        • 优化: 使用 const 声明常量。 const PI = 3.14159; const MAX_USERS = 100;
      29. 链式赋值可读性差

        • 不良: let a = b = c = 0;
        • 优化: 分开赋值。 let a = 0; let b = 0; let c = 0; (或 const 如果适用)
      30. 使用 void 操作符产生 undefined (除非刻意)

        • 不良: const result = void (a + b); (结果是 undefined,可读性差)
        • 优化: 直接使用 undefined 字面量。 let result = undefined;
      31. 依赖函数声明提升的行为

        • 不良:

          javascript
          代码解读
          复制代码
          hoistedFunc(); // 可以调用,因为函数声明被提升了 function hoistedFunc() { console.log("Hoisted!"); }
        • 优化: 先声明后使用,提高可读性。

          javascript
          代码解读
          复制代码
          function definedFirst() { console.log("Defined First!"); } definedFirst(); // 调用在声明之后
        • 解释: 虽然函数声明提升是 JavaScript 的特性,但依赖它会降低代码从上到下的可读性。函数表达式(const myFunc = function() {} 或 const myFunc = () => {})则不会提升函数体。

      32. 使用 document.write (阻塞渲染,可能覆盖页面)

        • 不良: document.write("

          Hello

          ");
        • 优化: 使用 DOM 操作 API (createElement, appendChild, textContent 等)。
      33. 在非构造函数上使用 new

        • 不良: const result = new Math.max(1, 2); (TypeError: Math.max is not a constructor)
        • 优化: 直接调用函数 const result = Math.max(1, 2);
      34. 不使用严格模式 ('use strict';)

        • 不良: (省略 'use strict';) 导致静默错误、不安全的特性可用。
        • 优化: 在脚本或函数顶部添加 'use strict';。现代模块默认是严格模式。
      35. 对象属性或方法名使用保留字 (可能需要引号)

        • 不良: const obj = { try: 1, catch: 2 }; (在旧浏览器或某些情况下可能出错)
        • 优化: 使用引号 const obj = { 'try': 1, 'catch': 2 }; 或避免使用保留字。
      36. 对 document.querySelectorAll 的结果期望是实时更新的 NodeList

        • 不良: const list = document.querySelectorAll('.item'); /* 添加新 .item 元素 */; console.log(list.length); (长度不变,它是静态列表)
        • 优化: 了解 querySelectorAll 返回静态列表。如果需要实时列表,使用 getElementsByClassName 或 getElementsByTagName (返回 HTMLCollection),或者在需要时重新查询。
      37. 在 map, filter, reduce 中忘记 return

        • 不良: const doubled = [1, 2, 3].map(n => { n * 2; /* 忘记 return */ }); console.log(doubled); // [undefined, undefined, undefined]
        • 优化: 确保回调函数返回值。 const doubledOpt = [1, 2, 3].map(n => n * 2); 或 const doubledOpt = [1, 2, 3].map(n => { return n * 2; });
      38. reduce 方法不提供初始值 (当数组可能为空时)

        • 不良: const emptyArr = []; const sum = emptyArr.reduce((acc, val) => acc + val); (TypeError: Reduce of empty array with no initial value)
        • 优化: 提供初始值。 const sumOpt = emptyArr.reduce((acc, val) => acc + val, 0); console.log(sumOpt); // 0
      39. 在异步回调中修改循环变量 (ES5 var 陷阱的变种)

        • 不良: (类似 #1)

          javascript
          代码解读
          复制代码
          function processItemsVar(items) { for (var i = 0; i < items.length; i++) { // 假设 asyncOperation 是异步的 // asyncOperation(items[i], function(result) { // console.log(`Processed item ${i}: ${result}`); // i 始终是循环结束后的值 // }); } }
        • 优化: 使用 let 或将 i 传入异步回调 (通过闭包或参数)。

          javascript
          代码解读
          复制代码
          function processItemsLet(items) { for (let i = 0; i < items.length; i++) { // let 创建块作用域 // asyncOperation(items[i], function(result) { // console.log(`Processed item ${i}: ${result}`); // i 是当前循环的值 // }); } // 或者 // items.forEach((item, i) => { // asyncOperation(item, (result) => { // console.log(`Processed item ${i}: ${result}`); // }); // }); }
      40. 不必要的 Promise.resolve() 或 Promise.reject()

        • 不良: async function getData() { return Promise.resolve(syncValue); } 或 return Promise.reject(new Error('...'));
        • 优化: async 函数自动包装返回值 async function getDataOpt() { return syncValue; }。对于 reject,直接 throw new Error('...');
      41. 在 Promise.all 中某个 Promise reject 但仍期望获取其他成功结果

        • 不良: (如 #27 所示,Promise.all 会快速失败)
        • 优化: 使用 Promise.allSettled()。
      42. 将 async 函数作为事件监听器或回调,但不处理其返回的 Promise

        • 不良: button.addEventListener('click', async () => { await mightReject(); /* 未处理 rejection */ });
        • 优化: 在 async 回调内部使用 try...catch。 button.addEventListener('click', async () => { try { await mightReject(); } catch (e) { console.error(e); } });
      43. 使用 substr (可能被废弃)

        • 不良: str.substr(startIndex, length)
        • 优化: 使用 substring(startIndex, endIndex) 或 slice(startIndex, endIndex)。
      44. 浮点数精度问题未处理

        • 不良: console.log(0.1 + 0.2); // 0.30000000000000004 if (0.1 + 0.2 === 0.3) { /* false */ }
        • 优化: 比较时设置一个小的容差 (epsilon),或将浮点数转为整数计算再转回,或使用 Decimal 库。 const epsilon = 1e-10; if (Math.abs((0.1 + 0.2) - 0.3) < epsilon) { console.log("Approximately equal"); }
      45. 不缓存 DOM 查询结果 (在循环或多次使用时)

        • 不良: for (let i=0; i<10; i++) { document.getElementById('myElement').style.opacity = i / 10; /* 每次都查询 */ }
        • 优化: 查询一次并缓存结果。 const element = document.getElementById('myElement'); if (element) { for (let i=0; i<10; i++) { element.style.opacity = i / 10; } }
      46. 使用 setAttribute 设置 style (不如直接访问 style 对象)

        • 不良: element.setAttribute('style', 'color: red; font-weight: bold;'); (会覆盖所有现有内联样式)
        • 优化: element.style.color = 'red'; element.style.fontWeight = 'bold'; 或 element.style.cssText += '; color: red;'; (追加) 或使用 class。
      47. 事件监听器中进行耗时操作阻塞主线程

        • 不良: button.addEventListener('click', () => { for (let i=0; i<1e9; i++) {} /* 长时间计算 */; console.log('Done'); /* 界面卡顿 */ });
        • 优化: 使用 Web Workers 处理耗时计算,或将任务拆分成小块使用 setTimeout 或 requestAnimationFrame。
      48. 在 window.onscroll 或 window.onresize 中执行高频操作,未使用节流或防抖

        • 不良: window.onscroll = () => { console.log('Scrolled!', window.scrollY); /* 高频触发 */ };
        • 优化: 使用节流 (throttle) 或防抖 (debounce) 函数包装事件处理程序。 (需要引入 lodash/debounce 或自行实现)
      49. 未正确处理 this 的箭头函数 (当需要动态 this 时)

        • 不良: (在对象方法中,如果需要访问调用上下文的 this)

          javascript
          代码解读
          复制代码
          const counter = { count: 0, // 箭头函数 this 指向外层作用域 (可能是 window 或 undefined) increment: () => { // this.count++; // TypeError or increments global count if exists // console.log(this); } }; // counter.increment();
        • 优化: 使用普通函数作为对象方法。

          javascript
          代码解读
          复制代码
          const counterOpt = { count: 0, increment: function() { // this 指向 counterOpt this.count++; console.log("Counter incremented:", this.count); } // 或者使用 ES6 方法简写 // increment() { this.count++; console.log(this.count); } }; counterOpt.increment(); // Counter incremented: 1
      50. 过度使用解构赋值使代码难以理解

        • 不良: const { a: { b: [{ c: d }] }, e: [f,,g] } = complexNestedObject; (如果层级很深或命名随意)
        • 优化: 分步解构或使用更清晰的变量名。
      51. 将同步代码错误地放入 Promise 构造函数

        • 不良: new Promise((resolve) => { const result = 1 + 1; /* 同步操作 */ resolve(result); }); (可以用 Promise.resolve(1 + 1) 代替)
        • 优化: Promise 构造函数用于包装异步操作。同步值直接用 Promise.resolve()。
      52. 不必要的 await (对非 Promise 值)

        • 不良: async function example() { const syncValue = 10; const result = await syncValue; /* await 对 10 无效 */ return result; }
        • 优化: 直接返回值 async function exampleOpt() { const syncValue = 10; return syncValue; }
      53. 使用 async/await 但忘记函数声明为 async

        • 不良: function fetchData() { const data = await fetch('/api'); /* SyntaxError: await is only valid in async functions */ }
        • 优化: 添加 async 关键字 async function fetchDataOpt() { const response = await fetch('/api'); ... }
      54. 条件语句中复杂的布尔逻辑,未使用变量或函数封装

        • 不良: if ((user.isAdmin && user.isActive && !user.isSuspended) || (user.isSupport && settings.allowSupportOverride))
        • 优化: 提取为具名变量或函数。 const canPerformAction = isAdminUser(user) || isSupportOverride(user, settings); if (canPerformAction) { ... }
      55. 在 finally 块中修改可能在 try 或 catch 中设置的变量,意图影响返回值 (见 #44)

      56. 使用 for 循环反向迭代时索引处理不当

        • 不良: for (let i = arr.length; i >= 0; i--) { console.log(arr[i]); /* i=arr.length 时越界 */ }
        • 优化: for (let i = arr.length - 1; i >= 0; i--) { console.log(arr[i]); }
      57. 依赖对象属性的插入顺序 (ES2015 后部分情况有序,但不应完全依赖)

        • 不良: 期望 for...in 或 Object.keys 总是按插入顺序返回键 (仅对非负整数键按升序,其他字符串键按插入顺序,Symbol 键最后)。
        • 优化: 如果需要严格顺序,使用 Map 或将键存储在数组中按数组顺序访问。
      58. 将 null 和 undefined 视为完全相同 (虽然 == 认为它们相等)

        • 不良: 在逻辑判断中不区分它们,可能导致问题 (例如 null 可能表示“值确实为空”,undefined 可能表示“值未定义/未提供”)。
        • 优化: 根据需要使用 === null 或 === undefined 进行精确判断,或使用 value == null 同时检查两者。
      59. 不使用可选链 (?.) 处理可能不存在的方法调用

        • 不良: if (callback) { callback(); }
        • 优化: callback?.();
      60. 不使用空值合并 (??) 提供默认值 (与 || 的区别)

        • 不良: const timeout = options.timeout || 5000; (如果 options.timeout 是 0 或 false,也会被替换为 5000)
        • 优化: const timeout = options.timeout ?? 5000; (?? 只在左侧是 null 或 undefined 时才使用右侧的值)
      61. 在类方法中使用箭头函数,导致无法被子类正确覆盖或 super 调用

        • 不良:

          javascript
          代码解读
          复制代码
          class Parent { myMethod = () => { // 箭头函数绑定在实例上 console.log("Parent method"); } } class Child extends Parent { // 尝试覆盖,但实际上是定义了另一个实例属性 myMethod = () => { console.log("Child method"); // super.myMethod(); // TypeError: super.myMethod is not a function (箭头函数不在原型上) } } // const c = new Child(); // c.myMethod();
        • 优化: 使用普通方法定义在原型上。

          javascript
          代码解读
          复制代码
          class ParentOpt { myMethod() { // 定义在原型上 console.log("Parent method Opt"); } } class ChildOpt extends ParentOpt { myMethod() { console.log("Child method Opt"); super.myMethod(); // 可以正确调用父类方法 } } const cOpt = new ChildOpt(); cOpt.myMethod();
        • 解释: 类字段中的箭头函数会作为实例自身的属性,而不是原型上的方法。这使得子类无法通过原型链正确覆盖它,也无法使用 super 调用父类的同名方法。如果需要继承和覆盖,应使用标准的类方法语法。

      62. 手动实现 debounce 或 throttle 而不使用可靠的库 (容易出错)

        • 不良: (自行实现复杂的防抖/节流逻辑,可能未处理边缘情况)
        • 优化: 使用 Lodash (_.debounce, _.throttle) 或其他成熟库,或者仔细测试自己实现的版本。
      63. 正则表达式缺乏注释解释复杂模式

        • 不良: const regex = /^([a-z0-9_.-]+)@([\da-z.-]+).([a-z.]{2,6})$/; (没有注释)

        • 优化: 添加注释解释各部分含义。

          javascript
          代码解读
          复制代码
          // 匹配邮箱地址 const emailRegex = new RegExp( '^([a-z0-9_\.-]+)' + // 用户名部分: 字母、数字、下划线、点、连字符 '@' + // @ 符号 '([\da-z\.-]+)' + // 域名部分: 数字、字母、点、连字符 '\.' + // 点 . '([a-z\.]{2,6})$' // 顶级域名: 2-6个字母或点 );
      64. 在 Promise 构造函数中忘记调用 resolve 或 reject (导致 Promise 永远 pending)

        • 不良: new Promise((resolve, reject) => { setTimeout(() => { console.log("Done"); /* 忘记 resolve() */ }, 100); });
        • 优化: 确保异步操作完成后调用 resolve 或 reject。
      65. 使用 == true 或 == false 以外的布尔比较 (如 == 1, == 0)

        • 不良: if (count == 1) (如果只想判断是否为 true/false 意图,这会误导)
        • 优化: 使用 === true/=== false 或直接 if (booleanValue) / if (!booleanValue)。
      66. 在 Array.sort() 中提供不返回 -1, 0, 1 (或负数/零/正数) 的比较函数

        • 不良: arr.sort((a, b) => a > b); (返回布尔值,排序结果不可靠)
        • 优化: arr.sort((a, b) => a - b); (升序数字) 或 arr.sort((a, b) => a.localeCompare(b)); (字符串)
      67. 过度使用全局状态管理库 (如 Redux) 处理本地组件状态

        • 不良: 将所有组件内部的临时状态(如表单输入、开关状态)都放入全局 Store。
        • 优化: 使用组件本地状态 (useState) 管理仅与该组件相关的状态,仅将真正需要跨组件共享或持久化的状态放入全局 Store。
      68. 不使用 ?. 和 ?? 导致冗长的空值检查

        • 不良: const value = data && data.payload && data.payload.items ? data.payload.items[0] : 'default';
        • 优化: const value = data?.payload?.items?.[0] ?? 'default';
      69. 在循环中修改被迭代的集合 (如使用 splice 时未正确调整索引)

        • 不良:

          javascript
          代码解读
          复制代码
          const nums = [1, 2, 3, 4, 5]; for (let i = 0; i < nums.length; i++) { if (nums[i] % 2 === 0) { nums.splice(i, 1); // 删除元素后,后续元素前移,导致跳过检查 // i--; // 需要手动调整索引,容易出错 } } console.log("Incorrect removal:", nums); // 可能不是 [1, 3, 5]
        • 优化: 反向迭代或使用 filter 创建新数组。

          javascript
          代码解读
          复制代码
          const numsOpt = [1, 2, 3, 4, 5]; for (let i = numsOpt.length - 1; i >= 0; i--) { // 反向迭代 if (numsOpt[i] % 2 === 0) { numsOpt.splice(i, 1); } } console.log("Reverse removal:", numsOpt); // [1, 3, 5] const numsFilterOpt = [1, 2, 3, 4, 5]; const oddNums = numsFilterOpt.filter(n => n % 2 !== 0); // filter 更简洁且不可变 console.log("Filter removal:", oddNums); // [1, 3, 5]
      70. 代码重复,未抽取可重用函数或组件

        • 不良: (在多处复制粘贴相似的代码块)
        • 优化: 将重复逻辑封装到函数、类或组件中,进行调用。遵循 DRY (Don't Repeat Yourself) 原则。

      注:本文转载自blog.csdn.net的区块链大本营的文章"https://blog.csdn.net/Blockchain_lemon/article/details/88968602"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
      复制链接
      复制链接
      相关推荐
      发表评论
      登录后才能发表评论和回复 注册

      / 登录

      评论记录:

      未查询到任何数据!
      回复评论:

      分类栏目

      后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

      热门文章

      101
      推荐
      关于我们 隐私政策 免责声明 联系我们
      Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
      Scroll to Top