JavaScript存在哪些反模式?

56

我发现不应该做什么比应该做什么更难学。

根据我的经验,专家和初级者之间的差别在于能否从各种看似相等的方法中选择出最佳的。

那么,当涉及到JavaScript时应该避免哪些事情,以及为什么

我能够找到很多关于Java的这些内容,但由于JavaScript的典型上下文(在浏览器中)与Java非常不同,因此我很想看看会发生什么。


7
据我所见,整个编程语言都是一种反面模式。我不断惊讶于Sun公司没有因“Java”品牌的玷污而提起诉讼。请注意,此处仅为翻译,不包含任何解释或额外内容。 - Paul Tomblin
5
@Paul:真的吗?目前被广泛认为是最好的脚本语言是什么?具有动态性,可扩展性,并且具有一流的函数和最好的闭包实现。你在胡说八道 :/ (翻译):@Paul: 真的吗?哪种脚本语言被公认为是最好的?它必须是动态的、可扩展的、具备一等函数和我见过的最好的闭包实现。你在搞恶作剧 :/ - annakata
1
不,我不是在恶意挑衅。对我来说,它看起来很混乱、没有纪律。它被认为是“最好的脚本语言”唯一的原因是因为它是每个浏览器上唯一可用的脚本语言,而无需安装插件。 - Paul Tomblin
1
我不会轻易地称每个人编写自己的抽象层为琐事。2001年有点牵强(YUI的开发始于2005年,jQuery于2006年发布)。 - Greg Dean
9
强烈推荐您阅读《JavaScript语言精粹》一书,以了解JavaScript被称为“混乱和缺乏纪律性”的观点。虽然这门语言存在缺陷,但通常情况下,问题在于脚本开发人员的“混乱和缺乏纪律性”,无论是哪种语言,他们都会这样 :) - Tony Arkles
显示剩余11条评论
10个回答

50

语言:

  • 通过在全局上下文中创建大量变量来污染命名空间。

  • 以 'foo.onclick = myFunc' 的形式绑定事件处理程序(不可扩展,应使用attachEvent / addEventListener)。

  • 在几乎任何非 JSON 上下文中使用eval

  • 几乎每种使用document.write的方法(请使用DOM方法如document.createElement)

  • 针对Object对象进行原型设计(BOOM!)

  • 虽然很少,但是使用'+'进行大量的字符串连接(创建数组并将其合并效率更高)

  • 引用不存在的undefined常量

设计/部署:

  • (通常)不提供无脚本支持。

  • 未将代码打包为单个资源

  • 将内联脚本(即body)放置在顶部(它们会阻塞加载)

Ajax 特定:

  • 未向用户指示请求的开始、结束或错误状态

  • 轮询

  • 传递和解析 XML 而不是 JSON 或 HTML(在适当的情况下)

其中许多内容来自Addy Osmati的《学习JavaScript设计》一书:https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch06.html

编辑:我还在想更多!


3
我知道X代表什么,很多时候A也是错误的。它只是一个不幸的总称,用来描述我们很久以前就在做的东西。顺便说一下,JSON和XML之间的区别很大,但关键是JSON更轻量,并且是JS不需要解析的本地格式。 - annakata
4
我认为这只是一种纯粹学究的区别。AJAX这个词汇已经被广泛使用,所有人都知道它指的是什么,所以这种区别并不重要。忘记AJAX作为首字母缩略词的含义,把它理解为我们所有人都指的那个东西就好了。 - BobbyShaftoe
1
@Allain:与将答案放在一个易于找到的地方相比,业力不重要-此外,我印象中这是SO上首选的方法(可以确认吗?) - annakata
1
在"Ajax特定"下,你指的是什么样的“轮询”?许多JS框架(如jQuery)会对DOM或XMLHttpRequest对象进行自己的轮询。例如,jQuery有许多带有注释的“setTimeout”调用,“不断检查文档是否就绪”。 - thomasrutter
1
@instantsetsuna - 这是主要的一个问题,但反过来,当您直接绑定到事件时,您永远不知道您刚刚隐式地杀死了哪些功能。使用attach更安全,因为您必须明确删除它。 - annakata
显示剩余8条评论

