JavaScript中常見的10個BUG及其修複方法

来源:http://www.cnblogs.com/shsxt/archive/2017/11/15/7839804.html
-Advertisement-
Play Games

如今網站幾乎100%使用JavaScript。JavaScript看上去是一門十分簡單的語言,然而事實並不如此。它有很多容易被弄錯的細節,一不註意就導致BUG。 1. 錯誤的對this進行引用 在閉包或則回調中,this關鍵字的作用域很容易弄錯。舉個例子: Game.prototype.restar ...


                                                   

如今網站幾乎100%使用JavaScript。JavaScript看上去是一門十分簡單的語言,然而事實並不如此。它有很多容易被弄錯的細節,一不註意就導致BUG。

1. 錯誤的對this進行引用

在閉包或則回調中,this關鍵字的作用域很容易弄錯。舉個例子:


Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(function() {
this.clearBoard(); // 此處this指的是?
}, 0);
};

 

如果執行上面的代碼,我們會看到報錯:


Uncaught TypeError: undefined is not a function

 

出錯的原因在於:當你調用setTimeout函數,你實際上調用的是window.setTimeout()。在setTimeout中傳入的匿名函數是在window這個對象環境下,所以this是指向window,但是window並沒有clearBoard方法。

如何解決呢?定義新的變數引用指向Game對象的this,然後就可以使用啦。


Game.prototype.restart = function () {
this.clearLocalStorage();
var self = this; // 將this指向的對象綁定到self
this.timer = setTimeout(function(){
self.clearBoard();
}, 0);
};

 

或則使用bind()函數:


Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this'
};
 
Game.prototype.reset = function(){
this.clearBoard(); // 此處this的引用正確
};

 還有一些JavaScript中錯誤的正確處理方式,歡迎點擊參考閱讀http://www.shsxt.com/it/html5/539.html

2. 和塊作用域(block scope)有關的BUG

在大多數程式語言中,每一個函數塊都有一個獨立的新的作用域,但是在JavaScript中並不是。例如:


for (var i = 0; i < 10; i++) {
/* ... */
}
console.log(i); // 會輸出什麼呢?

通常在這種情況下,調用console.log()會輸出undefined或則報錯。不過呢,這裡會輸出10。在JavaScript中,即使for迴圈已經結束,變數i依然存在,並且記錄最後的值。有些開發者會忘記這一點,然後導致許多bug。我們可以使用let而不是for來杜絕這一問題。

3. 記憶體泄漏

你需要監控記憶體使用量,因為泄露很難避免。記憶體泄露可能由於引用不存在的對象或則迴圈引用導致。

  • 如何避免:關註對象的可訪問性(reachability)。
  • 可訪問的對象:
    • 現有的call stack任何位置可以訪問的對象
    • 全局對象

當一個對象可以通過引用訪問到,那麼會在記憶體中保存。瀏覽器的垃圾回收器僅僅會把那些不可訪問的對象回收。

4. 混淆的相等判斷

JavaScript自動將所有在布爾環境下的變數類型轉換為布爾類型,但是可能導致bug。舉例:


// 所有都是true
console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0);
 
// 註意:下麵兩個也是
if ({}) // …
if ([]) // …

{}[]都是對象,他們都會被轉換為true。為了防止bug出現,推薦使用===!==來做比較,因為不會隱式做類型轉換。

5. 低效的DOM操作

在JavaScript中,你可以輕鬆操作DOM(添加、修改和刪除),但是開發者往往很低效地去操作。這會導致bug出現,因為這些操作非常耗費計算資源。為瞭解決這個問題,推薦使用文檔碎片(Document Fragment),如果你需要操作多個DOM元素。

 

6. 在for迴圈中錯誤的定義函數

舉例:


var elements = document.getElementsByTagName('input');
var n = elements.length; // 假設我們有10個元素
for (var i = 0; i < n; i++) {
elements[i].onclick = function() {
console.log("元素編號#" + i);
};
}

如果我們有10個元素,那麼點擊任何一個元素都會顯示“元素編號#10”!因為在onclick被調用的時候,for迴圈已經結束,因此所有的i都是10。

解法:


var elements = document.getElementsByTagName('input');
var n = elements.length; // 假設有10個元素
var makeHandler = function(num) { // outer function
return function() { // inner function
console.log("元素編號##" + num);
};
};
for (var i = 0; i < n; i++) {
elements[i].onclick = makeHandler(i+1);
}

makeHandler在for迴圈執行的時候立即被調用,獲取到當前的值i+1,並且存儲在變數num中。makeHandler返回一個函數使用num變數,該函數被綁定到元素的點擊事件。

7. 通過原型錯誤地繼承

開發者如果沒能正確理解繼承的原理,那麼就可能寫出有bug的代碼:


BaseObject = function(name) {
if(typeof name !== "undefined") {
this.name = name;
} else {
this.name = 'default'
}
};
var firstObj = new BaseObject();
var secondObj = new BaseObject('unique');
 
console.log(firstObj.name); // -> 輸出'default'
console.log(secondObj.name); // -> 輸出'unique'

但是,如果我們做如下操作:


delete secondObj.name;

那麼:


console.log(secondObj.name); // -> 輸出'undefined'

 

而我們實際上想要的結果是列印預設的name。


BaseObject = function (name) {
if(typeof name !== "undefined") {
this.name = name;
}
};
 
BaseObject.prototype.name = 'default';

 

每一個BaseObject都繼承name屬性,並且預設值為default。此時如果secondObjname屬性被刪除掉,通過原型鏈查找會返回正確的預設值。


var thirdObj = new BaseObject('unique');
console.log(thirdObj.name); // -> 輸出'unique'
 
delete thirdObj.name;
console.log(thirdObj.name); // -> 輸出'default'

8. 實例方法中的無效引用

我們來實現一個簡單的構造函數用來創建對象:


var MyObject = function() {}
 
MyObject.prototype.whoAmI = function() {
console.log(this === window ? "window" : "MyObj");
};
 
var obj = new MyObject();

為了使用方便,我們定義變數whoAmI來引用obj.whoAmI


var whoAmI = obj.whoAmI;

 

列印出來看看:


console.log(whoAmI);

 

控制台會輸出:


function () {
console.log(this === window ? "window" : "MyObj");
}

 

現在我們來對比一下兩者調用的區別:


obj.whoAmI(); // 輸出"MyObj" (和期望一致)
whoAmI(); // 輸出"window" (竟然輸出了window)

 

當我們把obj.whoAmI賦值給whoAmI的時候,這個新的變數whoAmI是定義在全局下,因此this指向全局的window,而不是MyObj。如果我們真的要獲取對MyObj的函數的引用,需要在其作用域下。


var MyObject = function() {}
 
MyObject.prototype.whoAmI = function() {
console.log(this === window ? "window" : "MyObj");
};
 
var obj = new MyObject();
obj.w = obj.whoAmI; // 任然在obj的作用域
 
obj.whoAmI(); // 輸出"MyObj"
obj.w(); // 輸出"MyObj"

9. setTimeout/setInterval函數第一個參數誤用字元串

如果你將一個字元串作為setTimeout/setTimeInterval,它會被傳給函數構造函數並構建一個新的函數。該操作流程很慢而且低效,並導致bug出現。


var hello = function(){
console.log("hello, fundebug !");
}
setTimeout("hello", 1000);

一個好的替代方法就是傳入函數作為參數:


setInterval(logTime, 1000); // 將logTime函數傳入
 
setTimeout(function() { // 傳入一個匿名函數
logMessage(msgValue);
}, 1000);

10. 未能成功使用strict mode

使用strict model會增加很多限制條件來加強安全和防止某些錯誤的出現,如果不使用strict mode,你就相當於少了一個得力的助手幫你避免錯誤:

  • 更加容易debug
  • 避免不小心定義了不該定義的全局變數
  • 避免this隱式轉換
  • 避免屬性名字或則參數值的重覆使用
  • eval()更加安全
  • 無效地使用delete會自動拋出錯誤

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Title {{message}} {{num1+num2}} {{temp?name:name2}} 大人們往往會因為小孩子們崇拜拾荒者、郵差以及起重機司機而感到擔憂,但現在看來,比起我們成年人所崇拜的那些好萊塢大糞坑裡的蛆,孩子們的理想似乎要崇高的多。 ... ...
  • Project 可以理解為項目、工程或者站點,以下稱項目。使用項目管理的好處是:不用將所有文件都放到同一個根目錄,可以將相關但不同路徑的文件組成一個Project,每個項目都是獨立的,文件的狀態等都會被保存,因此只需一個視窗便可以在多個項目中隨意切換。 ...
  • DOM(Document Object Modle) 操作文檔的編程介面DOM定義了表示和修改文檔的方法,不能修改css樣式表,在js中使用DOM方法改變元素的css樣式,實質上是在元素上添加行間樣式。DOM對象就是宿主對象,用來操作HTML和xml功能對象的集合。 xml——>xhtml——>ht ...
  • ES的數據類型: 原始類型(值存在棧記憶體中): Number、String Boolean、undefined、null charAt(index)返回該index所在的位元組,charCodeAt(index)返回該index所在位元組的Unicode值。 undefined和null不能和數字進行比 ...
  • 之前對 MVVM 模式一直只是模模糊糊的認識,正所謂沒有實踐就沒有發言權,通過這兩年對 Vue 框架的深入學習和項目實踐,終於有了撥開雲霧見月明的感覺。在此記錄一下,算是拋磚了。 ...
  • const Koa = require('koa'); const route = require('koa-route'); const app = new Koa(); const about = ctx => { ctx.response.body = 'Hello World111'; };... ...
  • 無數遍的被問到一個問題,沒有邏輯思維是不是學不了編程?“邏輯思維”這個詞,很多人會對它敬而遠之。因為大多人,也包括我在內,我們這些並沒有天生才智的人來說,似乎總認為這是自己不擅長的領域。 ...
  • 應用場景:一些圖片較多的頁面,一些需要加上進度條或者百分比讀取等載入效果的頁面,一般移動端頁面用得比較多 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...