摘要:當(dāng)未捕獲的錯誤通過處理程序引發(fā)的錯誤,而不是捕獲在中被瀏覽器的跨域策略限制時,會產(chǎn)生這類的腳本錯誤。例如,如果您將您的代碼托管在上,則任何未被捕獲的錯誤將被報告為腳本錯誤而不是包含有用的堆棧信息。
譯者按: null/undefined引發(fā)的錯誤在10大錯誤中比例很高。而它們很可能導(dǎo)致嚴重問題,所以要重視起來。
原文: Top 10 JavaScript errors from 1000+ projects (and how to avoid them)
譯者: Fundebug
為了保證可讀性,本文采用意譯而非直譯。另外,本文版權(quán)歸原作者所有,翻譯僅用于學(xué)習(xí)。
為了回饋擁護我們的開發(fā)者,我們將所有項目數(shù)據(jù)分析了一下,總結(jié)出10大JavaScript錯誤。我們會詳細解釋錯誤的原因以及如何預(yù)防再次發(fā)生。如果你學(xué)會了避開這些坑,那么你將會是一個更加出色的開發(fā)者。
如今數(shù)據(jù)為王,我們聚合了大量BUG數(shù)據(jù),并對它們進行分析,列出了排名前十的JavaScript錯誤。Rollbar收集每一個項目所有的錯誤,并統(tǒng)計它們發(fā)生的次數(shù)。我們將相同的錯誤聚合起來。如果同一個錯誤出現(xiàn)很多次的話,這樣就可以避免像日志一樣非常多,讓人無從下手。
我們將統(tǒng)計同一個錯誤在多少個項目中出現(xiàn),并以此來排序。如下所示:
為了方便閱讀,每一條錯誤我們將后面的內(nèi)容做了適當(dāng)省略。接下來我們詳細介紹每一個錯誤。
1. Uncaught TypeError: Cannot read property如果你是一個JavaScript開發(fā)者,這種錯誤大概你已經(jīng)見怪不怪了。在Chrome下,當(dāng)你從一個不存在的對象(undefined)獲取屬性或則進行函數(shù)調(diào)用,就會報這樣的錯。你可以在Chrome瀏覽器控制臺測試:
有很多種原因可以導(dǎo)致這種情況的出現(xiàn),一個常見的情況是在渲染UI部件的時候,沒有正確地初始化狀態(tài)(state)。我們來看一個真實的例子。在這里我選用React,不過內(nèi)在的原理同樣適用于Angular、Vue或則其它框架。
class Quiz extends Component { componentWillMount() { axios.get("/thedata").then(res => { this.setState({items: res.data}); }); } render() { return (
這里有兩個關(guān)鍵點:
組件的狀態(tài)(state)(this.state)沒有初始化,值為undefined。
如果使用異步的方式獲取數(shù)據(jù),那么在數(shù)據(jù)加載前,該組件已經(jīng)至少渲染一次。這和componentWillMount或則componentDidMount是否獲取數(shù)據(jù)無關(guān)。也就是說,當(dāng)Quiz第一次渲染的時候,this.state.items是未定義的。因此,會報錯:"Uncaught TypeError: Cannot read property ‘map’ of undefined" 。
這個bug很容易修復(fù)。最簡單的方法:在構(gòu)造函數(shù)中初始化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 (
也許在你的應(yīng)用中會有點不一樣,不夠希望能夠給你一些線索幫助你去修復(fù)或則避免這樣的問題。如果沒有,那么繼續(xù)往下看吧,還有更多相關(guān)的例子等著你呢。
2. TypeError: ‘undefined’ is not an object (evaluating在Safari下,如果在一個未定義(undefined)的對象上讀取屬性或則調(diào)用函數(shù),就會觸發(fā)這樣的錯誤。你可以在Safari控制臺測試。這個錯誤根本上來說和第一個在Chrome下的錯誤是一樣的,只是錯誤的消息不同。
備注:Fundebug早已機智地將這兩種情況聚合為一個錯誤了,更加方便分析,歡迎各位老鐵試用!
3. TypeError: null is not an object (evaluating在Safari下,如果你嘗試從null讀取屬性或則調(diào)用方法,就會報錯。如下:
有趣的是,在JavaScript中,null和undefined是不同的,所以我們看到兩個不同的錯誤消息。Undefined指的是一個變量沒有被賦值,而null指的是值為空。我們可以用===來判斷:
一種現(xiàn)實中可能的情況就是:如果你嘗試在一個DOM元素加載之前使用它。那么DOM API就會返回null。任何處理DOM元素的JS代碼都應(yīng)當(dāng)在DOM加載完畢之后調(diào)用。JS代碼是按照代碼的順序從上往下依次解釋執(zhí)行。如果在DOM元素前有腳本,那么在瀏覽器分析HTML頁面的時候,JS代碼也在執(zhí)行了。如果JS代碼執(zhí)行的時候,DOM還沒有創(chuàng)建好,那么你會遇到這個錯誤。
最常用的解法是使用事件監(jiān)聽,當(dāng)DOM加載完畢之后,再觸發(fā)JS代碼的執(zhí)行。
來自網(wǎng)友的備注:
上面說的這個問題,是因為在html中所有資源的加載都是從上而下同步加載的,所以以前的代碼規(guī)范都會有一句:”在html里css標(biāo)簽放上面,js標(biāo)簽放下面“;包括比如jQuery里的ready方法,這些做法都是為了保證js代碼執(zhí)行的時候,頁面上的dom元素都是創(chuàng)建好了的。
這里我再介紹一下defer和async,在外鏈引入js文件的情況,可以在script標(biāo)簽上加上defer或async修飾符,使該js能夠異步加載,從而解決上面遇到的問題。async表示后續(xù)的解析任務(wù)和當(dāng)前js標(biāo)簽的加載任務(wù)并行執(zhí)行,defer表示該js標(biāo)簽的代碼會在所有頁面元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前執(zhí)行。兩者具體區(qū)別參考:https://segmentfault.com/q/1010000000640869。
4. (unknown): Script error當(dāng)未捕獲的 JavaScript 錯誤(通過window.onerror處理程序引發(fā)的錯誤,而不是捕獲在try-catch中)被瀏覽器的跨域策略限制時,會產(chǎn)生這類的腳本錯誤。 例如,如果您將您的 JavaScript 代碼托管在 CDN 上,則任何未被捕獲的錯誤將被報告為“腳本錯誤” 而不是包含有用的堆棧信息。這是一種瀏覽器安全措施,旨在防止跨域傳遞數(shù)據(jù),否則將不允許進行通信。
想要獲取到真實詳細的錯誤信息,你可以像這樣做:
在header里添加 Access-Control-Allow-Origin 字段
在header(這應(yīng)該是服務(wù)器返回的response header)字段里,把Access-Control-Allow-Origin設(shè)為,這樣就表示來自任意的域名請求都可以正確地訪問到服務(wù)器的資源。必要的話也可以指定具體的域名來代替星號,比如:Access-Control-Allow-Origin: www.example.com。但是配置的域名太多的話,處理起來會有點棘手,而且如果你在使用CDN的話還會出現(xiàn)緩存的問題,這樣就有點費力不討好了。更多參考這里。
下面舉一些在各種環(huán)境下配置這個header的示例:
Apache
在JavaScript代碼所在的文件夾目錄下,新建一個.htaccess文件,內(nèi)容如下:
Header add Access-Control-Allow-Origin "*"
Nginx
在JavaScript代碼所在文件夾目錄下面,添加add_header命令:
location ~ ^/assets/ { add_header Access-Control-Allow-Origin *; }```
HAProxy
在后端的JavaScript所在文件加入以下內(nèi)容:
rspadd Access-Control-Allow-Origin: *
在JavaScript標(biāo)簽上設(shè)置crossorigin="anonymous"
在html代碼里,每個設(shè)置好了Access-Control-Allow-Origin的js資源,都可以在其JavaScript標(biāo)簽上添加crossorigin="anonymous"。在設(shè)置crossorigin="anonymous"之前,確定好header字段都是正確發(fā)送了的。在Firefox里,如果js標(biāo)簽上出現(xiàn)了crossorigin屬性,但是header里沒有Access-Control-Allow-Origin,那么該js將不會被執(zhí)行。(crossorigin是html5新增的功能,不只是JavaScript標(biāo)簽獨有的,比如video、image也可以設(shè)置)
在IE中,如果調(diào)用未定義的方法就會發(fā)生這種錯誤。您可以在IE開發(fā)者控制臺中進行測試。
相當(dāng)于 Chrome 中的 “TypeError:”undefined“ is not a function” 錯誤。 對于相同的錯誤,不同的瀏覽器具有不同的錯誤消息。
在IE里使用JavaScript的命名空間時,就很容易碰到這個錯誤。發(fā)生這個錯誤十有八九是因為IE無法將當(dāng)前命名空間里的方法綁定到this關(guān)鍵字上。例如,假設(shè)有個命名空間Rollbar,它有一個方法叫isAwesome()。在Rollbar命名空間中,可以直接使用this關(guān)鍵字來調(diào)用這個方法:
this.isAwesome();
在Chrome、Firefox和Opera中這樣做都是沒有問題的,但在IE中就不行。所以,最安全的做法是指定全命名空間:
Rollbar.isAwesome();6. TypeError: ‘undefined’ is not a function
在Chrome下,調(diào)用一個未定義的函數(shù)時就會發(fā)生這個錯誤,可以在Chrome/Mozilla開發(fā)者控制臺測試:
隨著js代碼的編碼技巧和設(shè)計模式越來越復(fù)雜,在回調(diào)函數(shù)、閉包等各種作用域中this的指向的層級也隨之增加,這就是js代碼中this/that指向容易混淆的原因。
比如下面這段代碼:
function testFunction() { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); // 這里的”this"是指什么? }, 0); };
執(zhí)行上面的代碼會報錯:“Uncaught TypeError: undefined is not a function”。因為在調(diào)用setTimeout()方法時,實際上是在調(diào)用window.setTimeout()。傳給setTimeout()的匿名函數(shù)的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里,有幾種情況會發(fā)生這個錯誤,其中一個就是函數(shù)的遞歸調(diào)用,并且不能終止。這個錯誤可以在Chrome開發(fā)者控制臺重現(xiàn)。
還有,如果傳給函數(shù)的值超出可接受的范圍時,也會出現(xiàn)這個錯誤。很多函數(shù)只接受指定范圍的數(shù)值,例如,Number.toExponential(digits)和Number.toFixed(digits)方法,只接受0到20的數(shù)值,而Number.toPrecision(digits)只接受1到21的數(shù)值。
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!
來自網(wǎng)友的備注:
我在chorme測試時,發(fā)現(xiàn)上述的第二種參數(shù)超出范圍的情況,錯誤信息并不是”Maximum call stack“,并且Number.toExponential(digits) 和 Number.toFixed(digits)方法,接收的范圍應(yīng)該是0到100
另外,如果遞歸層數(shù)太多,會導(dǎo)致內(nèi)存溢出。那么如何防止呢?可以尾調(diào)用優(yōu)化,函數(shù)結(jié)尾改成尾遞歸,具體內(nèi)容參考這里,文中提到的一個觀念就是使用尾遞歸來避免棧溢出,遺憾的是目前js還是無法支持"尾調(diào)用優(yōu)化"。
8. TypeError: Cannot read property ‘length’在Chrome中,如果讀取未定義變量的長度屬性,會報錯。
如果數(shù)組未初始化,或者因為作用域的問題而沒有正確地獲取到,則可能會遇到此錯誤。讓我們用下面的例子來理解這個錯誤。
var testArray= ["Test"]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction();
函數(shù)的參數(shù)名會覆蓋全局的變量名。也就是說,全局的testArray被函數(shù)的參數(shù)名覆蓋了,所以在函數(shù)體里訪問到的是本地的testArray,但本地并沒有定義testArray,所以出現(xiàn)了這個錯誤。
有兩種方法可用于解決這個問題:
將函數(shù)的參數(shù)移除
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();
把外部的變量傳給函數(shù)testFunction函數(shù)
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”異常。
因為test對象不存在,就會拋出“Uncaught TypeError: cannot set property of undefined”異常。
10. ReferenceError: event is not defined當(dāng)訪問一個未定義的對象或超出當(dāng)前作用域的對象,就會發(fā)生這個錯誤。
如果在使用事件處理系統(tǒng)時遇到此錯誤,請確保使用傳入的事件對象作為參數(shù)。舊瀏覽器(IE)提供了全局的event變量,但并不是所有的瀏覽器都支持。像jQuery這樣的庫試圖規(guī)范化這種行為。盡管如此,最好使用傳入事件處理函數(shù)的函數(shù)。
function myFunction(event) { event = event.which || event.keyCode; if(event.keyCode===13){ alert(event.keyCode); } }結(jié)論
看到這里,你會發(fā)現(xiàn)這十大錯誤幾乎都是null/undefined錯誤。如果有一個好的靜態(tài)類型檢查系統(tǒng),比如使用TypeScript可以幫助你在編譯的時候就發(fā)現(xiàn)問題。如果沒有使用TypeScript,那么請多多使用條件語句做判斷,防止這種情況出現(xiàn)。
在生產(chǎn)環(huán)境中會出現(xiàn)各種不可預(yù)期的錯誤。關(guān)鍵是要及時發(fā)現(xiàn)那些影響用戶體驗的錯誤,并使用適當(dāng)?shù)墓ぞ呖焖侔l(fā)現(xiàn)和解決這些問題。Fundebug提供網(wǎng)站bug監(jiān)控,助你實時發(fā)現(xiàn)bug。
版權(quán)聲明:
轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/03/12/top-10-javascript-errors-from-1000-projects/
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93641.html
摘要:插入迭代器如前面兩條語句可以寫成使用直接量替換為替換為替換為如果要創(chuàng)建具有一些特性的一般對象,也可以使用字面量,如下前面的代碼可用對象字面量來改寫成這樣使用優(yōu)化多次一旦需要更新請考慮使用文檔碎片來構(gòu)建結(jié)構(gòu),然后再將其添加到現(xiàn)存的文檔中。 好贊,收藏自 總結(jié)的js性能優(yōu)化方面的小知識(不喜勿噴) 前言 一直在學(xué)習(xí)javascript,也有看過《犀利開發(fā)Jquery內(nèi)核詳解與實踐》,對...
摘要:前言在大廠工作了年,當(dāng)了年的前端面試官,把大廠常問的面試題與答案匯總在我的中。第題如何劫持的請求,提供思路難度阿里騰訊很多人在上搜索前端面試詳解,把答案倒背如流,但是問到如何劫持請求的時候就一臉懵逼,是因為還是停留在理論性階段。前言 在大廠工作了6年,當(dāng)了3年的前端面試官,把大廠常問的面試題與答案匯總在我的Github中。希望對大家有所幫助,助力大家進入自己理想的企業(yè)。 項目地址是:git...
摘要:前言在大廠工作了年,當(dāng)了年的前端面試官,把大廠常問的面試題與答案匯總在我的中。第題如何劫持的請求,提供思路難度阿里騰訊很多人在上搜索前端面試詳解,把答案倒背如流,但是問到如何劫持請求的時候就一臉懵逼,是因為還是停留在理論性階段。 前言 在大廠工作了6年,當(dāng)了3年的前端面試官,把大廠常問的面試題與答案匯總在我的Github中。希望對大家有所幫助,助力大家進入自己理想的企業(yè)。 項目地址是:...
摘要:前言在大廠工作了年,當(dāng)了年的前端面試官,把大廠常問的面試題與答案匯總在我的中。第題如何劫持的請求,提供思路難度阿里騰訊很多人在上搜索前端面試詳解,把答案倒背如流,但是問到如何劫持請求的時候就一臉懵逼,是因為還是停留在理論性階段。 前言 在大廠工作了6年,當(dāng)了3年的前端面試官,把大廠常問的面試題與答案匯總在我的Github中。希望對大家有所幫助,助力大家進入自己理想的企業(yè)。 項目地址是:...
閱讀 3558·2021-11-22 15:11
閱讀 4634·2021-11-18 13:15
閱讀 2702·2019-08-29 14:08
閱讀 3576·2019-08-26 13:49
閱讀 3090·2019-08-26 12:17
閱讀 3287·2019-08-26 11:54
閱讀 3111·2019-08-26 10:58
閱讀 2030·2019-08-26 10:21