前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >译文:开发人员面临的 10个最常见的JavaScript 问题

译文:开发人员面临的 10个最常见的JavaScript 问题

作者头像
程序员法医
发布2022-12-20 21:24:35
1.2K0
发布2022-12-20 21:24:35
举报
文章被收录于专栏:vue全家桶vue全家桶

前端猎手

转载自Duing(ID:duyi-duing)

大家好,我是法医

原文链接:https://www.toptal.com/javascript/10-most-common-javascript-mistakes

作者:Ryan J. Peterson

如有翻译不准,请多指正。

今天,JavaScript是几乎所有现代Web应用程序的核心。这就是为什么JavaScript问题,以及找到导致它们的错误,是Web开发人员的最前沿的问题。

用于单页应用程序(SPA) 开发、图形和动画以及服务器端JavaScript平台的强大基于JavaScript的库和框架并不是什么新鲜事。JavaScript在Web应用程序开发领域已经真正无处不在,因此是一项越来越重要的技能。

起初,JavaScript可能看起来很简单。事实上,对于任何有经验的软件开发人员来说,将基本的JavaScript功能构建到网页中都是一项相当简单的任务,即使他们是JavaScript的新手。然而,这种语言比人们最初认为的要微妙,强大和复杂得多。事实上,JavaScript的许多微妙之处导致了许多阻碍它工作的常见问题-我们在这里讨论其中的10个问题-在寻求成为JavaScript大师开发人员的过程中,重要的是要注意并避免这些问题。

Java 问题#1:错误引用this

在JavaScript 开发人员中,不乏关于对this的困惑

随着JavaScript编码技术和设计模式多年来变得越来越复杂,回调和闭包中自我引用范围的扩散也相应增加,这是导致JavaScript问题的“this/混淆”的一个相当常见的来源。

请考虑以下示例代码段:

执行上述代码会导致以下错误:

为什么?这完全取决于上下文。你收到上述错误的原因是,当你调用setTimeout()时,你实际上是在调用window.setTimeout()。因此,要传递给setTimeout()的匿名函数是在window对象的上下文中定义的,该对象没有clearBoard()方法。

传统的,与旧浏览器兼容的解决方案是简单地将你对this的引用保存在变量中,然后可以通过闭包继承,例如:

或者,在较新的浏览器中,可以使用bind()方法传入正确的引用:

JavaScript 问题#2:认为存在块级作用域

正如我们的 JavaScript招聘指南中所讨论的,JavaScript开发人员之间混淆的一个常见来源(也是错误的常见来源)是假设JavaScript为每个代码块创建了一个新的范围。虽然这在许多其他语言中是正确的,但在JavaScript中却不是这样。例如,请考虑以下代码:

如果你猜测调用console.log()会输出undefined或引发错误,那你猜错了。信不信由你,它会输出10。为什么?

在大多数其他语言中,上面的代码会导致错误,因为变量i的“生命”(即范围)将被限制在for块中。但是,在JavaScript中,情况并非如此,即使在for循环完成后,变量i仍保留在作用域中,在退出循环后保留其最后一个值。(顺便说一句,此行为可称为变量提升)。

通过let关键字支持JavaScript中的块级范围。多年来,浏览器和Node.js等后端JavaScript引擎广泛支持let关键字。

JavaScript问题#3:内存泄漏

内存泄漏几乎是不可避免的JavaScript问题,如果你没有有意识地编码以避免它们。它们发生的方式有很多种,所以我们只重点介绍一些更常见的发生。

内存泄漏示例1:悬空对已失效对象的引用

请考虑以下代码:

如果运行上述代码并监视内存使用情况,你会发现内存严重泄漏,每秒泄漏整整一兆字节!即使是手动垃圾回收器(GC)也无济于事。所以看起来我们每次调用replaceThing都在泄漏longStr。这是为什么?

内存泄漏几乎是不可避免的JavaScript问题,如果你没有有意识地编码以避免它们。

让我们更详细地研究一下:

每个theThing对象都包含自己的1MB longStr对象。每秒钟,当我们调用replaceThing时,它都会保留对theThing中priorThing对象的引用。但是我们仍然不会认为这将是一个问题,因为每次通过时,先前引用的priorThing内容都会被取消引用(当priorThing通过priorThing= theThing重置时)。而且,仅在实际上从未使用过replaceThing的功能的主体和函数中引用。

因此,我们再次想知道为什么这里存在内存泄漏。

为了理解发生了什么,我们需要更好地了解JavaScript的内部工作原理。实现闭包的典型方法是,每个函数对象都有一个指向字典样式对象的链接,该对象表示其词法范围。如果内部定义的两replaceThing函数实际上都使用了priorThing,那么重要的是它们都得到相同的对象,即使priorThing一遍又一遍地分配给它们,所以两个函数共享相同的词汇环境。但是,一旦变量被任何闭包使用,它就会最终进入该范围内所有闭包共享的词汇环境中。而这个小小的细微差别就是导致这种可怕的内存泄漏的原因。

内存泄漏示例2:循环引用

请考虑以下代码片段:

在这里,onClick有一个闭包,保留对元素的引用(通过element.nodeName)。通过还将onClick分配给element.click,创建了循环引用;即:element→ onClick → element → onClick → element...

有趣的是,即使element从DOM中删除,上面的圆形自我引用也会防止element和onClick被收集,从而防止内存泄漏。

避免内存泄漏:要点

JavaScript的内存管理(特别是垃圾回收)主要基于对象可访问性的概念。

假定以下对象是可访问的,称为“根”:

  • 从当前调用堆栈中的任意位置引用的对象(即,当前正在调用的函数中的所有局部变量和参数,以及闭包作用域中的所有变量)
  • 有全局变量

对象至少保留在内存中,只要它们可以通过引用或引用链从任何根访问。

浏览器中有一个垃圾回收器,用于清理无法访问的对象占用的内存;换句话说,当且仅当 GC认为对象无法访问时,才会从内存中删除对象。不幸的是,很容易得到已经失效的“僵尸”对象,这些对象不再使用,但GC仍然认为是“可访问的”。

JavaScript问题#4:关于平等的困惑

JavaScript中的一个优点是,它会自动强制在上下文中引用的任何值转换为布尔值。但在某些情况下,这可能既容易又令人困惑。例如,对于许多JavaScript开发人员来说,以下一些内容是很麻烦的:

关于最后两个,尽管是空的(这可能会导致人们相信他们会评估为false),但{}和[]事实上,是对象,任何对象都将被迫在JavaScript中达到true的布尔值,这与ECMA-262规范一致。

正如这些例子所表明的那样,类型强制转换规则有时会一清二楚。因此,除非明确需要类型胁迫,否则通常最好使用===和!==(而不是==和!=),以避免类型胁迫的任何意外副作用。(==和!=在比较两件事时自动执行类型转换,而===和!==在不转换类型的情况下进行相同的比较。)

完全作为旁观——但由于我们正在谈论类型胁迫和比较——值得一提的是,将NaN与任何东西进行比较(甚至NaN!)总是返回false。因此,您不能使用等式运算符(==,===, !=,!==)确定一个值是否为NaN。相反,请使用内置的全局isNaN()函数:

JavaScript问题#5:低效的DOM 操作

JavaScript使得操作DOM(即添加,修改和删除元素)相对容易,但对促进这样做没有任何作用。

一个常见示例是一次添加一个DOM元素系列的代码。添加DOM元素是一项代价高昂的操作。连续添加多个DOM元素的代码效率低下,并且可能无法正常工作。

当需要添加多个DOM元素时,一种有效的替代方法是改用文档片段,从而提高效率和性能。

例如:

除了此方法固有的改进效率之外,创建附加的DOM 元素成本高昂,而在分离时创建和修改它们,然后附加它们会产生更好的性能。

JavaScript 问题#6:在循环中不正确地使用函数定义

请考虑以下代码:

根据上述代码,如果有10个输入元素,单击其中任何一个都将显示“这是元素#10”!这是因为,当为任何元素调用onclick时,上述循环将已完成,i的值已经为10(对于所有元素)。

以下是我们如何纠正JavaScript的上述问题,以实现预期行为:

在这个修订后的代码版本中,每次我们通过循环时都会立即执行makeHandler,每次收到i+1的当前值并将其绑定到作用域num变量。外部函数返回内部函数(也使用此作用域num变量),元素的onclick设置为该内部函数。这确保了每个onclick接收和使用正确的i值(通过作用域num变量)。

JavaScript 问题#7:未能正确利用原型继承

令人惊讶的是,高比例的JavaScript开发人员无法完全理解,因此无法充分利用原型继承的功能。

下面是一个简单的示例。请考虑以下代码:

似乎相当简单。如果提供了名称,请使用它,否则将名称设置为“默认”。例如:

但是,如果我们这样做呢:

但是,将其恢复为“默认”不是更好吗?如果我们修改原始代码以利用原型继承,这可以很容易地完成,如下所示:

