分享

10 个最常见的 JavaScript 错误(以及如何避免它们)- 来自 Rollbar 1000 ...

 板桥胡同37号 2020-01-29

为了回馈我们的开发者社区,我们查看了数千个项目的数据库,发现了 JavaScript 的 10 大错误。我们将向你展示这些错误的原因,以及如何防止这些错误发生。如果你避免了这些 “陷阱” ,这将使你成为一个更好的开发人员。
由于数据是国王,我们收集,分析并排名前十的 JavaScript 错误。 Rollbar 会收集每个项目的所有错误,并总结每个项目发生的次数。 我们根据 指纹 对错误进行分组,来做到这一点。基本上,如果第二个错误只是第一个错误的重复,我们会把两个错误分到同一组。 这给用户一个很好的概括,而不是像在日志文件中看到的那些压迫性的一大堆垃圾描述。
我们专注于最有可能影响你和你的用户的错误。 为此,我们通过横跨不同公司的项目数来排列错误。 如果我们只查看每个错误发生的总次数,那么大流量的项目可能会淹没与大多数读者无关的错误的数据集。
以下是排名前 10 的 JavaScript 错误:

为了便于阅读,没有花大段的文字来描述每个错误。让我们深入到每一个错误,来确定什么可以导致它,以及如何避免它发生。

1.Uncaught TypeError: Cannot read property

如果你是一个 JavaScript 开发人员,你可能已经看到这个错误的次数比你敢承认的要多。当你读取一个属性或调用一个未定义的对象的方法时,这个错误会在 Chrome 中发生。你可以在 Chrome 开发者工具的控制台中轻松测试。

发生这种情况的原因很多,但常见的一种情况是在渲染UI组件时不恰当地初始化了 state(状态)。 我们来看一个在真实应用程序中如何发生的例子。 我们将选择 React,但不正确初始化的原则也适用于Angular,Vue或任何其他框架。
JavaScript 代码:
  1. classQuizextendsComponent{

  2. componentWillMount() {

  3. axios.get('/thedata').then(res => {

  4. this.setState({items: res.data});

  5. });

  6. }

  7. render() {

  8. return(

  9. <ul>

  10. {this.state.items.map(item =>

  11. <li key={item.id}>{item.name}</li>

  12. )}

  13. </ul>

  14. );

  15. }

  16. }

这里有两件重要的事情要实现:
  1. 组件的状态(例如 this.state)从 undefined 开始。

  2. 当您异步获取数据时,组件在数据加载之前至少会渲染一次,而不管它是在构造函数 componentWillMount 还是 componentDidMount 中获取的。 当 Quiz 第一次渲染时,this.state.items 是 undefined。 这又意味着 ItemList 将 items 定义为 undefined ,并且在控制台中出现错误 – “Uncaught TypeError: Cannot read property ‘map’ of undefined”。

这个问题很容易解决。最简单的方法:在构造函数中用合理的默认值来初始化 state。
JavaScript 代码:
  1. classQuizextendsComponent{

  2. // 添加这些代码:

  3. constructor(props) {

  4. super(props);

  5. // 分配 state(状态) 本身,以及 items 的默认值

  6. this.state = {

  7. items: []

  8. };

  9. }

  10. componentWillMount() {

  11. axios.get('/thedata').then(res => {

  12. this.setState({items: res.data});

  13. });

  14. }

  15. render() {

  16. return(

  17. <ul>

  18. {this.state.items.map(item =>

  19. <li key={item.id}>{item.name}</li>

  20. )}

  21. </ul>

  22. );

  23. }

  24. }

你的应用中的确切代码可能会有所不同,但是我们希望我们已经给了你足够的线索,来解决或避免在你的应用程序中出现这个问题。如果你还没有碰到,请继续阅读,因为我们将在下面覆盖更多相关错误的示例。

2.TypeError: ‘undefined’ is not an object (evaluating

这是在 Safari 中读取属性或调用未定义对象上的方法时发生的错误。你可以在 Safari Developer Console 中轻松测试。这与 Chrome 的上述错误基本相同,但 Safari 使用不同的错误消息。

3.TypeError: null is not an object (evaluating

这是在Safari中读取属性或调用 空对象(null) 上的方法时发生的错误。您可以在 Safari Developer Console中轻松测试。

有趣的是,在 JavaScript 中,null 和 undefined 是不一样的,这就是为什么我们看到两个不同的错误信息。 undefined 通常是一个尚未分配的变量,而 null 表示该值为空。 要验证它们不相等,请尝试使用严格相等运算符 === :

在现实的例子中,这种错误可能发生的一种场景是:如果在加载元素之前尝试在 JavaScript 中使用 DOM 元素。这是因为 DOM API 对于空白的对象引用返回 null 。
任何执行和处理 DOM 元素的 JS 代码都应在 DOM 元素创建后执行。JS 代码按照 HTML 中的规定从上到下进行解析。所以,如果 DOM 元素之前有一个 script 标签, script 标签内的JS代码将在浏览器解析 HTML 页面时执行。如果在加载脚本之前尚未创建 DOM 元素,则会出现此错误。
在这个例子中,我们可以通过添加一个事件监听器来解决这个问题,这个监听器会在页面准备好的时候通知我们。一旦 addEventListener 被触发,init() 方法就可以使用 DOM 元素。
HTML 代码:
  1. <script>

  2. function init() {

  3. var myButton = document.getElementById('myButton');

  4. var myTextfield = document.getElementById('myTextfield');

  5. myButton.onclick = function() {

  6. var userName = myTextfield.value;

  7. }

  8. }

  9. document.addEventListener('readystatechange', function() {

  10. if(document.readyState === 'complete') {

  11. init();

  12. }

  13. });

  14. </script>

  15. <form>

  16. <inputtype='text'id='myTextfield'placeholder='Type your name'/>

  17. <inputtype='button'id='myButton'value='Go'/>

  18. </form>

4.(unknown): Script error

当一个未捕获的 JavaScript 错误违反了跨域策略时,就会出现这类脚本错误。例如,如果你将 JavaScript 代码托管在 CDN 上,任何未被捕获的错误(这个会冒泡到 window.onerror 处理程序,而不是在 try-catch 捕获)将被报告为简单的 “脚本错误” ,而不会包含有用的信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则将不允许进行通信。
如果你要获取到真实的错误消息,请执行以下操作:
  1. 发送 Access-Control-Allow-Origin 头信息

将 Access-Control-Allow-Origin 头信息设置为 * ,表示可以从任何域正确访问资源。如有必要,您可以用你的域名替换 *,例如 Access-Control-Allow-Origin: www.example.com 。但是,处理多个域名会有些棘手,如果你使用 CDN ,由此出现的缓存问题可能会让你感觉不值得付出努力。 点击这里 看到更多。
下面是一些如何在不同环境中设置 Access-Control-Allow-Origin 头信息的例子。

Apache

在 JavaScript 文件所在的文件夹中,使用以下内容创建一个 .htaccess 文件:
.htaccess 代码:
  1. HeaderaddAccess-Control-Allow-Origin'*'

Nginx

将 add_header 指令添加到提供 JavaScript 文件的位置块中:
Nginx 代码:
  1. location ~ ^/assets/{

  2. add_header Access-Control-Allow-Origin*;

  3. }

HAProxy

将以下内容添加到提供资源服务的后端,并提供 JavaScript 文件:
HAProxy 代码:
  1. rspadd Access-Control-Allow-Origin:\ *

  1. 在 script 标签上设置  属性

在你的 HTML 源代码中,对于你设置的 Access-Control-Allow-Origin 头信息的每个脚本,在 script 标签上设置  。在添加脚本标记上的 crossorigin 属性之前,请确保验证上述头信息是否正确发送。在 Firefox 中,如果存在 crossorigin 属性,但 Access-Control-Allow-Origin 头信息不存在,则脚本将不会执行。

5.TypeError: Object doesn’t support property

这是你在调用未定义方法时发生在IE中的错误。你可以在IE开发者工具的控制台进行测试。

这相当于 Chrome 中的错误:”TypeError: ‘undefined’ is not a function” 。是的,对于相同的逻辑错误,不同的浏览器可能会有不同的错误消息。
在使用 JavaScript 命名空间的Web应用程序中,这中错误对于 IE 来说是一个常见问题。在这种情况下,这种问题 99.9% 是 IE 无法将当前名称空间内的方法绑定到 this 关键字。例如,如果你的 JS 命名空间 Rollbar 中有 isAwesome 方法。通常,如果你在 Rollbar 命名空间内,则可以使用以下语法调用 isAwesome 方法:
JavaScript 代码:
  1. this.isAwesome();

Chrome,Firefox 和 Opera 会欣然地接受这个语法。 IE 则不会。 因此,使用 JS 命名空间时最安全的选择是始终以实际命名空间作为前缀。
JavaScript 代码:
  1. Rollbar.isAwesome();

6.TypeError: ‘undefined’ is not a function

当你调用未定义的函数时,在 Chrome 中会发生这种错误。 你可以在 Chrome 开发者工具的控制台和 Mozilla Firefox 开发者工具的控制台中对此进行测试。

随着 JavaScript 编码技术和设计模式在这些年来越来越复杂,回调和闭包内的自引用作用域也相应增加,这是使用 this/that 混乱的一个相当常见的原因。
考虑这个示例代码片段:
JavaScript 代码:
  1. function clearBoard(){

  2. alert('Cleared');

  3. }

  4. document.addEventListener('click', function(){

  5. this.clearBoard(); // what is “this” ?

  6. });

如果你执行上面的代码然后点击页面,会导致以下错误: “Uncaught TypeError: this.clearBoard is not a function”。原因是正在执行的匿名函数在 document 上下文中, 而 clearBoard 定义在 window 中。
一个传统的,旧浏览器兼容的解决方案是简单地将你的 this 保存在一个变量,然后该变量可以被闭包继承。 例如:
JavaScript 代码:
  1. varself=this; // save reference to 'this', while it's still this!

  2. document.addEventListener('click', function(){

  3. self.clearBoard();

  4. });

或者,在较新的浏览器中,可以使用 bind() 方法传递适当的引用:
JavaScript 代码:
  1. document.addEventListener('click',this.clearBoard.bind(this));

7.Uncaught RangeError: Maximum call stack

这是 Chrome 在一些情况下会发生的错误。一个情况是当你调用一个不终止的递归函数时。你可以在Chrome开发者工具的控制台中进行测试。

如果你一个将值传递给超出范围的函数,也可能会发生这种情况。许多函数只接受其输入值的特定范围的数字。
例如,
Number.toExponential(digits) 和 Number.toFixed(digits) 接受0到20之间的数字,
和 
Number.toPrecision(digits) 接受从1到21的数字。
此外,如果您将值传递给超出范围的函数,也可能会发生这种情况。 许多函数只接受其输入值的特定范围的数字。 例如:Number.toExponential(digits) 和 Number.toFixed(digits) 接受 0 到 20 的数字,Number.toPrecision(digits) 接受 1 到 21 的数字。
JavaScript 代码:
  1. var a = newArray(4294967295); //OK

  2. var b = newArray(-1); //range error

  3. var num = 2.555555;

  4. document.writeln(num.toExponential(4)); //OK

  5. document.writeln(num.toExponential(-2)); //range error!

  6. num = 2.9999;

  7. document.writeln(num.toFixed(2)); //OK

  8. document.writeln(num.toFixed(25)); //range error!

  9. num = 2.3456;

  10. document.writeln(num.toPrecision(1)); //OK

  11. document.writeln(num.toPrecision(22)); //range error!

8.TypeError: Cannot read property ‘length’

这是 Chrome 中发生的错误,因为读取未定义变量的长度属性。你可以在Chrome开发者工具的控制台中进行测试。

你通常会在数组中找到定义的长度,但是如果数组未初始化或变量名隐藏在另一个上下文中,则可能会遇到此错误。让我们用下面的例子来理解这个错误。
JavaScript 代码:
  1. var testArray= ['Test'];

  2. function testFunction(testArray) {

  3. for(var i = 0; i < testArray.length; i++) {

  4. console.log(testArray[i]);

  5. }

  6. }

  7. testFunction();

当你用参数声明一个函数时,这些参数变成了本地参数。这意味着即使你有名称为 testArray 的变量,函数中具有相同名称的参数仍将被视为 本地参数
你有两种方法可以解决这个问题:
  1. 删除函数声明语句中的参数(事实证明如果你想访问那些在函数之外声明的变量,你不需要将其作为你函数的参数传入):

JavaScript 代码:
  1. var testArray = ['Test'];

  2. /* Precondition: defined testArray outside of a function */

  3. function testFunction(/* No params */) {

  4. for(var i = 0; i < testArray.length; i++) {

  5. console.log(testArray[i]);

  6. }

  7. }

  8. testFunction();

  1. 调用函数时,将我们声明的数组传递给它:

JavaScript 代码:
  1. var testArray = ['Test'];

  2. function testFunction(testArray) {

  3. for(var i = 0; i < testArray.length; i++) {

  4. console.log(testArray[i]);

  5. }

  6. }

  7. testFunction(testArray);

9.Uncaught TypeError: Cannot set property

当我们尝试访问一个未定义的变量时,它总是返回 undefined ,我们不能获取或设置任何 undefined 的属性。在这种情况下,应用程序将抛出 “Uncaught TypeError cannot set property of undefined.” 错误。
例如,在Chrome浏览器中:

如果 test 对象不存在,错误将会抛出 “Uncaught TypeError cannot set property of undefined.” 。

10.ReferenceError: event is not defined

当你尝试访问未定义的变量或超出当前作用域的变量时,会引发此错误。你可以在Chrome浏览器中轻松测试。

如果你在使用事件处理时遇到这种错误,请确保你使用传入的事件对象作为参数。像IE这样的老浏览器提供了一个全局变量事件, Chrome 会自动将事件变量附加到处理程序。Firefox 不会自动添加它。像jQuery这样的库试图规范化这种行为。不过,最佳实践是使用传递到事件处理程序函数的方法。
JavaScript 代码:
  1. document.addEventListener('mousemove', function(event) {

  2. console.log(event);

  3. })

总结

事实证明很多都是一些 null 或 undefined 错误。如果您使用严格的编译器选项,比如 Typescript 这样的好的静态类型检查系统可以帮助您避免它们。它可以警告你,如果一个类型是预期的,但尚未定义。
我们希望你学到了一些新的东西,并且可以避免将来出现这些错误,或者本指南帮助你解决了头痛的问题。尽管如此,即使有最佳做法,生产环境中还是会出现意想不到的错误。了解影响用户的错误非常重要,并有很好的工具来快速解决它们。
Rollbar 为你提供生产环境 JavaScript 错误的可视性,并为您提供更多上下文来快速解决它们。例如,它提供了额外的调试功能,例如 遥测功能,可以告诉你用户的浏览器发生了什么导致错误。这可以使在本地开发者工具的控制台之外发现问题。你可以在 Rollbar 的 JavaScript 应用程序的完整功能列表 中了解更多信息。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多