首頁  >  文章  >  web前端  >  10個JavaScript常見BUG及修復方法

10個JavaScript常見BUG及修復方法

小云云
小云云原創
2017-12-06 15:51:421422瀏覽

 JavaScript語言設計太靈活,用起來不免要多加小心掉進坑裡面, JavaScript語言設計太靈活,用起來不免要多加小心掉進坑裡面。本文和大家分享10個JavaScript常見BUG及修復方法。如今網站幾乎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而不是for來杜絕這個問題。

3. 記憶體洩漏

你需要監控記憶體使用量,因為外洩很難避免。記憶體外洩可能由於引用不存在的物件或則循環引用導致。

  • 如何避免:關注物件的可訪問性(reachability)。

  • 可存取的物件:

    • #現有的call stack任何位置可以存取的物件

    • #全域物件

當一個物件可以透過引用存取到,那麼就會在記憶體中保存。瀏覽器的垃圾回收器只會把那些不可存取的物件回收。

4. 混淆的相等判斷

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

// 所有都是true
console.log(false == &#39;0&#39;);
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log(&#39;&#39; == 0);

// 注意:下面两个也是
if ({}) // …
if ([]) // …

{}[]都是對象,他們都會轉換成true。為了防止bug出現,建議使用===!==來做比較,因為不會隱含做型別轉換。

5. 低效的DOM操作

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

廣告: 你的線上程式碼真的沒有BUG嗎?歡迎免費使用Fundebug!我們可以幫助您第一時間發現BUG!

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

範例:

var elements = document.getElementsByTagName(&#39;input&#39;);
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(&#39;input&#39;);
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 = &#39;default&#39;
    }
};
var firstObj = new BaseObject();
var secondObj = new BaseObject(&#39;unique&#39;);

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会自动抛出错误

以上内容就是10个JavaScript常见BUG及修复方法,希望能帮助到大家。

相关推荐:

PHP中调试函数debug_backtrace的使用方法介绍

JavaScript调试必备的5个debug技巧分享

JS中offsetWidth的bug及处理方法

以上是10個JavaScript常見BUG及修復方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn