您的位置:首页 > Web前端 > JavaScript

45个实用的JavaScript技巧、窍门和最佳实践

2014-06-20 23:03 387 查看
本文由伯乐在线-陈鑫伟翻译自flippinawesome。欢迎加入技术翻译小组。转载请参见文章末尾处的要求。

如你所知,JavaScript是世界上第一的编程语言,它是Web的语言,是移动混合应用(mobilehybridapps)的语言(比如PhoneGap或者Appcelerator),是服务器端的语言(比如NodeJS或者Wakanda),并且拥有很多其他的实现。同时它也是很多新手的启蒙语言,因为它不但可以在浏览器上显示一个简单的alert信息,而且还可以用来控制一个机器人(使用nodebot,或者nodruino)。掌握JavaScript并且能够写出组织规范并性能高效的代码的开发人员,已经成为人才市场上的猎寻对象。



在这篇文章中,我将分享一组JavaScript的技巧、窍门和最佳实践,这些都是JavaScript程序员应该知晓的,不管他们是使用在浏览器/引擎上,还是服务器端(SSJS——ServiceSideJavaScript)JavaScript解释器上。

【译者注:原文作者总共写了44条(漏写了第3条),译者自己补了一条觉得比较重要的技巧。】

需要注意的是,这篇文章中的代码片段都在最新的GoogleChrome(版本号30)上测试过,它使用V8JavaScript引擎(V83.20.17.15)

1–在第一次给一个变量赋值的时候不要忘记使用
var
关键字


给一个未定义的变量赋值会导致创建一个全局变量。要避免全局变量。

2–使用===,而不是==

==(或!=)操作符在需要的时候会自动执行类型转换。===(或!==)操作不会执行任何转换。它将比较值和类型,而且在速度上也被认为优于==。

1
2
3
4
5
6
7
8

[
10
]===
10
//isfalse

[
10
]==
10
//istrue

'10'
==
10
//istrue

'10'
===
10
//isfalse

[]==
0
//istrue

[]===
0
//isfalse

''
==
false
//istruebuttrue=="a"isfalse

''
===
false
//isfalse


3–使用闭包实现私有变量(译者添加)

1
2
3
4
5
6
7
8
9
10
11
12

function
Person(name,age){

this
.getName=
function
(){
return
name;};

this
.setName=
function
(newName){name=newName;};

this
.getAge=
function
(){
return
age;};

this
.setAge=
function
(newAge){age=newAge;};


//未在构造函数中初始化的属性

var
occupation;

this
.getOccupation=
function
(){
return
occupation;};

this
.setOccupation=
function
(newOcc){occupation=

newOcc;};

}


4–在语句结尾处使用分号

在语句结尾处使用分号是一个很好的实践。如果你忘记写了你也不会被警告,因为多数情况下JavaScript解释器会帮你加上分号。

5–创建对象的构造函数

1
2
3
4
5
6

function
Person(firstName,lastName){

this
.firstName=firstName;

this
.lastName=lastName;

}


var
Saad=
new
Person(
"Saad"
,
"Mousliki"
);


6–小心使用typeof、instanceof和constructor

1
2
3
4

var
arr=[
"a"
,
"b"
,
"c"
];

typeof
arr;
//return"object"

arr
instanceof
Array
//true

arr.constructor();
//[]


7–创建一个自调用函数(Self-callingFuntion)

这个经常被称为自调用匿名函数(Self-InvokedAnonymousFunction)或者即时调用函数表达式(IIFE-ImmediatelyInvokedFunctionExpression)。这是一个在创建后立即自动执行的函数,通常如下:

1
2
3
4
5
6
7

(
function
(){

//someprivatecodethatwillbeexecutedautomatically

})();

(
function
(a,b){

var
result=a+b;

return
result;

})(
10
,
20
)


8-从数组中获取一个随机项

1
2
3

var
items=[
12
,
548
,
'a'
,
2
,
5478
,
'foo'
,
8852
,,
'Doe'
,
2145
,
119
];


var
randomItem=items[Math.floor(Math.random()*items.length)];


9–在特定范围内获取一个随机数

这个代码片段在你想要生成测试数据的时候非常有用,比如一个在最小最大值之间的一个随机薪水值。

1

var
x=Math.floor(Math.random()*(max-min+
1
))+min;


10–在0和设定的最大值之间生成一个数字数组

1
2
3

var
numbersArray=[],max=
100
;


for
(
var
i=
1
;numbersArray.push(i++)<max;);
//numbers=[0,1,2,3...100]


11–生成一个随机的数字字母字符串

1
2
3
4
5

function
generateRandomAlphaNum(len){

var
rdmstring=
""
;

for
(;rdmString.length<len;rdmString+=Math.random().toString(
36
).substr(
2
));

return
rdmString.substr(
0
,len);

}


