本文是一篇翻譯文章。原文:45 Useful JavaScript Tips, Tricks and Best Practices
譯文開(kāi)始。
眾所周知,Javascript是全球最流行的語(yǔ)言之一,它涉足Web開(kāi)發(fā),移動(dòng)端開(kāi)發(fā)(PhoneGap、Appcelerator),服務(wù)端開(kāi)發(fā)(Nodejs、Wakanda),還有多種第三方實(shí)現(xiàn)(CoffeeScript這種)。此外Javascript還是許多開(kāi)發(fā)者進(jìn)入編程世界所接觸的第一門(mén)語(yǔ)言。它既可以在瀏覽器中簡(jiǎn)單的彈出一個(gè)alert窗口,也能達(dá)到控制機(jī)器人這種復(fù)雜的程度(比如nodebot、nodruino)?,F(xiàn)在那些能夠熟練編寫(xiě)結(jié)構(gòu)清晰、性能卓越的Javascript開(kāi)發(fā)者們,已經(jīng)成為招聘市場(chǎng)上炙手可熱的應(yīng)聘者了。
在這篇文章中,我將向你展示一系列Javascript相關(guān)的小技巧和一些最佳實(shí)踐。除了少數(shù)幾個(gè)示例,大部分示例都可以在瀏覽器環(huán)境或者服務(wù)端環(huán)境適用。
注意,文章中所有的代碼片段都已經(jīng)在Google Chrome V30(V8 3.20.17.15)測(cè)試通過(guò)。
var
關(guān)鍵字進(jìn)行變量賦值在Javascript中,如果一個(gè)變量沒(méi)有經(jīng)過(guò)聲明就直接進(jìn)行賦值操作,那么這個(gè)變量就會(huì)自動(dòng)轉(zhuǎn)變成全局變量。我們要盡量避免這種情況(全局變量)。
===
來(lái)代替==
進(jìn)行判等==
和!=
操作符會(huì)在某些情況下自動(dòng)進(jìn)行類(lèi)型轉(zhuǎn)化。但是===
和!==
不會(huì)做自動(dòng)轉(zhuǎn)化,它們?cè)谧霰容^時(shí),會(huì)同時(shí)比較數(shù)據(jù)類(lèi)型和值,這也使得===
和!==
要比==
和!=
的速度要快。
[10] === 10 // is false
[10] == 10 // is true
'10' == 10 // is true
'10' === 10 // is false
[] == 0 // is true
[] === 0 // is false
'' == false // is true but true == "a" is false
'' === false // is false
undefined
,null
,0,false
,NaN
,''
(空字符串)都為邏輯假值;
來(lái)結(jié)束一行代碼使用分號(hào)來(lái)結(jié)束代碼行是一個(gè)被Javascript社區(qū)推薦的最佳實(shí)踐。如果你忘了也沒(méi)有關(guān)系,因?yàn)楝F(xiàn)在的Javascript引擎將會(huì)自動(dòng)給你加上分號(hào)。至于我們?yōu)槭裁磻?yīng)該使用分號(hào),可以參閱這篇文章http://davidwalsh.name/javascript-semicolons。
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
var Saad = new Person("Saad", "Mousliki");
typeof
、instanceof
、constructor
時(shí)要謹(jǐn)慎小心typeof
:JavaScript的一元操作符,用于以字符串的形式返回變量的原始類(lèi)型,注意,typeof null
也會(huì)返回object
,大多數(shù)的對(duì)象類(lèi)型(數(shù)組Array
、時(shí)間Date
等)也會(huì)返回object
constructor
:對(duì)象(函數(shù))的內(nèi)部原型屬性,它是可寫(xiě)的(可以被重寫(xiě))instanceof
:JavaScript操作符,會(huì)在原型鏈中的構(gòu)造器中搜索,找到則返回true
,否則返回false
(常用于判斷某一個(gè)對(duì)象是否是某個(gè)構(gòu)造器或者其父類(lèi)構(gòu)造器的實(shí)例)var arr = ["a", "b", "c"];
typeof arr; // return "object"
arr instanceof Array // true
arr.constructor(); // []
函數(shù)在創(chuàng)建之后直接自動(dòng)執(zhí)行,通常稱(chēng)之為自調(diào)用匿名函數(shù)(Self-Invoked Anonymous Function)或直接調(diào)用函數(shù)表達(dá)式(Immediately Invoked Function Expression )。比如,
(function(){
// some private code that will be executed automatically
})();
(function(a,b){
var result = a+b;
return result;
})(10,20);
var items = [12, 548 , 'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' , 2145 , 119];
var randomItem = items[Math.floor(Math.random() * items.length)];
這個(gè)功能在生成測(cè)試用的假數(shù)據(jù)時(shí)特別有用。比如取一個(gè)指定范圍內(nèi)的工資數(shù)。
var x = Math.floor(Math.random() * (max - min + 1)) + min;
var numbersArray = [] , max = 100;
for( var i=1; numbersArray.push(i++) < max;); // numbers = [1,2,3 ... 100]
function generateRandomAlphaNum(len) {
var rdmString = "";
for( ;rdmString.length < len; rdmString += Math.random().toString(36).substr(2));
return rdmString.substr(0, len);
}
譯者注
toString()
方法可以接受一個(gè)參數(shù)表示數(shù)字進(jìn)制。而36進(jìn)制剛好可以使用a-z和0-9這些字符。所以此方法可以用于生成簡(jiǎn)單的隨機(jī)串。
var numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];
numbers = numbers.sort(function(){ return Math.random() - 0.5});
/* the array numbers will be equal for example to [120, 5, 228, -215, 400, 458, -85411, 122205] */
這里采用了原生的排序函數(shù)sort()
,此外我們還可以使用專(zhuān)門(mén)的工具庫(kù)來(lái)得到這一目的。
像Java、C#、PHP這些語(yǔ)言都內(nèi)置了trim()
功能函數(shù)用于字符串去空格。但是Javascript沒(méi)有這個(gè)內(nèi)置方法??梢酝ㄟ^(guò)下面的方法來(lái)得到此目的,
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
};
不過(guò),在新的Javascript引擎中,已經(jīng)內(nèi)置支持了這個(gè)功能。
var array1 = [12 , "foo" , {name "Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 值為 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
argruments
轉(zhuǎn)換成數(shù)組var argArray = Array.prototype.slice.call(arguments);
function isNumber(n){
return !isNaN(parseFloat(n)) && isFinite(n);
}
function isArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]' ;
}
如果toString()
被重寫(xiě)過(guò)的話,上面的方法就不行了。此時(shí)我們可以使用下面的方法,
Array.isArray(obj); // its a new Array method
如果在瀏覽器中沒(méi)有使用iframe,還可以用instanceof
,但如果上下文太復(fù)雜,也有可能出錯(cuò)。比如,
var myFrame = document.createElement('iframe');
document.body.appendChild(myFrame);
var myArray = window.frames[window.frames.length-1].Array;
var arr = new myArray(a,b,10); // [a,b,10]
// myArray 的構(gòu)造器已經(jīng)丟失,instanceof 的結(jié)果將不正常
// 不同iframe中的構(gòu)造器是不能共享的
arr instanceof Array; // false
var numbers = [5, 458 , 120 , -215 , 228 , 400 , 122205, -85411];
var maxInNumbers = Math.max.apply(Math, numbers);
var minInNumbers = Math.min.apply(Math, numbers);
var myArray = [12 , 222 , 1000 ];
myArray.length = 0; // myArray will be equal to [].
delete
關(guān)鍵字來(lái)移除一個(gè)數(shù)組元素應(yīng)該使用splice
方法而不是delete
來(lái)移除一個(gè)數(shù)組元素。對(duì)一個(gè)數(shù)組元素使用delete
會(huì)讓這個(gè)數(shù)組元素的值變?yōu)?code>undefined,并沒(méi)有將這個(gè)數(shù)組元素給刪除掉。
錯(cuò)誤的用法,
var items = [12, 548 ,'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' ,2154 , 119 ];
items.length; // return 11
delete items[3]; // return true
items.length; // return 11
/* items will be equal to [12, 548, "a", undefined × 1, 5478, "foo", 8852, undefined × 1, "Doe", 2154, 119] */
正確的用法,
var items = [12, 548 ,'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' ,2154 , 119 ];
items.length; // return 11
items.splice(3,1) ;
items.length; // return 10
/* items 結(jié)果為 [12, 548, "a", 5478, "foo", 8852, undefined × 1, "Doe", 2154, 119] */
注意,要想移除一個(gè)對(duì)象的屬性,應(yīng)該采用delete
方法。
length
來(lái)截?cái)嘁粋€(gè)數(shù)組與前面那個(gè)使用length
清空數(shù)組的示例類(lèi)似,我們可以使用length
來(lái)截?cái)嘁粋€(gè)數(shù)組。
var myArray = [12 , 222 , 1000 , 124 , 98 , 10 ];
myArray.length = 4; // myArray will be equal to [12 , 222 , 1000 , 124].
除此之外,如果我們使用一個(gè)更大的值去重寫(xiě)length
,那么數(shù)組的長(zhǎng)度將會(huì)改變,同時(shí)會(huì)用undefined
填充新增的數(shù)組元素。
myArray.length = 10; // the new array length is 10
myArray[myArray.length - 1] ; // undefined
&&
及||
進(jìn)行短語(yǔ)判斷var foo = 10;
foo == 10 && doSomething(); // is the same thing as if (foo == 10) doSomething();
foo == 5 || doSomething(); // is the same thing as if (foo != 5) doSomething();
||
還用于給函數(shù)參數(shù)設(shè)置默認(rèn)值,比如
function doSomething(arg1){
arg1 = arg1 || 10; // arg1 will have 10 as a default value if it’s not already set
}
map()
對(duì)數(shù)組進(jìn)行遍歷操作var squares = [1,2,3,4].map(function (val) {
return val * val;
});
// squares will be equal to [1, 4, 9, 16]
var num = 2.443242342;
num = num.toFixed(4); // num will be equal to 2.4432
注意,toFixed()
方法返回的是字符串而不是一個(gè)數(shù)字。
0.1 + 0.2 === 0.3 // is false
9007199254740992 + 1 // is equal to 9007199254740992
9007199254740992 + 2 // is equal to 9007199254740994
為什么呢?因?yàn)?.1+0.2等于0.30000000000000004。JavaScript的數(shù)字都遵循IEEE 754標(biāo)準(zhǔn)構(gòu)建,在內(nèi)部都是64位浮點(diǎn)小數(shù)表示,具體可以參閱這篇文章。
另外,你也可以使用toFixed()
或者toPrecision()
來(lái)解決這個(gè)問(wèn)題。
譯者注
關(guān)于這個(gè)問(wèn)題,博主也有一篇相關(guān)的文章,Javascript中浮點(diǎn)數(shù)的計(jì)算精度問(wèn)題。
for...in
來(lái)遍歷對(duì)象的屬性下面的代碼片段使用for...in
來(lái)遍歷對(duì)象屬性,可以防止遍歷到對(duì)象原型鏈上的屬性。
for (var name in object) {
if (object.hasOwnProperty(name)) {
// do something with name
}
}
var a = 0;
var b = ( a++, 99 );
console.log(a); // a will be equal to 1
console.log(b); // b is equal to 99
在使用jquery時(shí),我們可以臨時(shí)緩存整個(gè)jq對(duì)象,比如
var navright = document.querySelector('#right');
var navleft = document.querySelector('#left');
var navup = document.querySelector('#up');
var navdown = document.querySelector('#down');
isFinite()
的參數(shù)isFinite(0/0) ; // false
isFinite("foo"); // false
isFinite("10"); // true
isFinite(10); // true
isFinite(undefined); // false
isFinite(); // false
isFinite(null); // true 注意這里!??!
var numbersArray = [1,2,3,4,5];
var from = numbersArray.indexOf("foo") ; // from is equal to -1
numbersArray.splice(from,2); // will return [5]
注意傳給splice
的索引參數(shù)不要是負(fù)數(shù),當(dāng)是負(fù)數(shù)時(shí),會(huì)從數(shù)組結(jié)尾處刪除元素。
JSON
來(lái)進(jìn)行序列化和反序列化var person = {name :'Saad', age : 26, department : {ID : 15, name : "R&D"} };
var stringFromPerson = JSON.stringify(person);
/* stringFromPerson is equal to "{"name":"Saad","age":26,"department":{"ID":15,"name":"R&D"}}" */
var personFromString = JSON.parse(stringFromPerson);
/* personFromString is equal to person object */
eval()
和函數(shù)構(gòu)造器eval()
和函數(shù)構(gòu)造器(Function
consturctor)的開(kāi)銷(xiāo)都比較大,每次調(diào)用JavaScript引擎都要將源代碼轉(zhuǎn)換為可執(zhí)行的代碼。
var func1 = new Function(functionCode);
var func2 = eval(functionCode);
with()
使用with()
語(yǔ)法會(huì)將變量注入到全局變量中。因此,如果有重名的變量,就會(huì)發(fā)生覆蓋或者重寫(xiě)的問(wèn)題。
for...in
遍歷數(shù)組錯(cuò)誤的用法,
var sum = 0;
for (var i in arrayNumbers) {
sum += arrayNumbers[i];
}
更好的做法,
var sum = 0;
for (var i = 0, len = arrayNumbers.length; i < len; i++) {
sum += arrayNumbers[i];
}
除此之外,i
和len
是在for
循環(huán)的第一個(gè)聲明中,二者只會(huì)初始化一次,這要比下面這種寫(xiě)法快:
for (var i = 0; i < arrayNumbers.length; i++)
為什么呢?因?yàn)閿?shù)組arrayNumbers
的長(zhǎng)度在每次遍歷的時(shí)候都會(huì)計(jì)算一次,這就造成了不必要的消耗。
注意,這個(gè)問(wèn)題其實(shí)在最新的Javascript引擎中已經(jīng)被修復(fù)了。
setTimeout()
及setInterval()
傳遞函數(shù)而不是字符串更好如果你給setTimeout()
或者setInterval()
傳遞字符串的話,那么它內(nèi)部的執(zhí)行機(jī)制其實(shí)是和eval()
是一樣的,這樣會(huì)比較慢。
錯(cuò)誤的用法,
setInterval('doSomethingPeriodically()', 1000);
setTimeout('doSomethingAfterFiveSeconds()', 5000);
正確的用法,
setInterval(doSomethingPeriodically, 1000);
setTimeout(doSomethingAfterFiveSeconds, 5000);
switch/case
來(lái)代替一坨if/else
當(dāng)判斷有超過(guò)兩個(gè)分支的時(shí)候使用switch/case
要更快一些,而且也更優(yōu)雅,更利于代碼的組織,當(dāng)然,如果有超過(guò)10個(gè)分支,就不要使用switch/case
了。
switch/case
中使用數(shù)字范圍進(jìn)行分界其實(shí)switch/case
中的case
條件,還可以這樣寫(xiě):
function getCategory(age) {
var category = "";
switch (true) {
case isNaN(age):
category = "not an age";
break;
case (age >= 50):
category = "Old";
break;
case (age <= 20):
category = "Baby";
break;
default:
category = "Young";
break;
};
return category;
}
getCategory(5); // will return "Baby"
下面的示例演示了可以給定對(duì)象作為參數(shù),來(lái)創(chuàng)建以此為原型的新對(duì)象:
function clone(object) {
function OneShotConstructor(){};
OneShotConstructor.prototype = object;
return new OneShotConstructor();
}
clone(Array).prototype ; // []
function escapeHTML(text) {
var replacements= {'<': '<', '>': '>', '&': '&', '"': '"'};
return text.replace(/[<>&"]/g, function(character) {
return replacements[character];
});
}
try...catch...finally
try...catch...finally
在捕獲一個(gè)異常時(shí),會(huì)創(chuàng)建一個(gè)運(yùn)行時(shí)環(huán)境的子作用域。而異常變量的生命周期僅限在這個(gè)運(yùn)行時(shí)的子作用域。
譯者注
這里我們可以使用閉包來(lái)保存這個(gè)運(yùn)行時(shí)的異常變量。
錯(cuò)誤的用法
var object = ['foo', 'bar'], i;
for (i = 0, len = object.length; i <len; i++) {
try {
// do something that throws an exception
} catch (e) {
// handle exception
}
}
正確的用法
var object = ['foo', 'bar'], i;
try {
for (i = 0, len = object.length; i <len; i++) {
// do something that throws an exception
}
} catch (e) {
// handle exception
}
XMLHttpRequests
時(shí)注意設(shè)置超時(shí)參數(shù)如果一個(gè)ajax請(qǐng)求長(zhǎng)時(shí)間沒(méi)有響應(yīng),我們應(yīng)該中止請(qǐng)求。否則瀏覽器將會(huì)一直等待。我們可以使用setTimeout()
來(lái)做一個(gè)定時(shí)器,
var xhr = new XMLHttpRequest ();
xhr.onreadystatechange = function () {
if (this.readyState == 4) {
clearTimeout(timeout);
// do something with response data
}
}
var timeout = setTimeout( function () {
xhr.abort(); // call error callback
}, 60*1000 /* timeout after a minute */ );
xhr.open('GET', url, true);
xhr.send();
除此之外,我們應(yīng)該避免同時(shí)發(fā)送多個(gè)同步的ajax請(qǐng)求。
一般地,WebSocket連接創(chuàng)建后,如果30秒內(nèi)沒(méi)有任何活動(dòng)服務(wù)器端會(huì)對(duì)連接進(jìn)行超時(shí)處理,防火墻也可以對(duì)單位周期內(nèi)沒(méi)有活動(dòng)的連接進(jìn)行超時(shí)處理。
為了防止超時(shí)中斷,你需要每隔一段時(shí)間發(fā)送一個(gè)心跳數(shù)據(jù)(空字符串)以保持websocket連接。下面的兩個(gè)方法一個(gè)用于周期的發(fā)送心跳數(shù)據(jù)保持連接,一個(gè)是取消心跳數(shù)據(jù)包。
var timerID = 0;
function keepAlive() {
var timeout = 15000;
if (webSocket.readyState == webSocket.OPEN) {
webSocket.send('');
}
timerId = setTimeout(keepAlive, timeout);
}
function cancelKeepAlive() {
if (timerId) {
cancelTimeout(timerId);
}
}
keepAlive()
方法應(yīng)該被添加在webSOcket
連接的onOpen()
方法的最后,而cancelKeepAlive()
添加在onClose()
方法的最后。
比如,
在有些時(shí)候,相比下面這種用法
var min = Math.min(a,b);
A.push(v);
可能在性能方面會(huì)比不上下面的做法
var min = a < b ? a : b;
A[A.length] = v;
更多建議: