如何编写高质量的JavaScript代码(2)

  • for循环

  • 在for循环中你会去迭代一些数组元素或者一些HTML元素。for循环常常如此:

       
       
    1. for (var i = 0; i < myarray.length; i++) {
    2. // do something with myarray[i]
    3. }

    这样写的问题在于,每一次迭代的时候都会计算数组的长度,尤其在这个参数不是一个数组而是一组HTML元素的时候会降低你的程序的性能。

    HTML元素的集合在页面上,这样每次都会去再页面上查找相应的元素,这是非常耗时的。所以对于for循环,你需要预先保存数组的长度,这样写:

       
       
    1. for (var i = 0, max = myarray.length; i < max; i++) {
    2. // do something with myarray[i]
    3. }

    这样缓存了参数的长度,在每次迭代的时候就不用再去查找计算了。

    在查找HTML元素集合的时候,缓存参数长度可以带来可观的性能提升,Safari下面提高两倍的速度,在IE7下面提高190倍的速度。

    需要注意的是,当你需要操作修改DOM元素的数量的时候,你肯定希望这个值是随时更新的而不是一个常量。

    使用下面的单一var模式,你也可以把var提到循环之外:

       
       
    1. function looper() {
    2. var i = 0,
    3. max,
    4. myarray = [];
    5. // ...
    6. for (i = 0, max = myarray.length; i < max; i++) {
    7. // do something with myarray[i]
    8. }
    9. }

    这个模式可以增强整个代码的连续性,但是不好的一点是当你重构代码的时候复制粘贴就没那么容易了。例如:如果你想在其他函数中也使用这个循环,那你需要确定在新的函数中处理好了i和max(或许还需要删掉这个)。

    这个函数还有两个点可以优化的:

    ◆可以少一个变量(不需要max)

    ◆递减到0,一个数字与0比较比这个数字与另外一个数字比较更快

    所以就可以写为:

       
       
    1. var i, myarray = [];
    2. for (i = myarray.length; i--;) {
    3. // do something with myarray[i]
    4. }

    针对第二点:

       
       
    1. var myarray = [],
    2. i = myarray.length;
    3. while (i--) {
    4. // do something with myarray[i]
    5. }

    这是两个比较微小的点的优化。另外,JSLint可能对于i–会有意见。

    for-in循环

    for-in循环用来迭代非数组的对象。使用for-in循环通常也成为枚举。

    从技术上来说,你也可以用for-in来循环数组,因为数组也是对象,但是不推荐。如果数组有一些自定义的扩展函数,那么就会出错。另外,对象属性的顺序在for-in循环中也是不确定的。所以最好还是用普通的循环来循环数组用for-in来循环对象。

    在循环对象的过程中,使用hasOwnProperty()方法来检验是对象本身的属性还是原型链上的属性很重要。

    看看下面的这个例子。

       
       
    1. // the object
    2. var man = {
    3. hands: 2,
    4. legs: 2,
    5. heads: 1
    6. };
    7. // somewhere else in the code
    8. // a method was added to all objects
    9. if (typeof Object.prototype.clone === "undefined") {
    10. Object.prototype.clone = function () {};
    11. }

    在这个例子中,我们有一个简单的称作man的对象字面量。在其他man定义之前或之后的地方,对象原型有一个很有用的clone()方法。因为原型链的原因,所有的对象都自动获得了这个方法。为了在枚举man对象的时候出现clone方法,你需要使用hasOwnProperty方法来区别。如果没有区别来自原型链的方法,那么就会有一些意想不到的事情发生:

       
       
    1. // 1.
    2. // for-in loop
    3. for (var i in man) {
    4. if (man.hasOwnProperty(i)) { // filter
    5. console.log(i, ":", man[i]);
    6. }
    7. }
    8. /* result in the console
    9. hands : 2
    10. legs : 2
    11. heads : 1
    12. */
    13. // 2.
    14. // antipattern:
    15. // for-in loop without checking hasOwnProperty()
    16. for (var i in man) {
    17. console.log(i, ":", man[i]);
    18. }
    19. /*
    20. result in the console
    21. hands : 2
    22. legs : 2
    23. heads : 1
    24. clone: function()
    25. */

    另外一种使用方法如下:

       
       
    1. for (var i in man) {
    2. if (Object.prototype.hasOwnProperty.call(man, i)) { // filter
    3. console.log(i, ":", man[i]);
    4. }
    5. }

    这样写的好处是可以防止man重新定义了hasOwnProperty方法导致的冲突。如果不想写这么长的一串,你也可以这样:

       
       
    1. var i, hasOwn = Object.prototype.hasOwnProperty;
    2. for (i in man) {
    3. if (hasOwn.call(man, i)) { // filter
    4. console.log(i, ":", man[i]);
    5. }
    6. }

    严格意义上讲,不适用hasOwnProperty也不是什么错误。根据任务的难度和你对代码的自信程度,你也可以不用这个直接循环。但是当你不确定的时候,最好还是使用这个方法检测一下。

    另外一种格式上的改变(不会通过jsLint的检查),去掉for的大括号,然后把if放在同一行。这样做的好处可以让循环体更加突出,缩进也就少一些:

       
       
    1. // Warning: doesn't pass JSLint
    2. var i, hasOwn = Object.prototype.hasOwnProperty;
    3. for (i in man) if (hasOwn.call(man, i)) { // filter
    4. console.log(i, ":", man[i]);
    5. }

    不要扩展内建的原型

    扩展原型的构造函数,可以提供一些很强大的功能,但是有时候他太强大了。

    有时候你会去扩展Object(),Array(),Fucntion()的原型方法,这样会导致可维护性的问题,因为这会让你的代码的移植性变差。其他的开发人员使用你的代码的时候,可能只需要原生的方法,并不需要额外的功能。

    另外,你添加进去的方法,如果在循环的时候没有使用hasOwnProperty方法就会被遍历出来,这会让人很迷惑。

    所以,最好还是不要扩展基本的对象。除非是下面的情况:

    ◆你确定在将来根据ECMAScript规范,浏览器会添加相应的原型方法,那么是可以的,你只不过是提前实现了这个功能。

    ◆你确定的你要实现的方法不存在–或许有时候在代码的其他的地方实现了,或者有的浏览器支持,这都是不行的。

    ◆有非常清晰的文档,并且与团队成员沟通过

    如果在这些情况之下,那么你就可以添加,最好是下面这种形式:

       
       
    1. if (typeof Object.prototype.myMethod !== "function") {
    2. Object.prototype.myMethod = function () {
    3. // implementation...
    4. };
    5. }

    switch模式

    按照下面的风格写switch的话,可以提高你的代码可读性和健壮性:

       
       
    1. var inspect_me = 0,
    2. result = '';
    3. switch (inspect_me) {
    4. case 0:
    5. result = "zero";
    6. break;
    7. case 1:
    8. result = "one";
    9. break;
    10. default:
    11. result = "unknown";
    12. }

    需要注意下面几个方面:

    ◆将case和switch对齐。

    ◆case的内容缩进

    ◆每一个case之后都有一个清晰的break

    ◆避免顺序往下执行case,非要如此的话,文档一定要写清楚

    ◆最后使用default,保证在没有命中case的情况下也有反馈

    避免隐藏的类型转换

    Javascript在你比较两个变量的时候会进行类型的转换,这就是为什么 false == 0或者”" == 0会返回true。

    为了避免这种隐藏的类型转换带来的迷惑,最好使用===或者!==操作符来比较:

       
       
    1. var zero = 0;
    2. if (zero === false) {
    3. // not executing because zero is 0, not false
    4. }
    5. // antipattern
    6. if (zero == false) {
    7. // this block is executed...
    8. }

    还有另外一种流派持这样的观点:当==够用时使用===就是多余的。比如,当你使用typeof的时候你知道会返回string,所以没必要使用严格的检验。然而,JSLint要求严格检验;他最大程度使代码在阅读的时候减少歧义,(“这个==是故意呢还是疏漏?”)。

    避免使用eval()

    如果你在你的代码中使用eval(),那么要记住”eval() is evil”。这个方法会将传入的字符串当做js代码来执行。如果代码是在运行前就确定的,那么没有必要使用eval()。如果代码是在运行时动态确定的,那么也有其他更安全的办法。例如使用方括号形式访问元素的属性:

       
       
    1. // antipattern
    2. var property = "name";
    3. alert(eval("obj." + property));
    4. // preferred
    5. var property = "name";
    6. alert(obj[property]);

    使用eval()还有安全问题,比如你运行网络上的一段代码,而这段代码又被别人篡改了。在处理Ajax请求返回的JSON数据的时候,最好还是使用浏览器内建的处理方法,如果对于低端的浏览器不支持的,可以从JSON.org上下载对应的处理库。

    另外还要记住使用setTimeout、setInterval以及Function的构造函数的是,传入的字符串的参数,js的处理方法跟eval()类似,所以也要注意。因为,js会把你传入的字符串解析执行:

       
       
    1. // antipatterns
    2. setTimeout("myFunc()", 1000);
    3. setTimeout("myFunc(1, 2, 3)", 1000);
    4. // preferred
    5. setTimeout(myFunc, 1000);
    6. setTimeout(function () {
    7. myFunc(1, 2, 3);
    8. }, 1000);

    使用Function的构造函数,跟eval()差不多,也要注意。这是个非常有用的功能,但是常常被错用。如果你必须使用eval(),那么可以考虑new一个Function来替代。另外的一个好处就是,使用Function的构造函数,函数的作用域在本方法内,这样你使用var声明的变量就不会变成全局的。另外一个防止eval()生成全局变量的办法就是使用匿名函数。

    看看下面这个例子,只有un变量最终是全局的:

       
       
    1. console.log(typeof un); // "undefined"
    2. console.log(typeof deux); // "undefined"
    3. console.log(typeof trois); // "undefined"
    4. var jsstring = "var un = 1; console.log(un);";
    5. eval(jsstring); // logs "1"
    6. jsstring = "var deux = 2; console.log(deux);";
    7. new Function(jsstring)(); // logs "2"
    8. jsstring = "var trois = 3; console.log(trois);";
    9. (function () {
    10. eval(jsstring);
    11. }()); // logs "3"
    12. console.log(typeof un); // number
    13. console.log(typeof deux); // undefined
    14. console.log(typeof trois); // undefined

    eval()和Function构造函数的另一个区别就是eval()会影响到作用域,而Function则相当于一个沙盒。例如:

       
       
    1. (function () {
    2. var local = 1;
    3. eval("local = 3; console.log(local)"); // logs 3
    4. console.log(local); // logs 3
    5. }());
    6. (function () {
    7. var local = 1;
    8. Function("console.log(typeof local);")(); // logs undefined
    9. }());

    使用parseInt()转换处理数字

    使用parseInt()你可以将字符串转为数字。这个方法支持第二个表示进制的参数,常常被忽略。问题常常在处理一段以0开始的字符串的时候。在ECMAS3标准中,以0开始表示八进制,但是在ES5中又改了,所以为了避免麻烦,最好还是标明第二个参数。

       
       
    1. var month = "06",
    2. year = "09";
    3. month = parseInt(month, 10);
    4. year = parseInt(year, 10);

    在这个例子中,如果你使用parseInt(year),就会返回0,因为09被认为是8进制数字,然而9是非法的八进制字符,所以返回0。

    其他的可以把字符串转为数字的方法有:

       
       
    1. +"08" // result is 8
    2. umber("08") // 8

    这些通常都比parseInt()快一些,因为parseInt并不只是简单的转换。但是如果你的输入是”08 hello”这样的,那么parseInt()也会返回8,但是其他的方法就只能返回NaN。

    原文链接:http://www.cnblogs.com/beiyu/archive/2011/03/03/essential-javascript.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值