来自1000多个项目的10大JavaScript错误浅析

  Queryparser是一款来自Uber的开源SQL解析和分析工具,通过其分析结果能够洞悉大规模数据访问的模式。在本文中,我们会讨论Queryparser的实现、它解锁的应用多样性以及发展过程中所遇到的一些问题和限制。

  随着移动和数据驱动应用的爆发性增长,用户现在需要在任意地点实时访问所有的功能。系统的弹性和响应性变成了业务的基本需求。业务方面需要更加灵活的“反应式”系统。为了支持反应式开发,结合Actor模型和领域驱动设计能够满足现代系统弹性的需要。

  当Telenor需要更好地评估产品早期开发阶段的情况时,他们为早期不同的产品阶段建立了基于经验学习的对应KPI组合来取代财务指标。他们研究了诸如微软和IBM等公司使用的门径管理流程,并在Telenor也发展出了一个门径管理系统以进行相关投入。

  近期,InfoQ与solo.io的CEO Idit Levine进行了一次座谈。Idit新创立了一种称为“Squash”的开源微服务调试器。在此次座谈中,她介绍了观察和调试分布式系统和应用中的挑战。

  亲爱的读者:我们最近添加了一些个人消息定制功能,您只需选择感兴趣的技术主题,即可获取重要资讯的邮件和网页通知。

  作为对社区开发者的回馈,我们从我们的数据库里选出了10大来自数千个项目的JavaScript错误。我们将会给出产生这些错误的根源,以及如何避免再发生这些错误。如果能够避免这些错误,就可以成为更好的开发者。

  数据才是王道,我们通过收集和分析大量数据才选出了这10大JavaScript错误。我们收集每一个项目中出现的错误,并统计每一个错误发生的次数。我们根据错误代码的指纹(fingerprint)对它们进行分组,也就是说,如果第二个错误与第一个是重复的,就把它们归入同一个组。这样就可以为用户提供更好的视图,而不是像查看繁琐的日志文件那样。

  我们只关注影响面最大的那些错误。为此,我们统计了错误在各个公司的项目中发生的次数,而不是错误发生的总次数,因为如果是这样的话,读者就可能看到大量与他们不相干的统计信息。

  如果你是一名JavaScript开发者,对这个错误可能已经熟视无睹。在Chrome里读取未定义对象的属性或调用未定义对象的方法时就会发生这个错误,在Chrome开发者控制台可以很容易地重现这个错误。

  发生这个错误的原因有很多,其中最为常见的是,在渲染UI组件时没有正确初始化状态。我们通过一个真实的例子来看看这个错误是怎么发生的。我们选择React作为示例,不过在其他框架(Angular、Vue等)中也是一样的。

  在Safari里读取未定义对象的属性或调用未定义对象的方法时就会发生这个错误,在Safari开发者控制台可以很容易地重现这个错误。这个错误与发生在Chrome里的是差不多的,只是Safari为它提供了不同的错误信息。

  在Safari里读取空(null)对象的属性或调用空对象的方法时就会发生这个错误,在Safari开发者控制台可以很容易地重现这个错误。

  有意思的是,在JavaScript里,null和undefined其实是不一样的,所以我们会看到两个不同的错误消息。undefined表示未赋值的变量,而null表示变量值为空。可以使用严格等于号来证明它们不是同一个东西。

  在实际应用当中,在JavaScript里调用一个未加载的DOM元素就会出现这个错误。如果对象为空,DOM API就会返回null。

  DOM元素需要在创建之后才能被访问。JavaScript代码是按照从上到下的顺序进行解析的,所以,如果在DOM元素之前有一个标签包含了JavaScript代码,浏览器在解析HTML时就会执行这些代码。在加载JavaScript之前,如果DOM元素没有被创建,就会出现这个错误。

  在这个例子里,我们可以通过添加一个事件来解决这个问题,在页面加载完毕时,事件会通知我们。在addEventListener被触发之后,init()方法就可以大胆地访问DOM元素了。

  跨域的未捕捉JavaScript异常会变成Script error。例如,假设JavaScript托管在CDN上,那么未捕捉的错误(错误没有在try-catch里被捕获,一路直上到了window.onerror里)就会显示成“Script error”,而不是显示具体的错误消息。这是浏览器出于安全方面的考虑,防止跨域传递数据。

  将Access-Control-Allow-Origin设置成“*”,表示该资源可以被任何一个域访问。如果有必要,可以把“*”替换成你的域名,例如Access-Control-Allow-Origin: 。不过,如果使用了CDN,那么要支持多个域名可能就会得不偿失,因为CDN存在缓存问题。

  在JavaScript文件所在的目录创建一个叫作.htaccess的文件,并加入如下内容:

  在IE里读取未定义对象的属性或调用未定义对象的方法时就会发生这个错误,在IE开发者控制台可以很容易地重现这个错误。

  在IE里使用JavaScript的命名空间时,就很容易碰到这个错误。发生这个错误十有八九是因为IE无法将当前命名空间里的方法绑定到this关键字上。例如,假设有个命名空间Rollbar,它有一个方法叫isAwesome()。在Rollbar命名空间中,可以直接使用this关键字来调用这个方法:

  在Chrome、Firefox和Opera中这样做都是没有问题的,但在IE中就不行。所以,最安全的做法是指定全命名空间:

  在Chrome里调用一个未定义的函数时就会发生这个错误,可以在Chrome开发者控制台和Mozilla开发者控制台重现这个错误。

  近年来,JavaScript的编码技术和设计模式变得日趋复杂,回调和闭包中的自引用情况越来越普遍,让人搞不清楚代码中的this/that表示的是什么意思。

  对于旧浏览器,以往的解决办法是将this赋值给某个变量,然后在闭包里使用这个变量。例如:

  在Chrome里,有几种情况会发生这个错误,其中一个就是无限递归调用一个函数。这个错误可以在Chrome开发者控制台重现。

  在Chrome里读取undefined变量的length属性时会发生这个错误,这个错误可以在Chrome开发者控制台重现。

  length是数组的属性,但如果数组没有初始化或者数组的变量名被另一个上下文隐藏起来的话,访问length属性就会发生这个错误。例如:

  函数的参数名会覆盖全局的变量名。也就是说,全局的testArray被函数的参数名覆盖了,所以在函数体里访问到的是本地的testArray,但本地并没有定义testArray,所以出现了这个错误。

  1). 将函数的参数名移除(这就表示函数里要访问的变量已经在函数外面定义好了,所以函数不需要参数):

  在访问一个未定义的对象或超出当前作用域的对象时就会发生这个错误,这个错误可以在Chrome开发者控制台重现。

  如果在进行事件处理时遇到这个错误,请确保事件对象被作为参数传入到函数当中。旧浏览器(IE)提供了全局的event变量,但并不是所有的浏览器都会这样。尽管jQuery尝试对这种行为进行规范化,但最好还是使用传给函数的event对象:

  我们希望这些内容能够帮助大家在未来避免这些错误,解决大家的痛点。不过,即使有了这些最佳实践,在生产环境中仍然会出现各种不可预期的错误。关键是要及时发现那些影响用户体验的错误,并使用适当的工具快速解决这些问题。

  我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。

相关阅读