10 種最常見的 Javascript 錯誤
為了回饋我們的開發者社群,我們查看了數千個專案的資料庫,發現了 JavaScript 中頻度最高的 10 種錯誤。我們會告訴你什麼原因導致了這些錯誤,以及如何防止這些錯誤發生。如果你能夠避免落入這些 “陷阱”,你將會成為一個更好的開發者。
資料才是王道,我們收集並分析了出現頻次排前 10 的 JavaScript 錯誤。 Rollbar 會收集每個專案的所有錯誤,並總結每個錯誤發生的次數。我們通過根據 “指紋”(rollbar 用到的一種演算法,詳見:https://rollbar.com/docs/grouping-algorithm/)對錯誤進行分組。基本上,如果第二個錯誤只是第一個錯誤的重複,我們會把兩個錯誤分到同一組。這會給使用者一個很好的概括,而不是像在日誌檔案中看到的那樣直接一大堆讓人感覺到十分壓迫的 dump。
我們專注於最有可能影響您和您的使用者的錯誤。為此,我們通過研究各種不同公司的專案集來對於錯誤進行排列。如果我們只檢視每個錯誤發生的總次數,那麼客戶量大的的專案產生的錯誤可能會壓倒其他錯誤,導致實際收集到的是與大多數讀者無關的錯誤資料集。
以下是 JavaScript 錯誤 Top 10:
為了便於閱讀,我們將每個錯誤描述都縮短了。接下來,讓我們深入到每一個錯誤,來確定什麼會導致它,以及如何避免建立它。
1. Uncaught TypeError: Cannot read property
如果你是一個 JavaScript 開發人員,可能你看到這個錯誤的次數比你敢承認的要多(LOL…)。當你讀取一個未定義的物件的屬性或呼叫其方法時,這個錯誤會在 Chrome 中出現。 您可以很容易的在 Chrome 開發者控制檯中進行測試(嘗試)。
發生這種情況的原因很多,但常見的一種是在渲染 UI 元件時對於狀態的初始化操作不當。
我們來看一個在真實應用程式中發生的例子:我們選擇 React,但該情況也同樣適用於 Angular、Vue 或任何其他框架。
class Quiz extends Component { componentWillMount() { axios.get('/thedata').then(res => { this.setState({items: res.data}); }); } render() { return ( <ul> {this.state.items.map(item => <likey={item.id}>{item.name}</li> )} </ul> ); } }
這裡有兩件重要的事情要實現:
- 元件的狀態(例如 this.state)從
undefined
開始。 - 當非同步獲取資料時,不管它是在建構函式
componentWillMount
還是componentDidMount
中獲取的,元件在資料載入之前至少會呈現一次,當 Quiz 第一次呈現時,this.state.items
是未定義的。 這又意味著ItemList
將items
定義為undefined
,並且在控制檯中出現錯誤 - “Uncaught TypeError: Cannot read property ‘map’ of undefined”。
這很容易解決。最簡單的方法:在建構函式中用合理的預設值來初始化 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 =>
<likey={item.id}>{item.name}</li>
)}
</ul>
);
}
}
在你的應用程式中的具體程式碼可能是不同的,但我們希望我們已經給你足夠的線索,以解決或避免在你的應用程式中出現的這個問題。如果還沒有,請繼續閱讀,因為我們將在下面覆蓋更多相關錯誤的示例。
2. TypeError: ‘undefined’ is not an object
這是在 Safari 中讀取屬性或呼叫未定義物件上的方法時發生的錯誤。您可以在 Safari Developer Console 中輕鬆測試。這與 1 中提到的 Chrome 的錯誤基本相同,但 Safari 使用了不同的錯誤訊息提示語。
3. TypeError: null is not an object
這是在 Safari 中讀取屬性或呼叫空物件上的方法時發生的錯誤。 您可以在 Safari Developer Console 中輕鬆測試。
有趣的是,在 JavaScript 中,null
和 undefined
是不一樣的,這就是為什麼我們看到兩個不同的錯誤資訊。undefined
通常是一個尚未分配的變數,而 null
表示該值為空。
要驗證它們不相等,請嘗試使用嚴格的相等運算子 ===
:
在現實世界的例子中,這種錯誤可能發生的一種場景是:如果在載入元素之前嘗試在 JavaScript 中使用元素。 因為 DOM API 對於空白的物件引用返回值為 null
。
任何執行和處理 DOM 元素的 JS 程式碼都應該在建立 DOM 元素之後執行。 JS 程式碼按照 HTML 中的規定從上到下進行解釋。 所以,如果 DOM 元素之前有一個標籤,指令碼標籤內的 JS 程式碼將在瀏覽器解析 HTML 頁面時執行。 如果在載入指令碼之前尚未建立 DOM 元素,則會出現此錯誤。
在這個例子中,我們可以通過新增一個事件監聽器來解決這個問題,這個監聽器會在頁面準備好的時候通知我們。 一旦 addEventListener
被觸發,init()
方法就可以使用
DOM 元素。
<script>
functioninit() {
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>
4. (unknown): Script error
當未捕獲的 JavaScript 錯誤(通過window.onerror
處理程式引發的錯誤,而不是捕獲在try-catch
中)被瀏覽器的跨域策略限制時,會產生這類的指令碼錯誤。
例如,如果您將您的 JavaScript 程式碼託管在 CDN 上,則任何未被捕獲的錯誤將被報告為“指令碼錯誤” 而不是包含有用的堆疊資訊。這是一種瀏覽器安全措施,旨在防止跨域傳遞資料,否則將不允許進行通訊。
要獲得真正的錯誤訊息,請執行以下操作:
1. 傳送 ‘Access-Control-Allow-Origin’ 頭部
將 Access-Control-Allow-Origin
標頭設定為 * 表示可以從任何域正確訪問資源。
如有必要,您可以將域替換為您的域:例如,Access-Control-Allow-Origin:www.example.com
。
但是,處理多個域會變得棘手,如果你使用 CDN,可能由此產生更多的快取問題會讓你感覺到這種努力並不值得。 在這裡看到更多。
這裡有一些關於如何在各種環境中設定這個標頭檔案的例子:
Apache
在 JavaScript 檔案所在的資料夾中,使用以下內容建立一個 .htaccess 檔案:
Header add Access-Control-Allow-Origin "*"
將 add_header
指令新增到提供 JavaScript 檔案的位置塊中:Nginx
location ~ ^/assets/ {
add_header Access-Control-Allow-Origin *;
}
將以下內容新增到您為 JavaScript 檔案提供資源服務的後端:HAProxy
rspadd Access-Control-Allow-Origin:\ *
2. 在 <script>
中設定 crossorigin="anonymous"
在您的 HTML 程式碼中,對於您設定了Access-Control-Allow-Origin
header
的每個指令碼,在 script 標籤上設定crossorigin =“anonymous”
。在指令碼標記中新增 crossorigin
屬性之前,請確保驗證上述
header 正確傳送。 在 Firefox 中,如果存在crossorigin
屬性,但Access-Control-Allow-Origin
頭不存在,則指令碼將不會執行。
5. TypeError: Object doesn’t support property
這是您在呼叫未定義的方法時發生在 IE 中的錯誤。 您可以在 IE 開發者控制檯中進行測試。
這相當於 Chrome 中的 “TypeError:”undefined“ is not a function” 錯誤。 是的,對於相同的邏輯錯誤,不同的瀏覽器可能具有不同的錯誤訊息。
對於使用 JavaScript 名稱空間的 Web 應用程式,這是一個 IE l瀏覽器的常見的問題。 在這種情況下,99.9% 的原因是 IE 無法將當前名稱空間內的方法繫結到 this
關鍵字。
例如:如果你 JS 中有一個名稱空間 Rollbar 以及方法 isAwesome
。 通常,如果您在
Rollbar 名稱空間內,則可以使用以下語法呼叫isAwesome
方法:
this.isAwesome();
Chrome,Firefox 和 Opera 會欣然接受這個語法。 另一方面 IE,不會。 因此,使用 JS 名稱空間時最安全的選擇是始終以實際名稱空間作為字首。
Rollbar.isAwesome();
6. TypeError: ‘undefined’ is not a function
當您呼叫未定義的函式時,這是 Chrome 中產生的錯誤。 您可以在 Chrome 開發人員控制檯和 Mozilla Firefox 開發人員控制檯中進行測試。
隨著 JavaScript 編碼技術和設計模式在過去幾年中變得越來越複雜,回撥和關閉中的自引用範圍也相應增加,這是這種/那種混淆的相當常見的來源。
考慮這個程式碼片段:
functiontestFunction() {
this.clearLocalStorage();
this.timer = setTimeout(function(){
this.clearBoard();// what is "this"?
},0);
};
執行上面的程式碼會導致以下錯誤:“Uncaught TypeError:undefined is not a function”。 你得到上述錯誤的原因是,當你呼叫setTimeout()
時,實際上是呼叫window.setTimeout()
。
因此,在視窗物件的上下文中定義了一個傳遞給setTimeout()
的匿名函式,該函式沒有clearBoard()
方法。
一個傳統的,舊瀏覽器相容的解決方案是簡單地將您的 this
儲存在一個變數,然後可以由閉包繼承。
例如:
functiontestFunction () {
this.clearLocalStorage();
var self =this; // save reference to 'this', while it's still this!
this.timer = setTimeout(function(){
self.clearBoard();
},0);
};
或者,在較新的瀏覽器中,可以使用bind()
方法傳遞適當的引用:
functiontestFunction () {
this.clearLocalStorage();
this.timer = setTimeout(this.reset.bind(this),0); // bind to 'this'
};
functiontestFunction(){
this.clearBoard();//back in the context of the right 'this'!
};
7. Uncaught RangeError: Maximum call stack
這是 Chrome 在一些情況下會發生的錯誤。 一個是當你呼叫一個不終止的遞迴函式。您可以在 Chrome 開發者控制檯中進行測試。
此外,如果您將值傳遞給超出範圍的函式,也可能會發生這種情況。 許多函式只接受其輸
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!
入值的特定範圍的數字。 例如:Number.toExponential(digits)
和 Number.toFixed(digits)
接受
0 到 20 的數字,Number.toPrecision(digits)
接受 1 到 21
的數字。
8. TypeError: Cannot read property ‘length’
這是 Chrome 中發生的錯誤,因為讀取未定義變數的長度屬性。 您可以在 Chrome 開發者控制檯中進行測試。
您通常會在陣列中找到定義的長度,但是如果陣列未初始化或者變數名稱在另一個上下文中隱藏,則可能會遇到此錯誤。讓我們用下面的例子來理解這個錯誤。
var testArray = ["Test"];
functiontestFunction(testArray) {
for (var i = 0; i < testArray.length; i++) {
console.log(testArray[i]);
}
}
testFunction();
當你用引數宣告一個函式時,這些引數變成了函式作用域內的本地引數。這意味著即使你函式外有名為
testArray
的變數,在一個函式中具有相同名字的引數也會被視為本地引數。
您有兩種方法可以解決您的問題:
1. 刪除函式宣告語句中的引數(事實上你想訪問那些宣告在函式之外的變數,所以你不需要函式的引數):
var testArray = ["Test"];
/* Precondition: defined testArray outside of a function */
functiontestFunction(/* No params */){
for (var i = 0; i < testArray.length; i++) {
console.log(testArray[i]);
}
}
testFunction();
2. 用宣告的陣列呼叫該函式:
var testArray = ["Test"];
functiontestFunction(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”。
例如,在 Chrome 瀏覽器中:
如果測試物件不存在,錯誤將會丟擲 “Uncaught TypeErrorUncaught TypeError: Cannot set property”。
10. ReferenceError: event is not defined
當您嘗試訪問未定義的變數或超出當前範圍的變數時,會引發此錯誤。 您可以在 Chrome 瀏覽器中輕鬆測試。
如果在使用事件處理系統時遇到此錯誤,請確保使用傳入的事件物件作為引數。像 IE 這樣的舊瀏覽器提供了一個全域性變數事件,但並不是所有瀏覽器都支援。像 jQuery 這樣的庫試圖規範化這種行為。儘管如此,最好使用傳入事件處理函式的函式。
functionmyFunction(event) {
event = event.which || event.keyCode;
if(event.keyCode===13){
alert(event.keyCode);
}
}
結論
我們希望你學到了新的東西,可以避免將來的錯誤,或者本指南幫助你解決了頭痛的問題。