20
除了已经提到的之外...
  • 使用 for..in 结构来迭代数组
    (遍历数组方法和索引)

  • <body onload="doThis();"> 这样在 JavaScript 中嵌入代码
    (不灵活且无法添加多个事件监听器)

  • 使用 'Function()' 构造函数
    (与 eval() 相同的原因使其不好)

  • 将字符串传递给 setTimeoutsetInterval 而不是函数
    (内部也使用了 eval())

  • 通过不使用分号来依赖隐式语句
    (养成不好的习惯,可能导致意外行为)

  • 使用 /* .. */ 来注释代码行
    (可能会干扰正则表达式字面量,例如:/* /.*/ */)

    <evangelism> 当然,不使用 Prototype ;) </evangelism>


有人知道我为什么被标记了吗?我是新手,不确定是否违反了某些规则... - Kenan Banks
我不知道。我已经投了你一票。我喜欢你大部分的回答。 - Allain Lalonde
2
@Chris。那太荒谬了。我加了个眨眼! - Kenan Banks
@annakata 你的意思是我不应该大写吗? - Kenan Banks
1
<3 原型。还有原型。对于原型设计也是一样。 - Matt Kantor
显示剩余2条评论

14

对我来说最大的问题不是理解 JavaScript 编程语言本身。

  • 过度使用对象层次结构并构建非常深的继承链。在大多数情况下,JS 中浅层次的层次结构可以很好地工作。
  • 不理解基于原型的面向对象编程,而是构建大量的脚手架使 JS 的行为类似于传统的面向对象语言。
  • 无必要地使用面向对象范式时,程序化/函数式编程可以更简明、高效。

接下来是一些针对浏览器运行时的建议:

  • 不使用好的已经存在的事件模式,如事件委托或观察者模式(pub / sub)来优化事件处理。
  • 在 DOM 更新频繁的情况下(例如在循环中使用 .appendChild),当 DOM 节点可以在内存中并一次性附加时,不需要这样做。(巨大的性能收益)。
  • 在可以使用本地方法(getElementById,getElementByTagName 等)的情况下,避免使用复杂选择器选择节点的库。(这在当今已经不是一个大问题,但值得一提)。
  • 如果你期望第三方脚本与你的脚本在同一页上,避免扩展 DOM 对象(你们将会相互覆盖代码)。

最后是部署问题:

  • 未压缩文件。
  • Web 服务器配置 - 不压缩文件,不明智地缓存它们。

<插入广告> 我在我的博客上有一些涵盖了我上面提到的一些内容以及更多内容的客户端性能优化技巧</插入广告>


9
  • 浏览器检测(而不是测试你想要使用的特定方法/字段是否存在)
  • 在大多数情况下使用alert()

另请参阅Crockford的《JavaScript: The Good Parts》,了解其他需要避免的内容。(编辑:警告,他在某些建议中有点严格,例如使用“===”而非“==”,因此根据自己的情况接受建议)


我也对Crockford的一些观点提出异议(例如他对with和continue的立场)。 - annakata
浏览器检测中的例外情况是,如果浏览器报告支持某个方法或属性,但你知道实现是错误/有缺陷的。IE支持elem.setAttribute(name,value),但它肯定不正确地支持它。 - scunliffe
@annakata:好观点,我也是这样做的。 - Jason S

8

以下是我能想到的一些事情。如果我想到更多,我会编辑这个列表。

  • 不要污染全局命名空间。应该将功能组织在对象中;
  • 不要省略变量的 'var' 关键字。这样会污染全局命名空间,并可能与其他脚本产生冲突。

请注意,JavaScript 没有类。但我假设你指的是函数作用域或对象属性/方法。 - thomasrutter
@thomasrutter - 抱歉,已经更正。 :) - Vilx-
哇,我的两条评论之间几乎相隔了5年... - thomasrutter
@thomasrutter - 让你感觉老了,是不是?XD - Vilx-

7

'with' 的任何使用

with (document.forms["mainForm"].elements) {
input1.value = "垃圾";
input2.value = "垃圾"; }


