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