詳解1000+項目數據分析出來的10大JavaScript錯誤

譯者按: null/undefined引起的錯誤在10大錯誤中比例很高。而它們極可能致使嚴重問題,因此要重視起來。javascript

爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。css

爲了回饋擁護咱們的開發者,咱們將全部項目數據分析了一下,總結出10大JavaScript錯誤。咱們會詳細解釋錯誤的緣由以及如何預防再次發生。若是你學會了避開這些坑,那麼你將會是一個更加出色的開發者。html

現在數據爲王,咱們聚合了大量BUG數據,並對它們進行分析,列出了排名前十的JavaScript錯誤。Rollbar收集每個項目全部的錯誤,並統計它們發生的次數。咱們將相同的錯誤聚合起來。若是同一個錯誤出現不少次的話,這樣就能夠避免像日誌同樣很是多,讓人無從下手。html5

咱們將統計同一個錯誤在多少個項目中出現,並以此來排序。以下所示:java

clipboard.png

爲了方便閱讀,每一條錯誤咱們將後面的內容作了適當省略。接下來咱們詳細介紹每個錯誤。ios

1. Uncaught TypeError: Cannot read property

若是你是一個JavaScript開發者,這種錯誤大概你已經見怪不怪了。在Chrome下,當你從一個不存在的對象(undefined)獲取屬性或則進行函數調用,就會報這樣的錯。你能夠在Chrome瀏覽器控制檯測試:git

clipboard.png

有不少種緣由能夠致使這種狀況的出現,一個常見的狀況是在渲染UI部件的時候,沒有正確地初始化狀態(state)。咱們來看一個真實的例子。在這裏我選用React,不過內在的原理一樣適用於Angular、Vue或則其它框架。axios

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

  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

這裏有兩個關鍵點:segmentfault

  • 組件的狀態(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 (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

也許在你的應用中會有點不同,不夠但願可以給你一些線索幫助你去修復或則避免這樣的問題。若是沒有,那麼繼續往下看吧,還有更多相關的例子等着你呢。

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

在Safari下,若是在一個未定義(undefined)的對象上讀取屬性或則調用函數,就會觸發這樣的錯誤。你能夠在Safari控制檯測試。這個錯誤根本上來講和第一個在Chrome下的錯誤是同樣的,只是錯誤的消息不一樣。

clipboard.png

備註:Fundebug早已機智地將這兩種狀況聚合爲一個錯誤了,更加方便分析,歡迎各位老鐵試用!

3. TypeError: null is not an object (evaluating

在Safari下,若是你嘗試從null讀取屬性或則調用方法,就會報錯。以下:

clipboard.png

有趣的是,在JavaScript中,null和undefined是不一樣的,因此咱們看到兩個不一樣的錯誤消息。Undefined指的是一個變量沒有被賦值,而null指的是值爲空。咱們能夠用===來判斷:

clipboard.png

一種現實中可能的狀況就是:若是你嘗試在一個DOM元素加載以前使用它。那麼DOM API就會返回null。任何處理DOM元素的JS代碼都應當在DOM加載完畢以後調用。JS代碼是按照代碼的順序從上往下依次解釋執行。若是在DOM元素前有腳本,那麼在瀏覽器分析HTML頁面的時候,JS代碼也在執行了。若是JS代碼執行的時候,DOM尚未建立好,那麼你會遇到這個錯誤。

最經常使用的解法是使用事件監聽,當DOM加載完畢以後,再觸發JS代碼的執行。

<script>
  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>

<form>
  <input type="text" id="myTextfield" placeholder="Type your name" />
  <input type="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:\ *
  2. 在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開發者控制檯中進行測試。

clipboard.png

至關於 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開發者控制檯測試:

clipboard.png

隨着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開發者控制檯重現。

clipboard.png

還有,若是傳給函數的值超出可接受的範圍時,也會出現這個錯誤。不少函數只接受指定範圍的數值,例如,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.writeln(num.toPrecision(22));  //range error!

來自網友的備註

  • 我在chorme測試時,發現上述的第二種參數超出範圍的狀況,錯誤信息並非」Maximum call stack「,而且Number.toExponential(digits) 和 Number.toFixed(digits)方法,接收的範圍應該是0到100
    clipboard.png
  • 另外,若是遞歸層數太多,會致使內存溢出。那麼如何防止呢?能夠尾調用優化,函數結尾改爲尾遞歸,具體內容參考這裏,文中提到的一個觀念就是使用尾遞歸來避免棧溢出,遺憾的是目前js仍是沒法支持"尾調用優化"。

8. TypeError: Cannot read property ‘length’

在Chrome中,若是讀取未定義變量的長度屬性,會報錯。

clipboard.png

若是數組未初始化,或者由於做用域的問題而沒有正確地獲取到,則可能會遇到此錯誤。讓咱們用下面的例子來理解這個錯誤。

var testArray= ["Test"];

function testFunction(testArray) {
    for (var i = 0; i < testArray.length; i++) {
      console.log(testArray[i]);
    }
}

testFunction();

函數的參數名會覆蓋全局的變量名。也就是說,全局的testArray被函數的參數名覆蓋了,因此在函數體裏訪問到的是本地的testArray,但本地並無定義testArray,因此出現了這個錯誤。

有兩種方法可用於解決這個問題:

  1. 將函數的參數移除

    var testArray = ["Test"];
    
    /* Precondition: defined testArray outside of a function */
    function testFunction(/* No params */) {
        for (var i = 0; i < testArray.length; i++) {
          console.log(testArray[i]);
        }
    }
    
    testFunction();
  2. 把外部的變量傳給函數testFunction函數

    var testArray = ["Test"];
    function testFunction(testArray) {
        for (var i = 0; i < testArray.length; i++) {
          console.log(testArray[i]);
        }
    }
    testFunction(testArray);

9. Uncaught TypeError: Cannot set property

若是對undefined變量進行賦值或讀取操做,會拋出「Uncaught TypeError: cannot set property of undefined」異常。

clipboard.png

由於test對象不存在,就會拋出「Uncaught TypeError: cannot set property of undefined」異常。

10. ReferenceError: event is not defined

當訪問一個未定義的對象或超出當前做用域的對象,就會發生這個錯誤。

clipboard.png

若是在使用事件處理系統時遇到此錯誤,請確保使用傳入的事件對象做爲參數。舊瀏覽器(IE)提供了全局的event變量,但並非全部的瀏覽器都支持。像jQuery這樣的庫試圖規範化這種行爲。儘管如此,最好使用傳入事件處理函數的函數。

function myFunction(event) {
    event = event.which || event.keyCode;
    if(event.keyCode===13){
       alert(event.keyCode);
    }
}

結論

看到這裏,你會發現這十大錯誤幾乎都是null/undefined錯誤。若是有一個好的靜態類型檢查系統,好比使用TypeScript能夠幫助你在編譯的時候就發現問題。若是沒有使用TypeScript,那麼請多多使用條件語句作判斷,防止這種狀況出現。

在生產環境中會出現各類不可預期的錯誤。關鍵是要及時發現那些影響用戶體驗的錯誤,並使用適當的工具快速發現和解決這些問題。Fundebug提供網站bug監控,助你實時發現bug。

clipboard.png

版權聲明:
轉載時請註明做者Fundebug以及本文地址:
https://blog.fundebug.com/2018/03/12/top-10-javascript-errors-from-1000-projects/

相關文章
相關標籤/搜索