譯者按: null/undefined引起的錯誤在10大錯誤中比例很高。而它們極可能致使嚴重問題,因此要重視起來。javascript
爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。css
爲了回饋擁護咱們的開發者,咱們將全部項目數據分析了一下,總結出10大JavaScript錯誤。咱們會詳細解釋錯誤的緣由以及如何預防再次發生。若是你學會了避開這些坑,那麼你將會是一個更加出色的開發者。html
現在數據爲王,咱們聚合了大量BUG數據,並對它們進行分析,列出了排名前十的JavaScript錯誤。Rollbar收集每個項目全部的錯誤,並統計它們發生的次數。咱們將相同的錯誤聚合起來。若是同一個錯誤出現不少次的話,這樣就能夠避免像日誌同樣很是多,讓人無從下手。html5
咱們將統計同一個錯誤在多少個項目中出現,並以此來排序。以下所示:java
爲了方便閱讀,每一條錯誤咱們將後面的內容作了適當省略。接下來咱們詳細介紹每個錯誤。ios
若是你是一個JavaScript開發者,這種錯誤大概你已經見怪不怪了。在Chrome下,當你從一個不存在的對象(undefined)獲取屬性或則進行函數調用,就會報這樣的錯。你能夠在Chrome瀏覽器控制檯測試:git
有不少種緣由能夠致使這種狀況的出現,一個常見的狀況是在渲染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
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> ); } }
也許在你的應用中會有點不同,不夠但願可以給你一些線索幫助你去修復或則避免這樣的問題。若是沒有,那麼繼續往下看吧,還有更多相關的例子等着你呢。
在Safari下,若是在一個未定義(undefined)的對象上讀取屬性或則調用函數,就會觸發這樣的錯誤。你能夠在Safari控制檯測試。這個錯誤根本上來講和第一個在Chrome下的錯誤是同樣的,只是錯誤的消息不一樣。
備註:Fundebug早已機智地將這兩種狀況聚合爲一個錯誤了,更加方便分析,歡迎各位老鐵試用!
在Safari下,若是你嘗試從null讀取屬性或則調用方法,就會報錯。以下:
有趣的是,在JavaScript中,null和undefined是不一樣的,因此咱們看到兩個不一樣的錯誤消息。Undefined指的是一個變量沒有被賦值,而null指的是值爲空。咱們能夠用===
來判斷:
一種現實中可能的狀況就是:若是你嘗試在一個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>
來自網友的備註:
當未捕獲的 JavaScript 錯誤(經過window.onerror處理程序引起的錯誤,而不是捕獲在try-catch中)被瀏覽器的跨域策略限制時,會產生這類的腳本錯誤。 例如,若是您將您的 JavaScript 代碼託管在 CDN 上,則任何未被捕獲的錯誤將被報告爲「腳本錯誤」 而不是包含有用的堆棧信息。這是一種瀏覽器安全措施,旨在防止跨域傳遞數據,不然將不容許進行通訊。
想要獲取到真實詳細的錯誤信息,你能夠像這樣作:
在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:\ *
在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();
在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'! };
在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.writeln(num.toPrecision(22)); //range error!
來自網友的備註:
在Chrome中,若是讀取未定義變量的長度屬性,會報錯。
若是數組未初始化,或者由於做用域的問題而沒有正確地獲取到,則可能會遇到此錯誤。讓咱們用下面的例子來理解這個錯誤。
var testArray= ["Test"]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction();
函數的參數名會覆蓋全局的變量名。也就是說,全局的testArray被函數的參數名覆蓋了,因此在函數體裏訪問到的是本地的testArray,但本地並無定義testArray,因此出現了這個錯誤。
有兩種方法可用於解決這個問題:
將函數的參數移除
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();
把外部的變量傳給函數testFunction
函數
var testArray = ["Test"]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction(testArray);
若是對undefined變量進行賦值或讀取操做,會拋出「Uncaught TypeError: cannot set property of undefined」異常。
由於test對象不存在,就會拋出「Uncaught TypeError: cannot set property of undefined」異常。
當訪問一個未定義的對象或超出當前做用域的對象,就會發生這個錯誤。
若是在使用事件處理系統時遇到此錯誤,請確保使用傳入的事件對象做爲參數。舊瀏覽器(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。
版權聲明:
轉載時請註明做者Fundebug以及本文地址:
https://blog.fundebug.com/2018/03/12/top-10-javascript-errors-from-1000-projects/