【译者注:特意查了一下Math.random()生成0到1之间的随机数,number.toString(36)是将这个数字转换成36进制(0-9,a-z),最后substr去掉前面的“0.”字符串】

12–打乱一个数字数组

1
2
3

var
numbers=[
5
,
458
,
120
,-
215
,
228
,
400
,
122205
,-
85411
];

numbers=numbers.sort(
function
(){
return
Math.random()-
0.5
});

/*thearraynumberswillbeequalforexampleto[120,5,228,-215,400,458,-85411,122205]*/


13–String的trim函数

在Java、C#、PHP和很多其他语言中都有一个经典的trim函数,用来去除字符串中的空格符,而在JavaScript中并没有,所以我们需要在String对象上加上这个函数。

1

String
.prototype.trim=
function
(){
return
this
.replace(/^\s+|\s+$/g,
""
);};


【译者注:去掉字符串的前后空格,不包括字符串内部空格】

14–附加(append)一个数组到另一个数组上

1
2
3
4
5

var
array1=[
12
,
"foo"
,{name:
"Joe"
},-
2458
];


var
array2=[
"Doe"
,
555
,
100
];

Array
.prototype.push.apply(array1,array2);

/*array1willbeequalto[12,"foo",{name"Joe"},-2458,"Doe",555,100]*/


【译者注:其实concat可以直接实现两个数组的连接,但是它的返回值是一个新的数组。这里是直接改变array1】

15–将arguments对象转换成一个数组

1

var
argArray=
Array
.prototype.slice.call(arguments);


【译者注:arguments对象是一个类数组对象,但不是一个真正的数组】

16–验证参数是否是数字(number)

1
2
3

function
isNumber(n){

return
!
isNaN
(
parseFloat
(n))&&
isFinite
(n);

}


17–验证参数是否是数组

1
2
3

function
isArray(obj){

return
Object
.prototype.toString.call(obj)===
'[objectArray]'
;

}


注意:如果toString()方法被重写了(overridden),你使用这个技巧就不能得到想要的结果了。或者你可以使用:

1

Array
.isArray(obj);
//这是一个新的array的方法


如果你不在使用多重frames的情况下,你还可以使用instanceof方法。但如果你有多个上下文,你就会得到错误的结果。

1
2
3
4
5
6
7
8
9

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]


//instanceofwillnotworkcorrectly,myArrayloseshisconstructor

//constructorisnotsharedbetweenframes

arr
instanceof
Array
;
//false


【译者注:关于如何判断数组网上有不少讨论,大家可以google一下。这篇就写的挺详细的。】

18–获取一个数字数组中的最大值或最小值

1
2
3

var
numbers=[
5
,
458
,
120
,-
215
,
228
,
400
,
122205
,-
85411
];

var
maxInNumbers=Math.max.apply(Math,numbers);

var
minInNumbers=Math.min.apply(Math,numbers);


【译者注:这里使用了Function.prototype.apply方法传递参数的技巧】

19–清空一个数组

1
2

var
myArray=[
12
,
222
,
1000
];

myArray.length=
0
;
//myArraywillbeequalto[].


20–不要使用delete来删除一个数组中的项。

使用splice而不要使用delete来删除数组中的某个项。使用delete只是用undefined来替换掉原有的项,并不是真正的从数组中删除。

不要使用这种方式:

1
2
3
4
5

var
items=[
12
,
548
,
'a'
,
2
,
5478
,
'foo'
,
8852
,,
'Doe'
,
2154
,
119
];

items.length;
//return11

delete
items[
3
];
//returntrue

items.length;
//return11

/*itemswillbeequalto[12,548,"a",undefined×1,5478,"foo",8852,undefined×1,"Doe",2154,119]*/


而使用:

1
2
3
4
5

var
items=[
12
,
548
,
'a'
,
2
,
5478
,
'foo'
,
8852
,,
'Doe'
,
2154
,
119
];

items.length;
//return11

items.splice(
3
,
1
);

items.length;
//return10

/*itemswillbeequalto[12,548,"a",5478,"foo",8852,undefined×1,"Doe",2154,119]*/


delete方法应该被用来删除一个对象的某个属性。

21–使用length来截短一个数组

跟上面的清空数组的方式类似,我们使用length属性来截短一个数组。

1
2

var
myArray=[
12
,
222
,
1000
,
124
,
98
,
10
];

myArray.length=
4
;
//myArraywillbeequalto[12,222,1000,124].


此外,如果你将一个数组的length设置成一个比现在大的值,那么这个数组的长度就会被改变,会增加新的undefined的项补上。数组的length不是一个只读属性。

1
2

myArray.length=
10
;
//thenewarraylengthis10

