原創(chuàng)|行業(yè)資訊|編輯:蔣永|2017-11-16 10:38:36.000|閱讀 635 次
概述:現(xiàn)在的網(wǎng)站,可以說是全部都使用的JavaScript。JavaScript是否真的像看上去那么簡單呢?,答案是否定的。它還有很多細(xì)節(jié)需要我們注意,因?yàn)橐粋€(gè)不小心,就會(huì)出現(xiàn)很多BUG。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
現(xiàn)在的網(wǎng)站,可以說是全部都使用的JavaScript。JavaScript是否真的像看上去那么簡單呢?,答案是否定的。它還有很多細(xì)節(jié)需要我們注意,因?yàn)橐粋€(gè)不小心,就會(huì)出現(xiàn)很多BUG。
在閉包或則回調(diào)中,this
關(guān)鍵字的作用域很容易弄錯(cuò)。舉個(gè)例子:
Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(function() {
this.clearBoard(); // 此處this指的是?
}, 0);
};
如果執(zhí)行上面的代碼,我們會(huì)看到報(bào)錯(cuò):
Uncaught TypeError: undefined is not a function
出錯(cuò)的原因在于:當(dāng)你調(diào)用setTimeout
函數(shù),你實(shí)際上調(diào)用的是window.setTimeout()
。在setTimeout
中傳入的匿名函數(shù)是在window
這個(gè)對(duì)象環(huán)境下,所以this
是指向window
,但是window
并沒有clearBoard
方法。
如何解決呢?定義新的變量引用指向Game
對(duì)象的this
,然后就可以使用啦。
Game.prototype.restart = function () {
this.clearLocalStorage();
var self = this; // 將this指向的對(duì)象綁定到self
this.timer = setTimeout(function(){
self.clearBoard();
}, 0);
};
或則使用bind()
函數(shù):
Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this'
};
Game.prototype.reset = function(){
this.clearBoard(); // 此處this的引用正確
};
在大多數(shù)程序語言中,每一個(gè)函數(shù)塊都有一個(gè)獨(dú)立的新的作用域,但是在JavaScript中并不是。例如:
for (var i = 0; i < 10; i++) {
/* ... */
}
console.log(i); // 會(huì)輸出什么呢?
通常在這種情況下,調(diào)用console.log()
會(huì)輸出undefined
或則報(bào)錯(cuò)。不過呢,這里會(huì)輸出10
。在JavaScript中,即使for循環(huán)已經(jīng)結(jié)束,變量i
依然存在,并且記錄最后的值。有些開發(fā)者會(huì)忘記這一點(diǎn),然后導(dǎo)致許多bug。我們可以使用let
而不是for
來杜絕這一問題。
你需要監(jiān)控內(nèi)存使用量,因?yàn)樾孤逗茈y避免。內(nèi)存泄露可能由于引用不存在的對(duì)象或則循環(huán)引用導(dǎo)致。
當(dāng)一個(gè)對(duì)象可以通過引用訪問到,那么會(huì)在內(nèi)存中保存。瀏覽器的垃圾回收器僅僅會(huì)把那些不可訪問的對(duì)象回收。
JavaScript自動(dòng)將所有在布爾環(huán)境下的變量類型轉(zhuǎn)換為布爾類型,但是可能導(dǎo)致bug。舉例:
for (var i = 0; i < 10; i++) {
/* ... */
}
console.log(i); // 會(huì)輸出什么呢?
{}
和[]
都是對(duì)象,他們都會(huì)被轉(zhuǎn)換為true。為了防止bug出現(xiàn),推薦使用===
和!==
來做比較,因?yàn)椴粫?huì)隱式做類型轉(zhuǎn)換。
在JavaScript中,你可以輕松操作DOM(添加、修改和刪除),但是開發(fā)者往往很低效地去操作。這會(huì)導(dǎo)致bug出現(xiàn),因?yàn)檫@些操作非常耗費(fèi)計(jì)算資源。為了解決這個(gè)問題,推薦使用文檔碎片(Document Fragment),如果你需要操作多個(gè)DOM元素。
舉例:
var elements = document.getElementsByTagName('input');
var n = elements.length; // 假設(shè)我們有10個(gè)元素
for (var i = 0; i < n; i++) {
elements[i].onclick = function() {
console.log("元素編號(hào)#" + i);
};
}
如果我們有10個(gè)元素,那么點(diǎn)擊任何一個(gè)元素都會(huì)顯示“元素編號(hào)#10”!因?yàn)樵?code>onclick被調(diào)用的時(shí)候,for循環(huán)已經(jīng)結(jié)束,因此所有的i都是10。
解法:
var elements = document.getElementsByTagName('input');
var n = elements.length; // 假設(shè)有10個(gè)元素
var makeHandler = function(num) { // outer function
return function() { // inner function
console.log("元素編號(hào)##" + num);
};
};
for (var i = 0; i < n; i++) {
elements[i].onclick = makeHandler(i+1);
}
makeHandler
在for循環(huán)執(zhí)行的時(shí)候立即被調(diào)用,獲取到當(dāng)前的值i+1
,并且存儲(chǔ)在變量num
中。makeHandler
返回一個(gè)函數(shù)使用num
變量,該函數(shù)被綁定到元素的點(diǎn)擊事件。
開發(fā)者如果沒能正確理解繼承的原理,那么就可能寫出有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'
而我們實(shí)際上想要的結(jié)果是打印默認(rèn)的name。
BaseObject = function (name) {
if(typeof name !== "undefined") {
this.name = name;
}
};
BaseObject.prototype.name = 'default';
每一個(gè)BaseObject
都繼承name
屬性,并且默認(rèn)值為default
。此時(shí)如果secondObj
的name
屬性被刪除掉,通過原型鏈查找會(huì)返回正確的默認(rèn)值。
var thirdObj = new BaseObject('unique');
console.log(thirdObj.name); // -> 輸出'unique'
delete thirdObj.name;
console.log(thirdObj.name); // -> 輸出'default'
我們來實(shí)現(xiàn)一個(gè)簡單的構(gòu)造函數(shù)用來創(chuàng)建對(duì)象:
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);
控制臺(tái)會(huì)輸出:
function () {
console.log(this === window ? "window" : "MyObj");
}
現(xiàn)在我們來對(duì)比一下兩者調(diào)用的區(qū)別:
obj.whoAmI(); // 輸出"MyObj" (和期望一致)
whoAmI(); // 輸出"window" (竟然輸出了window)
當(dāng)我們把obj.whoAmI
賦值給whoAmI
的時(shí)候,這個(gè)新的變量whoAmI
是定義在全局下,因此this
指向全局的window
,而不是MyObj
。如果我們真的要獲取對(duì)MyObj
的函數(shù)的引用,需要在其作用域下。
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函數(shù)第一個(gè)參數(shù)誤用字符串
如果你將一個(gè)字符串作為setTimeout/setTimeInterval,它會(huì)被傳給函數(shù)構(gòu)造函數(shù)并構(gòu)建一個(gè)新的函數(shù)。該操作流程很慢而且低效,并導(dǎo)致bug出現(xiàn)。
var hello = function(){
console.log("hello, fundebug !");
}
setTimeout("hello", 1000);
一個(gè)好的替代方法就是傳入函數(shù)作為參數(shù):
setInterval(logTime, 1000); // 將logTime函數(shù)傳入
setTimeout(function() { // 傳入一個(gè)匿名函數(shù)
logMessage(msgValue);
}, 1000);
使用strict model會(huì)增加很多限制條件來加強(qiáng)安全和防止某些錯(cuò)誤的出現(xiàn),如果不使用strict mode
,你就相當(dāng)于少了一個(gè)得力的助手幫你避免錯(cuò)誤:
怎么樣,是不是對(duì)這些bug有所了解了呢?趕緊看一下你的代碼有沒有這些問題吧。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn