如你所知,JavaScript是世界上第一的编程语言(编者注:2013年最后的一天就不要起圣战了=_=),它是Web的语言,是移动混合应用(mobile hybrid apps)的语言(好比 PhoneGap或者 Appcelerator),是服务器端的语言(好比 NodeJS或者 Wakanda),而且拥有不少其余的实现。同时它也是不少新手的启蒙语言,由于它不但能够在浏览器上显示一个简单的alert信息,并且还能够用来控制一个机器人(使用 nodebot,或者 nodruino)。掌握JavaScript而且可以写出组织规范并性能高效的代码的开发人员,已经成为人才市场上的猎寻对象。javascript
在这篇文章中,我将分享一组JavaScript的技巧、窍门和最佳实践,这些都是JavaScript程序员应该知晓的,无论他们是使用在浏览器/引擎上,仍是服务器端(SSJS——Service Side JavaScript)JavaScript解释器上。html
译者注:原文做者总共写了44条(漏写了第3条),译者本身补了一条以为比较重要的技巧。java
须要注意的是,这篇文章中的代码片断都在最新的Google Chrome(版本号30)上测试过,它使用V8 JavaScript引擎(V8 3.20.17.15)node
var
关键字给一个未定义的变量赋值会致使建立一个全局变量。要避免全局变量。git
===
,而不是==
==
(或!=
)操做符在须要的时候会自动执行类型转换。===
(或!==
)操做不会执行任何转换。它将比较值和类型,并且在速度上也被认为优于==
。程序员
[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
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; }; }
在语句结尾处使用分号是一个很好的实践。若是你忘记写了你也不会被警告,由于多数状况下 JavaScript 解释器会帮你加上分号。github
function Person(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } var Saad = new Person("Saad", "Mousliki");
typeof
、instanceof
和 constructor
var arr = ["a", "b", "c"]; typeof arr; // return "object" arr instanceof Array // true arr.constructor(); //[]
这个常常被称为自调用匿名函数(Self-Invoked Anonymous Function)或者即时调用函数表达式(IIFE-Immediately Invoked Function Expression)。这是一个在建立后当即自动执行的函数,一般以下:web
(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)];
这个代码片断在你想要生成测试数据的时候很是有用,好比一个在最小最大值之间的一个随机薪水值。编程
var x = Math.floor(Math.random() * (max - min + 1)) + min;
var numbersArray = [] , max = 100; for( var i=1; numbersArray.push(i++) < max;); // numbers = [0,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); }
【译者注:特地查了一下Math.random()
生成 0 到 1 之间的随机数,number.toString(36)
是将这个数字转换成36进制(0-9,a-z),最后`substr 去掉前面的“0.”字符串】segmentfault
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] */
在Java、C#、PHP和不少其余语言中都有一个经典的 trim 函数,用来去除字符串中的空格符,而在JavaScript中并无,因此咱们须要在String对象上加上这个函数。
String.prototype.trim = function(){return this.replace(/^\s+|\s+$/g, "");};
译者注:去掉字符串的先后空格,不包括字符串内部空格
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] */
译者注:其实concat能够直接实现两个数组的链接,可是它的返回值是一个新的数组。这里是直接改变array1
var argArray = Array.prototype.slice.call(arguments);
译者注:arguments
对象是一个类数组对象,但不是一个真正的数组
function isNumber(n){ return !isNaN(parseFloat(n)) && isFinite(n); }
function isArray(obj){ return Object.prototype.toString.call(obj) === '[object Array]' ; }
注意:若是 toString()
方法被重写了(overridden),你使用这个技巧就不能获得想要的结果了。或者你可使用:
Array.isArray(obj); // 这是一个新的array的方法
若是你不在使用多重frames的状况下,你还可使用 instanceof
方法。但若是你有多个上下文,你就会获得错误的结果。
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
译者注:关于如何判断数组网上有很多讨论,你们能够google一下。这篇就写的挺详细的。
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方法传递参数的技巧
var myArray = [12 , 222 , 1000 ]; myArray.length = 0; // myArray will be equal to [].
delete
来删除一个数组中的项。使用 splice 而不要使用 delete 来删除数组中的某个项。使用 delete 只是用 undefined 来替换掉原有的项,并非真正的从数组中删除。
不要使用这种方式:
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 will be equal to [12, 548, "a", 5478, "foo", 8852, undefined × 1, "Doe", 2154, 119] */
delete 方法应该被用来删除一个对象的某个属性。
跟上面的清空数组的方式相似,咱们使用 length 属性来截短一个数组。
var myArray = [12 , 222 , 1000 , 124 , 98 , 10 ]; myArray.length = 4; // myArray will be equal to [12 , 222 , 1000 , 124].
此外,若是你将一个数组的 length
设置成一个比如今大的值,那么这个数组的长度就会被改变,会增长新的 undefined 的项补上。 数组的 length 不是一个只读属性。
myArray.length = 10; // the new array length is 10 myArray[myArray.length - 1] ; // undefined
var foo = 10; foo == 10 && doSomething(); // 等价于 if (foo == 10) doSomething(); foo == 5 || doSomething(); // 等价于 if (foo != 5) doSomething();
逻辑 AND 还能够被使用来为函数参数设置默认值
function doSomething(arg1){ Arg1 = arg1 || 10; // 若是arg1没有被设置的话,Arg1将被默认设成10 }
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
0.1 + 0.2 === 0.3 // is false 9007199254740992 + 1 // is equal to 9007199254740992 9007199254740992 + 2 // is equal to 9007199254740994
为何会这样? 0.1+0.2 等于 0.30000000000000004。你要知道,全部的 JavaScript 数字在内部都是以 64 位二进制表示的浮点数,符合IEEE 754 标准。更多的介绍,能够阅读这篇博文。你可使用 toFixed()
和 toPrecision()
方法解决这个问题。
下面的代码片断可以避免在遍历一个对象属性的时候访问原型的属性
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选择器,咱们最好缓存这些DOM元素。
var navright = document.querySelector('#right'); var navleft = document.querySelector('#left'); var navup = document.querySelector('#up'); var navdown = document.querySelector('#down');
isFinite(0/0) ; // false isFinite("foo"); // false isFinite("10"); // true isFinite(10); // true isFinite(undifined); // 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]
确保调用 indexOf 时的参数不是负数。
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
构造函数使用 eval 和 Function 构造函数是很是昂贵的操做,由于每次他们都会调用脚本引擎将源代码转换成可执行代码。
var func1 = new Function(functionCode); var func2 = eval(functionCode);
with()
使用 with()
会插入一个全局变量。所以,同名的变量会被覆盖值而引发没必要要的麻烦。
避免使用这样的方式:
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 (var i = 0; i < arrayNumbers.length; i++)
为何?由于 arrayNumbers.length
每次循环的时候都会被计算。
setTimeout()
和 setInterval()
的时候传入函数,而不是字符串。若是你将字符串传递给 setTimeout()
或者 setInterval()
,这个字符串将被如使用 eval 同样被解析,这个是很是耗时的。
不要使用:
setInterval('doSomethingPeriodically()', 1000); setTimeOut('doSomethingAfterFiveSeconds()', 5000)
而用:
setInterval(doSomethingPeriodically, 1000); setTimeOut(doSomethingAfterFiveSeconds, 5000);
switch/case
语句,而不是一长串的 if/else
在判断状况大于2种的时候,使用 switch/case
更高效,并且更优雅(更易于组织代码)。但在判断的状况超过10种的时候不要使用 switch/case
。
译者注:查了一下文献,你们能够看一下这篇介绍
在下面的这种状况,使用 switch/case 判断数值范围的时候是合理的:
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"
译者注:通常对于数值范围的判断,用 if/else 会比较合适。 switch/case 更适合对肯定数值的判断
prototype
对象写一个函数来建立一个以指定参数做为 prototype
的对象是有可能:
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
在运行时,每次当 catch
从句被执行的时候,被捕获的异常对象会赋值给一个变量,而在 try-catch-finally
结构中,每次都会新建这个变量。
避免这样的写法:
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
设置超时。在一个XHR请求占用很长时间后(好比因为网络问题),你可能须要停止此次请求,那么你能够对XHR调用配套使用 setTimeout()。
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();
此外,通常你应该彻底避免同步的 Ajax 请求。
一般,在一个 WebSocket 链接建立以后,若是你没有活动的话,服务器会在30秒以后断开(time out)你的链接。防火墙也会在一段时间不活动以后断开链接。
为了防止超时的问题,你可能须要间歇性地向服务器端发送空消息。要这样作的话,你能够在你的代码里添加下面的两个函数:一个用来保持链接,另外一个用来取消链接的保持。经过这个技巧,你能够控制超时的问题。
使用一个 timerID:
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()
方法的最后。
举例来讲,不使用:
var min = Math.min(a,b); A.push(v);
而用:
var min = a < b ? a b; A[A.length] = v;
我知道还有不少其余的技巧,窍门和最佳实践,因此若是你有其余想要添加或者对我分享的这些有反馈或者纠正,请在评论中指出。
在这篇文章中,我使用了一些我本身的代码片断,也有一些代码片断来自别人的文章或者论坛:
原文:45 Useful JavaScript Tips, Tricks and Best Practices
转载翻译自: 伯乐在线 - Owen Chen