使用此版本,BaseObject从其prototype对象继承name属性,其中(默认情况下)设置为'default'”。因此,如果在没有名称的情况下调用构造函数,则名称将默认为default。同样,如果从BaseObject的实例中删除name属性,则将搜索原型链,并从其值仍然是'default'的prototype对象中检索name属性。所以现在我们得到了:

JavaScript 问题#8:创建对实例方法的错误引用

让我们定义一个简单的对象,并创建它的一个实例,如下所示:

现在,为了方便起见,让我们创建一个对whoAmI方法的引用,大概这样我们就可以仅通过whoAmI()而不是更长的objobj.whoAmI()访问它:

为了确保一切看起来都是同步的,让我们打印出新whoAmI变量的值:

输出:

但现在,看看我们调用objobj.whoAmI()与我们的方便引用whoAmI()时的区别:

出了什么问题?当我们进行赋值varwhoAmI =obj.whoAmI;时,正在全局命名空间中定义新的变量whoAmI。因此,它的值是window,而不是MyObject的obj实例!

因此,如果我们真的需要创建对对象现有方法的引用,我们需要确保在该对象的命名空间中进行引用,以保留该this的值。这样做的一种方式如下:

JavaScript问题#9:提供字符串作为setTimeout或setInterval的第一个参数

首先,让我们在这里明确一点:提供字符串作为setTimeout或setInterval的第一个参数本身本身并不是错误。这是完全合法的JavaScript代码。这里的问题更多的是性能和效率。很少解释的是,如果你将字符串作为setTimeout或setInterval的第一个参数传递,它将传递给函数构造函数以转换为新函数。这个过程可能缓慢且效率低下,而且没必要。

将字符串作为这些方法的第一个参数传递的替代方案是传递函数。让我们来举一个例子。

那么,这里将是setInterval和setTimeout的相当典型的使用,将字符串作为第一个参数:

更好的选择是传入函数作为初始参数;例如:

JavaScript 问题#10:未能使用“严格模式”

正如我们的JavaScript招聘指南中所解释的那样,“严格模式”(即包括'usestrict';在JavaScript源文件的开头)是一种在运行时自愿对JavaScript代码实施更严格的解析和错误处理的方法,并使其更安全。

虽然,不使用严格模式本身并不是“错误”,但人们越来越鼓励使用严格模式,其遗漏也越来越被视为不良形式。

以下是严格模式的一些关键好处:

·使调试变得更容易。否则将被忽略或默默失败的代码错误现在将生成错误或抛出异常,更快地提醒您代码库中的JavaScript问题,并更快地将您引导到它们的源代码。

·防止意外的全局。如果没有严格模式,将值分配给未声明的变量会自动创建一个具有该名称的全局变量。这是最常见的JavaScript错误之一。在严格模式下,尝试这样做会引发错误。

·消除this胁迫。如果没有严格模式,对空或未定义的this值的引用会自动强制到全局。这可能会导致许多令人沮丧的错误。在严格模式下,引用this值为null或未定义会引发错误。

·禁止重复的属性名称或参数值。当严格模式检测到对象中的重复命名属性(例如,varobject = {foo: "bar", foo: "baz"};)或函数的重复命名参数(例如,functionfoo(val1, val2, val1){})时,它会抛出错误,从而捕获代码中几乎可以肯定的错误,否则您可能会浪费大量时间跟踪。

·使eval()更安全。eval()在严格模式和非严格模式下的行为方式存在一些差异。最重要的是,在严格模式下,在eval()语句中声明的变量和函数不会在包含范围内创建。(它们是在非严格模式的包含范围内创建的,这也可能是JavaScript问题的常见来源。)

·无效使用delete时抛出错误。delete运算符(用于从对象中删除属性)不能用于对象的不可配置属性。当尝试删除不可配置的属性时,非严格代码将默默失败,而在这种情况下,严格模式会引发错误。

写在最后

与任何技术一样,你越了解JavaScript为什么以及如何工作和不起作用,你的代码就越可靠,你就越能有效地利用语言的真正力量。相反,缺乏对JavaScript 范式和概念的正确理解确实是许多JavaScript 问题所在。

彻底熟悉语言的细微差别和微妙之处是提高熟练程度和生产力的最有效策略。避免许多常见的JavaScript 错误将有助于正常工作。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端猎手 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存泄漏示例1:悬空对已失效对象的引用
  • 内存泄漏示例2:循环引用
  • 避免内存泄漏:要点
  • JavaScript问题#4:关于平等的困惑
  • JavaScript问题#5:低效的DOM 操作
  • JavaScript问题#9:提供字符串作为setTimeout或setInterval的第一个参数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档