我想知道当我们拥有更好的JavaScript代码分析工具时,这个问题是否会停止存在。 - Allain Lalonde
1
请参见此处:https://dev59.com/lXVD5IYBdhLWcg3wL4YA,了解“with”的讨论;在我看来,它至少有一个有效的用途,可能还有更多……但作为范围控制的手段,而不是修改对象成员的便利。 - Shog9

5

创建语句时大括号位置使用不当

由于自动分号插入,您应该始终在语句后放置大括号。

例如:

function()
{
    return
    {
        price: 10
    }
}

与此大不相同:

function(){
    return{
        price: 10
    }
}

因为在第一个例子中,JavaScript实际上会为您插入分号,使您最终得到这个结果:
function()
{
    return;    // oh dear!
    {
        price: 10
    }
}

使用setInterval处理潜在的长时间运行任务。

在需要重复执行某些操作的情况下,应该使用setTimeout而不是setInterval。

如果使用setInterval,但是计时器中执行的函数在计时器下一次触发之前未完成,那么这是不好的。相反,应该使用以下模式使用setTimeout。

function doThisManyTimes(){
    alert("It's happening again!");
}

(function repeat(){
    doThisManyTimes();
    setTimeout(repeat, 500);
})();

这个非常好地解释了,Paul Irish 在他的 从jQuery源码中学到的10件事 视频中讲得非常清楚。


2
如果您使用setInterval,但计时器执行的函数在计时器下一次触发之前尚未完成,那么这是不好的。为什么这样做不好?会发生什么事情? - Kirk Woll
@Kirk 这取决于函数的作用,如果该函数改变视图,然后初始化一个ajax请求,它的回调对视图做出了一些改变,那么这个顺序出现不同步是非常不可取的。以股票数据为例。 - Swaff
JavaScript是事件驱动的,执行上下文中的所有内容都在一个线程中按照排队顺序执行。使用需要较长时间的函数与setInterval没有负面影响 - 它完全没问题。它不会“中断”其他代码,只是意味着在当前调用结束后,下一次对该函数的调用将被延迟。 - thomasrutter
在您的一个函数发起ajax请求的示例中 - 如果它是同步请求,那么函数的其余部分将简单地等待请求完成,然后运行函数的其余部分,然后在此之后执行任何来自后续setinterval滴答声的排队函数。如果它是异步AJAX调用,那么您必须处理回调可能以与其他回调无论如何不可预测的顺序到达的可能性,这与setinterval与settimeout没有关系。 - thomasrutter

5
任何提及
document.all

在你的代码中,除非它是在特殊代码内部,仅为了克服IE的一个bug。(咳咳 document.getElementById() 咳咳)

4

不使用基于社区的框架来执行重复性任务,如DOM操作、事件处理等。


我的个人选择会是jQuery,但实际框架并不重要。只要它不是自己编写的就行。 - Allain Lalonde
6
我有些不同意 - 有时候特定的解决方案比通用的更好,我自己写的一些 JS 代码比 jQuery 的等效代码更优秀。也许我应该把这些东西交给 jQuery,但关键是群众并不总是正确的。 - annakata
1
那么我们就同意各自保留自己的观点。 :) 即使你个人的框架更匹配,但通过使用基于社区的方法,你可以获得“免费”的测试。 - Allain Lalonde
3
其实不完全是这样,这段代码跟我已经有五年的渊源了 :) - annakata
3
“Dragging”是一个贴切的隐喻。 :) - Allain Lalonde
我有点觉得大型框架有点笨重,当你只想使用一小部分功能时。也许YUI在这方面有正确的想法,因为你可以只加载你需要的模块,但它做起来非常冗长。但是,如果你把jQuery裁剪到合适的大小,你就会陷入维护自己的jQuery分支的境地。我不知道答案是什么。 - thomasrutter

0

有效的缓存很少被执行:

  • 当您可以使用共享 API(例如Google's Libraries API)加速页面加载时,请勿在服务器上存储库的副本(jQuery、Prototype、Dojo等)
  • 合并和压缩所有可能的脚本为一个
  • 使用mod_expires为所有脚本提供无限的缓存生命周期(永不重新下载)
  • 对于新版本,使用javascript文件名进行版本控制,以便客户端不需要重新加载/重启即可获取更新(即myFile_r231.js或myFile.js?r=231)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接