譯者按: JavaScript語言設計太靈活,用起來難免要多加當心掉進坑裏面。瀏覽器
爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。安全
現在網站幾乎100%使用JavaScript。JavaScript看上去是一門十分簡單的語言,然而事實並不如此。它有不少容易被弄錯的細節,一不注意就致使BUG。閉包
在閉包或則回調中,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
,而後就可使用啦。this
Game.prototype.restart = function () { this.clearLocalStorage(); var self = this; // 將this指向的對象綁定到self this.timer = setTimeout(function(){ self.clearBoard(); }, 0); };
或則使用bind()
函數:prototype
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中並非。例如:翻譯
for (var i = 0; i < 10; i++) { /* ... */ } console.log(i); // 會輸出什麼呢?
一般在這種狀況下,調用console.log()
會輸出undefined
或則報錯。不過呢,這裏會輸出10
。在JavaScript中,即便for循環已經結束,變量i
依然存在,而且記錄最後的值。有些開發者會忘記這一點,而後致使許多bug。咱們可使用let
而不是for
來杜絕這一問題。debug
你須要監控內存使用量,由於泄露很難避免。內存泄露可能因爲引用不存在的對象或則循環引用致使。
當一個對象能夠經過引用訪問到,那麼會在內存中保存。瀏覽器的垃圾回收器僅僅會把那些不可訪問的對象回收。
JavaScript自動將全部在布爾環境下的變量類型轉換爲布爾類型,可是可能致使bug。舉例:
// 全部都是true console.log(false == '0'); console.log(null == undefined); console.log(" \t\r\n" == 0); console.log('' == 0); // 注意:下面兩個也是 if ({}) // … if ([]) // …
{}
和[]
都是對象,他們都會被轉換爲true。爲了防止bug出現,推薦使用===
和!==
來作比較,由於不會隱式作類型轉換。
在JavaScript中,你能夠輕鬆操做DOM(添加、修改和刪除),可是開發者每每很低效地去操做。這會致使bug出現,由於這些操做很是耗費計算資源。爲了解決這個問題,推薦使用文檔碎片(Document Fragment),若是你須要操做多個DOM元素。
廣告: 你的線上代碼真的沒有BUG嗎?歡迎無償使用Fundebug!咱們能夠幫助您第一時間發現BUG!
舉例:
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
變量,該函數被綁定到元素的點擊事件。
開發者若是沒能正確理解繼承的原理,那麼就可能寫出有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
。此時若是secondObj
的name
屬性被刪除掉,經過原型鏈查找會返回正確的默認值。
var thirdObj = new BaseObject('unique'); console.log(thirdObj.name); // -> 輸出'unique' delete thirdObj.name; console.log(thirdObj.name); // -> 輸出'default'
咱們來實現一個簡單的構造函數用來建立對象:
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"
若是你將一個字符串做爲setTimeout/setTimeInterval,它會被傳給函數構造函數並構建一個新的函數。該操做流程很慢並且低效,並致使bug出現。
var hello = function(){ console.log("hello, fundebug !"); } setTimeout("hello", 1000);
一個好的替代方法就是傳入函數做爲參數:
setInterval(logTime, 1000); // 將logTime函數傳入 setTimeout(function() { // 傳入一個匿名函數 logMessage(msgValue); }, 1000);
使用strict model會增長不少限制條件來增強安全和防止某些錯誤的出現,若是不使用strict mode
,你就至關於少了一個得力的助手幫你避免錯誤:
版權聲明:
轉載時請註明做者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/15/top_10_bugs_and_fixing_method/