myArray[myArray.length-
1
];
//undefined


22–使用逻辑AND/OR做条件判断

1
2
3

var
foo=
10
;

foo==
10
&&doSomething();
//等价于if(foo==10)doSomething();

foo==
5
||doSomething();
//等价于if(foo!=5)doSomething();


逻辑AND还可以被使用来为函数参数设置默认值

1
2
3

function
doSomething(arg1){

Arg1=arg1||
10
;
//如果arg1没有被设置的话,Arg1将被默认设成10

}


23–使用map()方法来遍历一个数组里的项

1
2
3
4

var
squares=[
1
,
2
,
3
,
4
].map(
function
(val){

return
val*val;

});

//squareswillbeequalto[1,4,9,16]


24–四舍五入一个数字,保留N位小数

1
2

var
num=
2.443242342
;

num=num.toFixed(
4
);
//numwillbeequalto2.4432


25–浮点数问题

1
2
3

0.1
+
0.2
===
0.3
//isfalse

9007199254740992
+
1
//isequalto9007199254740992

9007199254740992
+
2
//isequalto9007199254740994


为什么会这样?0.1+0.2等于0.30000000000000004。你要知道,所有的JavaScript数字在内部都是以64位二进制表示的浮点数,符合IEEE754标准。更多的介绍,可以阅读这篇博文。你可以使用toFixed()和toPrecision()方法解决这个问题。

26–使用for-in遍历一个对象内部属性的时候注意检查属性

下面的代码片段能够避免在遍历一个对象属性的时候访问原型的属性

1
2
3
4
5

for
(
var
name
in
object){

if
(object.hasOwnProperty(name)){

//dosomethingwithname

}

}


27–逗号操作符

1
2
3
4

var
a=
0
;

var
b=(a++,
99
);

console.log(a);
//awillbeequalto1

console.log(b);
//bisequalto99


28–缓存需要计算和查询(calculationorquerying)的变量

对于jQuery选择器,我们最好缓存这些DOM元素。

1
2
3
4

var
navright=document.querySelector(
'#right'
);

var
navleft=document.querySelector(
'#left'
);

var
navup=document.querySelector(
'#up'
);

var
navdown=document.querySelector(
'#down'
);


29–在调用isFinite()之前验证参数

1
2
3
4
5
6
7

isFinite
(
0
/
0
);
//false

isFinite
(
"foo"
);
//false

isFinite
(
"10"
);
//true

isFinite
(
10
);
//true

isFinite
(undifined);
//false

isFinite
();
//false

isFinite
(
null
);
//true!!!


30–避免数组中的负数索引(negativeindexes)

1
2
3

var
numbersArray=[
1
,
2
,
3
,
4
,
5
];

var
from=numbersArray.indexOf(
"foo"
);
//fromisequalto-1

numbersArray.splice(from,
2
);
//willreturn[5]


确保调用indexOf时的参数不是负数。

31–基于JSON的序列化和反序列化(serializationanddeserialization)

1
2
3
4
5

var
person={name:
'Saad'
,age:
26
,department:{ID:
15
,name:
"R&D"
}};

var
stringFromPerson=JSON.stringify(person);

/*stringFromPersonisequalto"{"name":"Saad","age":26,"department":{"ID":15,"name":"R&D"}}"*/

var
personFromString=JSON.parse(stringFromPerson);

/*personFromStringisequaltopersonobject*/


32–避免使用eval()和Function构造函数

使用eval和Function构造函数是非常昂贵的操作,因为每次他们都会调用脚本引擎将源代码转换成可执行代码。

1
2

var
func1=
new
Function(functionCode);

var
func2=eval(functionCode);


33–避免使用with()

使用with()会插入一个全局变量。因此,同名的变量会被覆盖值而引起不必要的麻烦。

34–避免使用for-in来遍历一个数组

避免使用这样的方式:

1
2
3
4

var
sum=
0
;

for
(
var
i
in
arrayNumbers){

sum+=arrayNumbers[i];

}


更好的方式是:

1
2
3
4

var
sum=
0
;

for
(
var
i=
0
,len=arrayNumbers.length;i<len;i++){

sum+=arrayNumbers[i];

}


附加的好处是,i和len两个变量的取值都只执行了一次,会比下面的方式更高效:

1

for
(
var
i=
0
;i<arrayNumbers.length;i++)


为什么?因为arrayNumbers.length每次循环的时候都会被计算。

35–在调用setTimeout()和setInterval()的时候传入函数,而不是字符串。

如果你将字符串传递给setTimeout()或者setInterval(),这个字符串将被如使用eval一样被解析,这个是非常耗时的。
不要使用:

1
2

setInterval(
'doSomethingPeriodically()'
,
1000
);

setTimeOut(
'doSomethingAfterFiveSeconds()'
,
5000
)


而用:

1
2

setInterval(doSomethingPeriodically,
1000
);

setTimeOut(doSomethingAfterFiveSeconds,
5000
);


36–使用switch/case语句,而不是一长串的if/else

在判断情况大于2种的时候,使用switch/case更高效,而且更优雅(更易于组织代码)。但在判断的情况超过10种的时候不要使用switch/case。
【译者注:查了一下文献,大家可以看一下这篇介绍】

37–在判断数值范围时使用switch/case

在下面的这种情况,使用switch/case判断数值范围的时候是合理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function
getCategory(age){

var
category=
""
;

switch
(
true
){

case
isNaN
(age):

category=
"notanage"
;

break
;

case
(age>=
50
):

category=
"Old"
;

break
;

case
(age<=
20
):

category=
"Baby"
;

break
;

default
:

category=
"Young"
;

break
;

};

return
category;

}

getCategory(
5
);
//willreturn"Baby"


【译者注:一般对于数值范围的判断,用if/else会比较合适。switch/case更适合对确定数值的判断】

38–为创建的对象指定prototype对象

写一个函数来创建一个以指定参数作为prototype的对象是有可能:

1
2
3
4
5
6

function
clone(object){

function
OneShotConstructor(){};

OneShotConstructor.prototype=object;

return
new
OneShotConstructor();

}

clone(
Array
).prototype;
//[]


39–一个HTML转义函数

1
2
3
4
5
6

function
escapeHTML(text){

var
replacements={
"<"
:
"<"
,
">"
:
">"
,
"&"
:
"&"
,
"\""
:
""
"};

return
text.replace(/[<>&"]/g,
function
(character){

return
replacements[character];

});

}


40–避免在循环内部使用try-catch-finally

在运行时,每次当catch从句被执行的时候,被捕获的异常对象会赋值给一个变量,而在try-catch-finally结构中,每次都会新建这个变量。

避免这样的写法:

1
2
3
4
5
6
7
8
9

var
object=[
'foo'
,
'bar'
],i;

for
(i=
0
,len=object.length;i<len;i++){

try
{

//dosomethingthatthrowsanexception

}

catch
(e){

//handleexception

}

}


而使用:

1
2
3
4
5
6
7
8
9

var
object=[
'foo'
,
'bar'
],i;

try
{

for
(i=
0
,len=object.length;i<len;i++){

//dosomethingthatthrowsanexception

}

}

catch
(e){

//handleexception

}


41–为XMLHttpRequests设置超时。

在一个XHR请求占用很长时间后(比如由于网络问题),你可能需要中止这次请求,那么你可以对XHR调用配套使用setTimeout()。

1
2
3
4
5
6
7
8
9
10
11
12
13

var
xhr=
new
XMLHttpRequest();

xhr.onreadystatechange=
function
(){

if
(
this
.readyState==
4
){

clearTimeout(timeout);

//dosomethingwithresponsedata

}

}

var
timeout=setTimeout(
function
(){

xhr.abort();
//callerrorcallback

},
60
*
1000
/*timeoutafteraminute*/
);

xhr.open(
'GET'
,url,
true
);


xhr.send();


此外,一般你应该完全避免同步的Ajax请求。

42–处理WebSocket超时

通常,在一个WebSocket连接创建之后,如果你没有活动的话,服务器会在30秒之后断开(timeout)你的连接。防火墙也会在一段时间不活动之后断开连接。

为了防止超时的问题,你可能需要间歇性地向服务器端发送空消息。要这样做的话,你可以在你的代码里添加下面的两个函数:一个用来保持连接,另一个用来取消连接的保持。通过这个技巧,你可以控制超时的问题。

使用一个timerID:

1
2
3
4
5
6
7
8
9
10
11
12
13

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()方法应该被添加在webSOcket连接的onOpen()方法的最后,而cancelKeepAlive()添加在onClose()方法的最后。

43–牢记,原始运算符始终比函数调用要高效。使用VanillaJS。

举例来说,不使用:

1
2

var
min=Math.min(a,b);

A.push(v);


而用:

1
2

var
min=a<b?ab;

A[A.length]=v;


44–编码的时候不要忘记使用代码整洁工具。在上线之前使用JSLint和代码压缩工具(minification)(比如JSMin)。《省时利器:代码美化与格式化工具》

45–JavaScript是不可思议的。最好的JavaScript学习资源。

总结

我知道还有很多其他的技巧,窍门和最佳实践,所以如果你有其他想要添加或者对我分享的这些有反馈或者纠正,请在评论中指出。

引用

在这篇文章中,我使用了一些我自己的代码片段,也有一些代码片段来自别人的文章或者论坛:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: