专栏名称: Fundebug
Fundebug为JavaScript、微信小程序及Node.js开发团队提供专业的线上代码bug监控和智能分析服务。
目录
相关文章推荐
前端大全  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
歸藏的AI工具箱  ·  终于有给设计师用的 Cursor 了 ·  2 天前  
歸藏的AI工具箱  ·  终于有给设计师用的 Cursor 了 ·  2 天前  
前端早读课  ·  【第3454期】如何用语音学习编程的 ·  2 天前  
51好读  ›  专栏  ›  Fundebug

详解1000+项目数据分析出来的10大JavaScript错误

Fundebug  · 公众号  · 前端  · 2018-03-12 09:27

正文

译者按: null/undefined引发的错误在10大错误中比例很高。而它们很可能导致严重问题,所以要重视起来。

  • 原文: Top 10 JavaScript errors from 1000+ projects (and how to avoid them)

  • 译者: Fundebug

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

为了回馈拥护我们的开发者,我们将所有项目数据分析了一下,总结出10大JavaScript错误。我们会详细解释错误的原因以及如何预防再次发生。如果你学会了避开这些坑,那么你将会是一个更加出色的开发者。

如今数据为王,我们聚合了大量BUG数据,并对它们进行分析,列出了排名前十的JavaScript错误。Rollbar收集每一个项目所有的错误,并统计它们发生的次数。我们将相同的错误聚合起来。如果同一个错误出现很多次的话,这样就可以避免像日志一样非常多,让人无从下手。

我们将统计同一个错误在多少个项目中出现,并以此来排序。如下所示:



为了方便阅读,每一条错误我们将后面的内容做了适当省略。接下来我们详细介绍每一个错误。

1. Uncaught TypeError: Cannot read property

如果你是一个JavaScript开发者,这种错误大概你已经见怪不怪了。在Chrome下,当你从一个不存在的对象(undefined)获取属性或则进行函数调用,就会报这样的错。你可以在Chrome浏览器控制台测试:



有很多种原因可以导致这种情况的出现,一个常见的情况是在渲染UI部件的时候,没有正确地初始化状态(state)。我们来看一个真实的例子。在这里我选用React,不过内在的原理同样适用于Angular、Vue或则其它框架。

class Quiz extends Component {
 componentWillMount() {
   axios.get('/thedata').then(res => {
     this.setState({items: res.data});
   });
 }

 render() {
   return (
     

           {this.state.items.map(item =>
             
  • {item.name}</li>

  •        )}
         l>
       );
     }
    }

    这里有两个关键点:

    • 组件的状态(state)( this.state )没有初始化,值为 undefined

    • 如果使用异步的方式获取数据,那么在数据加载前,该组件已经至少渲染一次。这和 componentWillMount 或则 componentDidMount 是否获取数据无关。也就是说,当Quiz第一次渲染的时候, this.state.items 是未定义的。因此,会报错: "Uncaught TypeError: Cannot read property ‘map’ of undefined"

    这个bug很容易修复。最简单的方法:在构造函数中初始化state。

    class Quiz extends Component {
    
    
    
    
        
    
     // Added this:
     constructor(props) {
       super(props);

       // Assign state itself, and a default value for items
       this.state = {
         items: []
       };
     }

     componentWillMount() {
       axios.get('/thedata').then(res => {
         this.setState({items: res.data});
       });
     }

     render() {
       return (
         

             {this.state.items.map(item =>
               
    • {item.name}</li>

    •        )}
           l>
         );
       }
      }

      也许在你的应用中会有点不一样,不够希望能够给你一些线索帮助你去修复或则避免这样的问题。如果没有,那么继续往下看吧,还有更多相关的例子等着你呢。

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

      在Safari下,如果在一个未定义(undefined)的对象上读取属性或则调用函数,就会触发这样的错误。你可以在Safari控制台测试。这个错误根本上来说和第一个在Chrome下的错误是一样的,只是错误的消息不同。



      备注:Fundebug早已机智地将这两种情况聚合为一个错误了,更加方便分析,欢迎各位老铁试用!

      3. TypeError: null is not an object (evaluating

      在Safari下,如果你尝试从null读取属性或则调用方法,就会报错。如下:



      有趣的是,在JavaScript中,null和undefined是不同的,所以我们看到两个不同的错误消息。Undefined指的是一个变量没有被赋值,而null指的是值为空。我们可以用 === 来判断:



      一种现实中可能的情况就是:如果你尝试在一个DOM元素加载之前使用它。那么DOM API就会返回null。任何处理DOM元素的JS代码都应当在DOM加载完毕之后调用。JS代码是按照代码的顺序从上往下依次解释执行。如果在DOM元素前有脚本,那么在浏览器分析HTML页面的时候,JS代码也在执行了。如果JS代码执行的时候,DOM还没有创建好,那么你会遇到这个错误。

      最常用的解法是使用事件监听,当DOM加载完毕之后,再触发JS代码的执行。


       function init() {
         var myButton = document.getElementById("myButton");
         var myTextfield = document.getElementById("myTextfield");
         myButton.onclick = function () {
           var userName = myTextfield.value;
         }
       }
       document.addEventListener('readystatechange', function() {
         if (document.readyState === "complete") {
           init();
         }
       });
      </script>


       >
       "button" id="myButton" value="Go" />

      </form>


      来自网友的备注:

      • 上面说的这个问题,是因为在html中所有资源的加载都是从上而下同步加载的,所以以前的代码规范都会有一句:”在html里css标签放上面,js标签放下面“;包括比如jQuery里的ready方法,这些做法都是为了保证js代码执行的时候,页面上的dom元素都是创建好了的。

      • 这里我再介绍一下defer和async,在外链引入js文件的情况,可以在script标签上加上defer或async修饰符,使该js能够异步加载,从而解决上面遇到的问题。async表示后续的解析任务和当前js标签的加载任务并行执行,defer表示该js标签的代码会在所有页面元素解析完成之后,DOMContentLoaded 事件触发之前执行。两者具体区别参考:https://segmentfault.com/q/1010000000640869。

      4. (unknown): Script error

      当未捕获的 JavaScript 错误(通过window.onerror处理程序引发的错误,而不是捕获在try-catch中)被浏览器的跨域策略限制时,会产生这类的脚本错误。 例如,如果您将您的 JavaScript 代码托管在 CDN 上,则任何未被捕获的错误将被报告为“脚本错误” 而不是包含有用的堆栈信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则将不允许进行通信。

      想要获取到真实详细的错误信息,你可以像这样做:

      1. 在header里添加 Access-Control-Allow-Origin 字段
        在header(这应该是服务器返回的response header)字段里,把Access-Control-Allow-Origin设为,这样就表示来自任意的域名请求都可以正确地访问到服务器的资源。必要的话也可以指定具体的域名来代替星号,比如:Access-Control-Allow-Origin: www.example.com。但是配置的域名太多的话,处理起来会有点棘手,而且如果你在使用CDN的话还会出现缓存的问题,这样就有点费力不讨好了。更多参考这里。

        下面举一些在各种环境下配置这个header的示例:

      • Apache
        在JavaScript代码所在的文件夹目录下,新建一个.htaccess文件,内容如下:

        Header add Access-Control-Allow-Origin "*"
      • Nginx
        在JavaScript代码所在文件夹目录下面,添加add_header命令:

        location ~ ^/assets/ {
         add_header Access-Control-Allow-Origin *;
        }
      • HAProxy
        在后端的JavaScript所在文件加入以下内容:

        rspadd Access-Control-Allow-Origin:\ *
    • 在JavaScript标签上设置crossorigin=”anonymous”
      在html代码里,每个设置好了Access-Control-Allow-Origin的js资源,都可以在其JavaScript标签上添加crossorigin=”anonymous”。在设置crossorigin=”anonymous”之前,确定好header字段都是正确发送了的。在Firefox里,如果js标签上出现了crossorigin属性,但是header里没有Access-Control-Allow-Origin,那么该js将不会被执行。(crossorigin是html5新增的功能,不只是JavaScript标签独有的,比如video、image也可以设置)

    • 5. TypeError: Object doesn’t support property

      在IE中,如果调用未定义的方法就会发生这种错误。您可以在IE开发者控制台中进行测试。



      相当于 Chrome 中的 “TypeError:”undefined“ is not a function” 错误。 对于相同的错误,不同的浏览器具有不同的错误消息。

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

      this.isAwesome();

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

      Rollbar.isAwesome();

      6. TypeError: ‘undefined’ is not a function

      在Chrome下,调用一个未定义的函数时就会发生这个错误,可以在Chrome/Mozilla开发者控制台测试:



      随着js代码的编码技巧和设计模式越来越复杂,在回调函数、闭包等各种作用域中this的指向的层级也随之增加,这就是js代码中this/that指向容易混淆的原因。

      比如下面这段代码:

      function testFunction() {
      this.clearLocalStorage();
      this.timer = setTimeout(function() {
       this.clearBoard();  // 这里的”this"是指什么?
      }, 0);
      };

      执行上面的代码会报错:“Uncaught TypeError: undefined is not a function”。因为在调用setTimeout()方法时,实际上是在调用window.setTimeout()。传给setTimeout()的匿名函数的this实际上是window,而window并不包含clearBoard()方法。

      一个最简单的、能兼容旧版本浏览器的方法,就是先把this指向赋值给一个变量self,然后在闭包里直接引用这个self变量。例如:

      function testFunction () {
      this.clearLocalStorage();
      var self = this;  // 将this赋值给self
      this.timer = setTimeout(function(){
       self.clearBoard();  
      }, 0);
      };

      也可以使用bind方法来传递this:

      function testFunction () {
       this.clearLocalStorage();
       this.timer = setTimeout(this.reset.bind(this), 0);  // bind to 'this'
      };

      function testFunction(){
         this.clearBoard();    //back in the context of the right 'this'!
      };

      7. Uncaught RangeError: Maximum call stack

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



      还有,如果传给函数的值超出可接受的范围时,也会出现这个错误。很多函数只接受指定范围的数值,例如,Number.toExponential(digits)和Number.toFixed(digits)方法,只接受0到20的数值,而Number.toPrecision(digits)只接受1到21的数值。

      var a = new Array(4294967295);  //OK
      var b = new Array(-1); //range error

      var num = 2.555555;
      document.writeln(num.toExponential(4));  //OK
      document.writeln(num.toExponential(-2)); //range error!

      num = 2.9999;
      document.writeln(num.toFixed(2));   //OK
      document.writeln(num.toFixed(25));  //range error!

      num = 2.3456;
      document.writeln(num.toPrecision(1));   //OK
      document






      请到「今天看啥」查看全文