為了回饋我們的開發人員社區,我們查看了包含數千個項目的數據庫,並發現了JavaScript中的前10大錯誤。我將向你展示導致它們的原因以及如何防止它們發生。如果你避免這些“陷阱”,它將使你成為更好的開發人員。
由於數據為王,因此我們收集、分析並排名了前10個JavaScript錯誤。Rollbar收集每個項目的所有錯誤,並總結每個項目發生了多少次,我們通過根據錯誤的指紋對錯誤進行分組來實現。
我們集中於最有可能影響你和你的用戶的錯誤。為此,我們根據在不同公司中遇到錯誤的項目數量來對錯誤進行排名。
以下是十大JavaScript錯誤:
為了便於閱讀,每個錯誤都被縮短了,讓我們更深入地研究每一個問題,以確定是什麼導致了這些問題,以及如何避免產生這些問題。
1. Uncaught TypeError: Cannot read property
如果你是一個JavaScript開發人員,你可能已經看到過這個錯誤。當你讀取屬性或在未定義對象上調用方法時,Chrome中就會發生這種情況。你可以在Chrome開發者控制檯中輕鬆進行測試。
發生這種情況的原因有很多,但常見的原因是渲染UI組件時狀態初始化不當。讓我們來看一個在現實應用中如何發生這種情況的示例。我們將選擇React,但是不正確初始化的相同原理也適用於Angular,Vue或任何其他框架。
<code>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
> ); } }/<code>
這裡有兩件重要的事情要意識到:
- 組件的狀態(例如 this.state)以 undefined 狀態開始使用。
- 當你異步獲取數據時,無論數據是在構造函數 componentWillMount 還是 componentDidMount 中獲取,組件都將在數據加載之前至少渲染一次。當Quiz第一次渲染時,this.state.items 是 undefined。這反過來又意味著ItemList會得到未定義的items,你會在控制檯中得到一個錯誤——"UncaughtTypeError: Cannot read property 'map' of undefined "的錯誤。
這很容易解決,最簡單的方法:在構造函數中使用合理的默認值初始化狀態。
<code>class
Quiz
extends
Component
{constructor
(props) {super
(props);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
> ); } }/<code>
你的應用程序中的實際代碼可能會有不同,但我希望已經給了你足夠的線索,讓你在你的應用程序中修復或避免這個問題。如果沒有,請繼續閱讀,因為我將在下面介紹有關相關錯誤的更多示例。
2. TypeError: ‘undefined’ is not an object (evaluating
這是在Safari中讀取屬性或調用undefined對象上的方法時發生的錯誤,你可以在Safari開發者控制檯中非常輕鬆地進行測試。這基本上與上述針對Chrome的錯誤相同,但Safari使用了不同的錯誤消息。
3. TypeError: null is not an object (evaluating
這是在Safari中讀取屬性或調用null對象上的方法時發生的錯誤,你可以在Safari開發者控制檯中非常輕鬆地進行測試。
有趣的是,在JavaScript中,null和undefined不相同,這就是為什麼我們看到兩個不同的錯誤消息的原因。undefined通常是尚未分配的變量,而null表示該值為空白。要驗證它們是否相等,請嘗試使用嚴格相等運算符。
在實際示例中可能發生這種錯誤的一種方式是,在加載元素之前嘗試在JavaScript中使用DOM元素,這是因為DOM API對於空白的對象引用返回null。
任何執行和處理DOM元素的JS代碼都應在創建DOM元素後執行。JS代碼按照HTML格式從上到下進行解釋,所以,如果在DOM元素之前有一個標籤,那麼在瀏覽器解析HTML頁面的時候,腳本標籤內的JS代碼就會被執行。如果在加載腳本之前尚未創建DOM元素,則會出現此錯誤。
在此示例中,我們可以通過添加事件偵聽器來解決該問題,該事件偵聽器將在頁面準備就緒時通知我們。一旦觸發了 addEventListener,init() 方法就可以使用DOM元素。
<code><
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
>/<code>
4. (unknown): Script error
當未捕獲的JavaScript錯誤違反跨源策略跨越域邊界時,將發生腳本錯誤。例如,如果你將你的JavaScript代碼託管在CDN上,任何未被捕獲的錯誤(冒泡到window.onerror處理程序中的錯誤,而不是在try-catch中被捕獲的錯誤)都會被報告為 "Script error",而不是包含有用的信息。這是一種瀏覽器安全措施,旨在防止跨域傳遞數據,否則該域將無法通信。
要獲取真實的錯誤消息,請執行以下操作。
發送Access-Control-Allow-Origin標頭
將 Access-Control-Allow-Origin 標頭設置為 * 表示可以從任何域正確訪問資源。你可以根據需要將 * 替換為您的域:例如,Access-Control-Allow-Origin:www.example.com。但是,處理多個域比較複雜,如果使用CDN可能會出現緩存問題,那麼可能不值得花費精力。
以下是一些有關如何在各種環境中設置此標頭的示例:
- Apache
在將提供JavaScript文件的文件夾中,創建一個具有以下內容的 .htaccess 文件:
<code>Header
add Access-Control-Allow-Origin"*"
/<code>
- Nginx
將add_header指令添加到提供JavaScript文件的location塊中:
<code>location
~ ^/assets/
{add_header
Access-Control-Allow-Origin *; }/<code>
- HAProxy
將以下內容添加到提供JavaScript文件的asset後端:
<code>rspadd
Access-Control-Allow-Origin:\ */<code>
在腳本標籤上設置crossorigin =“ anonymous”
在你的HTML源代碼中,對於您設置了 Access-Control-Allow-Origin 標頭的每個腳本,在script標記上設置crossorigin="anonymous"。在script標記上添加 crossorigin 屬性之前,請確保已驗證是否已為腳本文件發送了標頭。在Firefox中,如果存在 crossorigin 屬性,但沒有 Access-Control-Allow-Origin 標頭,則不會執行腳本。
5. TypeError: Object doesn’t support property
這是在IE中發生的錯誤,當您調用undefined的方法時,你可以在IE開發人員控制檯中對此進行測試。
這等效於Chrome中的錯誤“ TypeError: ‘undefined’ is not a function”。是的,對於相同的邏輯錯誤,不同的瀏覽器可能具有不同的錯誤消息。
這是IE在採用JavaScript命名空間的Web應用程序中常見的問題,在這種情況下,99.9%的問題是IE無法將當前名稱空間中的方法綁定到 this 關鍵字。
例如,如果你的JS命名空間 Rollbar 使用 isAwesome 方法。通常,如果你在 Rollbar 名稱空間中,則可以使用以下語法調用 isAwesome 方法:
<code>this
.isAwesome();/<code>
Chrome,Firefox和Opera將很樂意接受此語法。另一方面,IE則不會。因此,在使用JS命名空間時,最安全的方法是用實際的命名空間作為前綴。
<code>Rollbar
.isAwesome
();/<code>
6. TypeError: ‘undefined’ is not a function
這是在Chrome中發生的錯誤,當你調用undefined的函數時。你可以在Chrome開發者控制檯和Mozilla Firefox開發者控制檯中對此進行測試。
隨著這些年來JavaScript的編碼技術和設計模式越來越複雜,在回調和閉包中的自引用作用域也相應地增多,這也是相當常見的這種或那種混亂的根源。
考慮以下示例代碼片段:
<code>function
clearBoard
() { alert("Cleared"
); }document
.addEventListener("click"
,function
(){this
.clearBoard(); });/<code>
如果執行上述代碼,然後單擊該頁面,則會導致以下錯誤“ Uncaught TypeError:this.clearBoard not a function”。原因是正在執行的匿名函數是在文檔的上下文中,而 clearBoard 是在 window 中定義的。
傳統的、與舊瀏覽器兼容的解決方案是簡單地將對它的引用保存在一個變量中,然後閉包可以繼承這個變量。例如:
<code>var
self =this
;document
.addEventListener("click"
,function
(){ self.clearBoard(); });/<code>
另外,在較新的瀏覽器中,可以使用 bind() 方法傳遞正確的引用:
<code>document.addEventListener("click"
,this
.clearBoard.bind(this
));/<code>
7. Uncaught RangeError: Maximum call stack
這是Chrome瀏覽器在幾種情況下出現的錯誤,一種是調用不終止的遞歸函數。你可以在Chrome開發者控制檯中對此進行測試。
如果將值傳遞給超出範圍的函數,也可能會發生這種情況。許多函數的輸入值僅接受特定範圍的數字,例如,Number.toExponential(digits)
和 Number.toFixed(digits) 接受0到20之間的數字,而Number.toFixed(digits) 接受1到21之間的數字。<code>var
a =new
Array
(4294967295
);var
b =new
Array
(-1
);var
num =2.555555
;document
.writeln(num.toExponential(4
));document
.writeln(num.toExponential(-2
)); num =2.9999
;document
.writeln(num.toFixed(2
));document
.writeln(num.toFixed(25
)); num =2.3456
;document
.writeln(num.toPrecision(1
));document
.writeln(num.toPrecision(22
)); /<code>
8. TypeError: Cannot read property ‘length’
這是Chrome瀏覽器中發生的錯誤,因為讀取undefined的變量的length屬性,你可以在Chrome開發者控制檯中進行測試。
通常情況下,你可以在數組上找到定義的長度,但如果數組沒有初始化或者變量名被隱藏在其他上下文中,你可能會遇到這個錯誤。通過以下示例讓我們瞭解此錯誤。
<code>var
testArray= ["Test"
];function
testFunction
(testArray
) {for
(var
i =0
; i < testArray.length; i++) {console
.log(testArray[i]); } } testFunction();/<code>
當你聲明一個帶參數的函數時,這些參數就變成了局部參數。這意味著即使你具有名稱為 testArray 的變量,函數內具有相同名稱的參數仍將被視為局部參數。
你可以通過兩種方式解決問題:
刪除函數聲明語句中的參數(事實證明,你想訪問那些在函數外部聲明的變量,因此你不需要為函數使用參數)
<code>var
testArray = ["Test"
];function
testFunction
(/* No params */
) {for
(var
i =0
; i < testArray.length; i++) {console
.log(testArray[i]); } } testFunction()/<code>
調用函數,將我們聲明的數組傳遞給它。
<code>var
testArray = ["Test"
];function
testFunction
(testArray
) {for
(var
i =0
; i < testArray.length; i++) {console
.log(testArray[i]); } } testFunction(testArray);/<code>
9. Uncaught TypeError: Cannot set property
當我們嘗試訪問未定義的變量時,它總是返回 undefined,我們無法獲取或設置任何 undefined 屬性。在這種情況下,應用程序將引發“ Uncaught TypeError:Cannot set property”。
例如,在Chrome瀏覽器中:
如果 test 對象不存在,則錯誤將引發“ Uncaught TypeError:Cannot set property”。
10. ReferenceError: event is not defined
當您嘗試訪問undefined 或超出當前範圍的變量時,將引發此錯誤。你可以在Chrome瀏覽器中非常輕鬆地對其進行測試。
如果你在使用事件處理系統時收到這個錯誤,請確保你使用傳入的事件對象作為參數。IE等較舊的瀏覽器會提供全局變量事件,而Chrome會自動將事件變量附加到處理程序。Firefox不會自動添加它。 jQuery之類的庫試圖規範這種行為,儘管如此,最好還是使用傳遞給事件處理程序函數的方法。
<code>document
.addEventListener("mousemove"
,function
(event
) {console
.log(event); })/<code>
總結
事實證明,其中許多都是null或undefined的錯誤。如果使用嚴格的編譯器選項,像Typescript這樣的靜態類型檢查系統可以幫助你避免使用它們。它可以警告你,如果一個類型是預期的,但還沒有被定義。
如果對你有所啟發和幫助,可以點個關注、收藏、轉發,也可以留言 討論,這是對作者的最大鼓勵。
作者簡介:Web前端工程師,全棧開發工程師、持續學習者。
私信回覆:大禮包,送某網精品視頻課程網盤資料,準能為你節省不少錢!