原文:https://colobu.com/2014/09/23/45-Useful-JavaScript-Tips,-Tricks-and-Best-Practices/
目录 [−]
-
列表
- 第一次给变量赋值时莫忘使用 var关键字.
- 使用 === 而不是 ==
- undefined, null, 0, false, NaN, '' (empty string) 都是false.
- 行尾使用分号
- 创建对象的构造函数
- 使用 typeof, instanceof 和 constructor要万分小心.
- 创建一个自调用的函数 Self-calling Function
- 从数组中随机选取一个元素
- 得到一个特定范围的随机值
- 生成一个[0,max]范围的数组
- 生成随机字符/数字的数组
- 打乱数组
- 字符串的trim函数
- 将一个数组附加到另外一个数组上: append函数
- 将arguments 对象转为数组
- 校验参数是否为数字
- 校验参数是否为数组
- 得到数组的最大或者最小元素
- 清空数组
- 不要使用delete方法删除数组的元素
- 设置length实现截短数组
- 使用逻辑 AND/ OR 作为条件判断
- 使用map()函数遍历数组
- 四舍五入, 保留 N位小数
- 浮点数问题
- 使用for-in循环检查对象的属性时需要注意
- 逗号操作符
- 缓存需要查询或者计算的变量
- 传给isFinite()的参数需要校验
- 避免数组的索引为负值
- JSON的序列化和反序列化
- 避免使用 eval() 或者Function的构造函数
- 避免使用 with() (The good part)
- 避免使用for-in遍历数组
- 调用setTimeout() 和 setInterval()时传入函数而不是函数的字符串名字
- 使用 switch/case statement 而不是一堆的 if/else
- 使用数字返回做switch/case 的条件判断
- 为创建的对象指定prototype
- HTML 转义函数
- 不要在循环内部使用try-catch-finally
- 为XMLHttpRequests设置超时
- 处理WebSocket 超时
- 牢记,原始运算符始终比函数调用要高效。使用VanillaJS。
- 编码时不要忘记使用代码美化工具. 发布前使用JSLint 和 minification (如JSMin).
- JavaScript如此美好,快来看看学习的一些资源吧
如你所知,JavaScript是世上编程语言的Number One (编者按: 原文如此), 用来编写Web和移动混合应用(比如PhoneGap或者Appcelerator), 也可以编写服务器端的程序(比如NodeJS或者Wakanda),并且拥有很多其他的实现。 它也是很多新手进入编程世界的启蒙语言,因为它不但可以在浏览器上显示一个简单的alert信息,而且还可以用来控制一个机器人(使用nodebot,或者nodruino)。掌握JavaScript并且能够写出规范并性能高效代码的开发人员,已经成为人才市场上的猎寻目标。
在这篇文章中,Saad Mousliki将分享一组JavaScript的技巧、窍门和最佳实践,这些都是JavaScript程序员应该知晓的,不管他们是使用在浏览器/引擎上,还是服务器端(SSJS Service Side JavaScript)JavaScript解释器上。
需要注意的是,这篇文章中的代码片段都在最新的Google Chrome(版本号30)上测试过,它使用V8 JavaScript引擎(V8 3.20.17.15)
英文原址: 45 Useful JavaScript Tips, Tricks and Best Practices
列表
第一次给变量赋值时莫忘使用 var
关键字.
给一个未声明的变量赋值会自动产生一个全局的变量。 避免全局变量。
使用 === 而不是 ==
操作符 == (或者 !=) 执行自动的类型转换. 操作符 === (或 !==) 不会执行任何转换,它会比较值和类型, 并且被认为比 == 更快.
1
2
3
4
5
6
7
8
|
[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, '' (empty string) 都是false.
行尾使用分号
行尾使用分号是一个好的习惯。 尽管如果忘了加也不会被警告, 因为大部分情况 JavaScript解析器会自动加上。 这篇文章描述了为什么要加分号的细节: http://davidwalsh.name/javascript-semicolons.
创建对象的构造函数
1
2
3
4
5
|
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
var Saad = new Person("Saad", "Mousliki");
|
使用 typeof, instanceof 和 constructor要万分小心.
- typeof : 一个JavaScript的一元操作符, 返回代表一个变量的原始primitive类型的字符串。 别忘了typeof null 返回 “object”, 并且大部分的object类型 (Array, Date, and others) 也返回 “object”.
- constructor : 一个内部prototype属性, 可以被覆盖。
- instanceof : 另一个JavaScript运算符, 用来在所有的prototype链的constructor。 如果找到返回true,否则false.
1
2
3
4
|
var arr = ["a", "b", "c"];
typeof arr; // return "object"
arr instanceof Array // true
arr.constructor(); //[]
|
创建一个自调用的函数 Self-calling Function
常被称作自调用匿名函数Self-Invoked Anonymous Function或者立即调用函数表达式Immediately Invoked Function Expression (IIFE). 它是这样一个函数:当创建时会自动执行。格式如下:
1
2
3
4
5
6
7
|
(function(){
// some private code that will be executed automatically
})();
(function(a,b){
var result = a+b;
return result;
})(10,20)
|
从数组中随机选取一个元素
1
2
3
|
var items = [12, 548 , 'a' , 2 , 5478 , 'foo' , 8852, , 'Doe' , 2145 , 119];
var randomItem = items[Math.floor(Math.random() * items.length)];
|
得到一个特定范围的随机值
This code snippet can be useful when trying to generate fake data for testing purposes, such as a salary between min and max.
1
|
var x = Math.floor(Math.random() * (max - min + 1)) + min;
|
生成一个[0,max]范围的数组
1
2
|
var numbersArray = [] , max = 100;
for( var i=1; numbersArray.push(i++) < max;); // numbers = [1,2,3 ... 100]
|
javascript
生成随机字符/数字的数组
1
2
3
4
5
6
|
function generateRandomAlphaNum(len) {
var rdmString = "";
for( ; rdmString.length < len; rdmString += Math.random().toString(36).substr(2));
return rdmString.substr(0, len);
}
|
打乱数组
1
2
3
|
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] */
|
A better option could be to implement a random sort order by code (e.g. : Fisher-Yates shuffle), than using the native sort JavaScript function. For more details take a look to this discussion.
字符串的trim函数
The classic trim function of Java, C#, PHP and many other language that remove whitespace from a string doesn’t exist in JavaScript, so we could add it to the String object.
1
|
String.prototype.trim = function(){return this.replace(/^\s+|\s+$/g, "");};
|
A native implementation of the trim() function is available in the recent JavaScript engines.
将一个数组附加到另外一个数组上: append函数
1
2
3
4
5
|
var array1 = [12 , "foo" , {name "Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 will be equal to [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
|
将arguments 对象转为数组
1
|
var argArray = Array.prototype.slice.call(arguments);
|
校验参数是否为数字
1
2
3
|
function isNumber(n){
return !isNaN(parseFloat(n)) && isFinite(n);
}
|
读者提供了另外一个技巧
1
2
3
|
function isNumber(n) {
return n === +n;
}
|
校验参数是否为数组
1
2
3
|
function isArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]' ;
}
|
Note that if the toString() method is overridden, you will not get the expected result using this trick.
Or use…
1
|
Array.isArray(obj); // its a new Array method
|
You could also use instanceof if you are not working with multiple frames. However, if you have many contexts, you will get a wrong result.
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]
// instanceof will not work correctly, myArray loses his constructor
// constructor is not shared between frames
arr instanceof Array; // false
|
得到数组的最大或者最小元素
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);
|
清空数组
1
2
|
var myArray = [12 , 222 , 1000 ];
myArray.length = 0; // myArray will be equal to [].
|
不要使用delete方法删除数组的元素
Use splice instead of using delete to delete an item from an array. Using delete replaces the item with undefined instead of the removing it from the array.
Instead of…
1
2
3
4
5
|
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] */
|
Use…
1
2
3
4
5
|
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 will be equal to [12, 548, "a", 5478, "foo", 8852, undefined × 1, "Doe", 2154, 119] */
|
The delete method should be used to delete an object property.
设置length实现截短数组
Like the previous example of emptying an array, we truncate it using the length property.
1
2
|
var myArray = [12 , 222 , 1000 , 124 , 98 , 10 ];
myArray.length = 4; // myArray will be equal to [12 , 222 , 1000 , 124].
|
As a bonus, if you set the array length to a higher value, the length will be changed and new items will be added with undefined as a value. The array length is not a read only property.
1
2
|
myArray.length = 10; // the new array length is 10
myArray[myArray.length - 1] ; // undefined
|
使用逻辑 AND/ OR 作为条件判断
1
2
3
|
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();
|
The logical OR could also be used to set a default value for function argument.
1
2
3
|
function doSomething(arg1){
arg1 = arg1 || 10; // arg1 will have 10 as a default value if it’s not already set
}
|
使用map()函数遍历数组
1
2
3
4
|
var squares = [1,2,3,4].map(function (val) {
return val * val;
});
// squares will be equal to [1, 4, 9, 16]
|
四舍五入, 保留 N位小数
1
2
|
var num =2.443242342;
num = num.toFixed(4); // num will be equal to 2.4432
|
NOTE : the toFixed() function returns a string and not a number.
浮点数问题
1
2
3
|
0.1 + 0.2 === 0.3 // is false
9007199254740992 + 1 // is equal to 9007199254740992
9007199254740992 + 2 // is equal to 9007199254740994
|
Why does this happen? 0.1 +0.2 is equal to 0.30000000000000004. What you need to know is that all JavaScript numbers are floating points represented internally in 64 bit binary according to the IEEE 754 standard. For more explanation, take a look to this blog post.
You can use toFixed() and toPrecision() to resolve this problem.
使用for-in循环检查对象的属性时需要注意
This code snippet could be useful in order to avoid iterating through the properties from the object’s prototype.
1
2
3
4
5
|
for (var name in object) {
if (object.hasOwnProperty(name)) {
// do something with name
}
}
|
逗号操作符
1
2
3
4
|
var a = 0;
var b = ( a++, 99 );
console.log(a); // a will be equal to 1
console.log(b); // b is equal to 99
|
缓存需要查询或者计算的变量
In the case of a jQuery selector, we could cache the DOM element.
1
2
3
4
|
var navright = document.querySelector('#right');
var navleft = document.querySelector('#left');
var navup = document.querySelector('#up');
var navdown = document.querySelector('#down');
|
传给isFinite()的参数需要校验
1
2
3
4
5
6
7
|
isFinite(0/0) ; // false
isFinite("foo"); // false
isFinite("10"); // true
isFinite(10); // true
isFinite(undefined); // false
isFinite(); // false
isFinite(null); // true !!!
|
避免数组的索引为负值
1
2
3
|
var numbersArray = [1,2,3,4,5];
var from = numbersArray.indexOf("foo") ; // from is equal to -1
numbersArray.splice(from,2); // will return [5]
|
Make sure that the arguments passed to splice are not negative.
JSON的序列化和反序列化
1
2
3
4
5
|
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() 或者Function的构造函数
Use of eval or the Function constructor are expensive operations as each time they are called script engine must convert source code to executable code.
1
2
|
var func1 = new Function(functionCode);
var func2 = eval(functionCode);
|
避免使用 with() (The good part)
Using with() inserts a variable at the global scope. Thus, if another variable has the same name it could cause confusion and overwrite the value.
避免使用for-in遍历数组
Instead of using…
1
2
3
4
|
var sum = 0;
for (var i in arrayNumbers) {
sum += arrayNumbers[i];
}
|
…it’s better to use…
1
2
3
4
|
var sum = 0;
for (var i = 0, len = arrayNumbers.length; i < len; i++) {
sum += arrayNumbers[i];
}
|
As a bonus, the instantiation of i and len is executed once because it’s in the first statement of the for loop. Thsi is faster than using…
1
|
for (var i = 0; i < arrayNumbers.length; i++)
|
Why? The length of the array arrayNumbers is recalculated every time the loop iterates.
NOTE : the issue of recalculating the length in each iteration was fixed in the latest JavaScript engines.
调用setTimeout() 和 setInterval()时传入函数而不是函数的字符串名字
If you pass a string into setTimeout() or setInterval(), the string will be evaluated the same way as with eval, which is slow. Instead of using…
1
2
|
setInterval('doSomethingPeriodically()', 1000);
setTimeout('doSomethingAfterFiveSeconds()', 5000);
|
…use…
1
2
|
setInterval(doSomethingPeriodically, 1000);
setTimeout(doSomethingAfterFiveSeconds, 5000);
|
使用 switch/case statement 而不是一堆的 if/else
Using switch/case is faster when there are more than 2 cases, and it is more elegant (better organized code). Avoid using it when you have more than 10 cases.
使用数字返回做switch/case 的条件判断
Using a switch/case statement with numeric ranges is possible with this trick.
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 = "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"
|
为创建的对象指定prototype
It’s possible to write a function that creates an object whose prototype is the given argument like this…
1
2
3
4
5
6
7
|
function clone(object) {
function OneShotConstructor(){};
OneShotConstructor.prototype= object;
return new OneShotConstructor();
}
clone(Array).prototype ; // []
|
HTML 转义函数
1
2
3
4
5
6
|
function escapeHTML(text) {
var replacements= {"<": "<", ">": ">","&": "&", "\"": """};
return text.replace(/[<>&"]/g, function(character) {
return replacements[character];
});
}
|
不要在循环内部使用try-catch-finally
The try-catch-finally construct creates a new variable in the current scope at runtime each time the catch clause is executed where the caught exception object is assigned to a variable.
Instead of using…
1
2
3
4
5
6
7
8
9
|
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
}
}
|
…use…
1
2
3
4
5
6
7
8
9
|
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设置超时
You could abort the connection if an XHR takes a long time (for example, due to a network issue), by using setTimeout() with the XHR call.
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);
// 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();
|
As a bonus, you should generally avoid synchronous XHR calls completely.
处理WebSocket 超时
Generally when a WebSocket connection is established, a server could time out your connection after 30 seconds of inactivity. The firewall could also time out the connection after a period of inactivity.
To deal with the timeout issue you could send an empty message to the server periodically. To do this, add these two functions to your code: one to keep alive the connection and the other one to cancel the keep alive. Using this trick, you’ll control the timeout.
Add a 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);
}
}
|
The keepAlive() function should be added at the end of the onOpen() method of the webSocket connection and the cancelKeepAlive() at the end of the onClose() method.
牢记,原始运算符始终比函数调用要高效。使用VanillaJS。
For example, instead of using…
1
2
3
4
5
6
7
|
var min = Math.min(a,b);
A.push(v);
…use…
var min = a < b ? a : b;
A[A.length] = v;
|
编码时不要忘记使用代码美化工具. 发布前使用JSLint 和 minification (如JSMin).
JavaScript如此美好,快来看看学习的一些资源吧
Code Academy JavaScript tracks: http://www.codecademy.com/tracks/javascript
Eloquent JavaScript by Marjin Haverbeke: http://eloquentjavascript.net/
Advanced JavaScript by John Resig: http://ejohn.org/apps/learn/
结尾
我(Saad Mousliki)知道还有许许多多的技巧窍门和最佳实践。 所以如果你有更多想增加的,或者针对以上条目的反馈和更正, 请添加注释。
参考
本文大大部分的代码都是我(Saad Mousliki)自己写的。 有部分的代码片段参考了一些其它文章或者论坛的帖子。
- JavaScript Performance Best Practices (CC)
- Google Code JavaScript tips
- * tips and tricks
- TimeOut for XHRfu
附加技巧
除了前面文章中提到的技巧, 我也在这篇文章列出收集的更多的JavaScript技巧
两个感叹号
一个元素转换为真的布尔值,一般用来判断某个元素是否存在,例如:
1
2
3
4
5
|
1.!![] true; ![] false
2.!!{} true; !{} false
3.!!false false; !false true
4.!!true true; !true false
5.!!undefined false; !undefined true
|
双感叹号才能真正的将他转换成对应的Boolean值,第一个感叹号是将其转化成Boolean类型的值,但是这一操作得到的是其取反以后的值,在进行一次取反运算才能得到其对应真正的布尔值
把数字变字符串, 把字符串变数字
把s变数字:s = s - 0;
或者 s = +s;
把n变字符串:n = n + "";
把数组转换成CSV字符串
1
2
3
4
5
|
var fruits = ['apple', 'peaches', 'oranges', 'mangoes'];
var str = fruits.valueOf();
//print str: apple,peaches,oranges,mangoes
|
如果不想逗号分隔:
1
2
3
4
5
|
var fruits = ['apple', 'peaches', 'oranges', 'mangoes'];
var str = fruits.join("|");
//print str: apple|peaches|oranges|mangoes
|
转换CSV字符串为数组
使用split
方法:
1
2
3
4
5
|
var str = "apple, peaches, oranges, mangoes";
var fruitsArray = str.split(",");
//print fruitsArray[0]: apple
|
移除数组指定索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function removeByIndex(arr, index) {
arr.splice(index, 1);
}
test = new Array();
test[0] = 'Apple';
test[1] = 'Ball';
test[2] = 'Cat';
test[3] = 'Dog';
alert("Array before removing elements: "+test);
removeByIndex(test, 2);
alert("Array after removing elements: "+test);
|
移除数组特定的值
假定数组的值不会重复。如果有重复且想删除所有的重复的值, 注释掉break
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function removeByValue(arr, val) {
for(var i=0; i<arr.length; i++) {
if(arr[i] === val) {
arr.splice(i, 1);
break;
}
}
}
var somearray = ["mon", "tue", "wed", "thur"]
removeByValue(somearray, "tue");
//somearray will now have "mon", "wed", "thur"
|
下面的方法是为Array类增加removeByValue方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Array.prototype.removeByValue = function(val) {
for(var i=0; i<this.length; i++) {
if(this[i] == val) {
this.splice(i, 1);
break;
}
}
}
//..
var somearray = ["mon", "tue", "wed", "thur"]
somearray.removeByValue("tue");
//somearray will now have "mon", "wed", "thur"
|
根据方法名调用方法
1
2
3
4
5
6
7
8
|
var strFun = "someFunction"; //Name of the function to be called
var strParam = "this is the parameter"; //Parameters to be passed in function
//Create the function
var fn = window[strFun];
//Call the function
fn(strParam);
|
检查Form 是否dirty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/**
* Determines if a form is dirty by comparing the current value of each element
* with its default value.
*
* @param {Form} form the form to be checked.
* @return {Boolean} <code>true</code> if the form is dirty, <code>false</code>
* otherwise.
*/
function formIsDirty(form) {
for (var i = 0; i < form.elements.length; i++) {
var element = form.elements[i];
var type = element.type;
if (type == "checkbox" || type == "radio") {
if (element.checked != element.defaultChecked) {
return true;
}
}
else if (type == "hidden" || type == "password" ||
type == "text" || type == "textarea") {
if (element.value != element.defaultValue) {
return true;
}
}
else if (type == "select-one" || type == "select-multiple") {
for (var j = 0; j < element.options.length; j++) {
if (element.options[j].selected !=
element.options[j].defaultSelected) {
return true;
}
}
}
}
return false;
}
|
检查字符串是否包含子串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(obj, start) {
for (var i = (start || 0), j = this.length; i < j; i++) {
if (this[i] === obj) { return i; }
}
return -1;
}
}
if (!String.prototype.contains) {
String.prototype.contains = function (arg) {
return !!~this.indexOf(arg);
};
}
|
移除数组的重复数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function removeDuplicates(arr) {
var temp = {};
for (var i = 0; i < arr.length; i++)
temp[arr[i]] = true;
var r = [];
for (var k in temp)
r.push(k);
return r;
}
//Usage
var fruits = ['apple', 'orange', 'peach', 'apple', 'strawberry', 'orange'];
var uniquefruits = removeDuplicates(fruits);
//print uniquefruits ['apple', 'orange', 'peach', 'strawberry'];
|
记不住apply
和call
的区别
*:
Think of a in apply for array of args and c in call for columns of args.
双波浪号~~是取整
http://rocha.la/JavaScript-bitwise-operators-in-practice :
单波浪号是按位非,双波浪号是取整。
1
2
3
|
~~2 === Math.floor(2); //true, 2
~~2.4 === Math.floor(2); //true, 2
~~3.9 === Math.floor(3); //true, 3
|
参考文档: