10個JavaScript常見BUG及修復方法

譯者按: JavaScript語言設計太靈活,用起來難免要多加當心掉進坑裏面。小程序

爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。微信小程序

現在網站幾乎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的引用正確
};

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

在大多數程序語言中,每個函數塊都有一個獨立的新的做用域,可是在JavaScript中並非。例如:網站

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

一般在這種狀況下,調用console.log()會輸出undefined或則報錯。不過呢,這裏會輸出10。在JavaScript中,即便for循環已經結束,變量i依然存在,而且記錄最後的值。有些開發者會忘記這一點,而後致使許多bug。咱們可使用let而不是var來杜絕這一問題。this

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元素。

廣告: 你的線上代碼真的沒有BUG嗎?歡迎無償使用Fundebug!咱們能夠幫助您第一時間發現BUG!

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會自動拋出錯誤

關於Fundebug

Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了6億+錯誤事件,獲得了Google、360、金山軟件等衆多知名用戶的承認。歡迎免費試用!

圖片描述

版權聲明

轉載時請註明做者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/15/top_10_bugs_and_fixing_method/

相關文章
相關標籤/搜索