高质量JavaScript代码基本要点汇总(持续更新)

本博文包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档、执行同行评审以及运行JSLint。这些习惯和最佳做法可以帮助你写出更好的,更易于理解和维护的代码,这些代码在几个月或是几年之后再回过头看看也是会觉得很自豪的。所以将不断从项目中,日常累积,并时常回头来回味。。。

全局变量的问题

全局变量的问题在于,JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,他们住在同一个全局命名空间,所以当程序的两个不同部分定义同名但不同作用的全局变量的时候,命名冲突在所难免。

1) 避免不自觉创建出全局变量

function sum(x, y){
    result = x + y;
    return result;
}

代码照常运行正常,但是在调用函数后最后的结果就多了一个全局命名空间。极有可能遇上命名冲突,而不被察觉

2)隐式全局变量

function foo(){
    var a = b = 0;
    // ...
}

从右到左的赋值,首先,是赋值表达式b = 0,此情况下b是未声明的。这个表达式返回值是0,在将其分配给通过var定义的这个局部变量a。所以就会想全局空间添加一个b的全局变量。

3) 忘记var 的副作用

通过var 创建的全局变量(任何函数之外的程序中创建)是不能被删除的

无var 创建的隐式全局变量(无视是否在函数中创建)是能被删除的

var global_var  = 1;
global_novar = 2;        //反例
(function(){
    global_fromfunc = 3;        //反例
}());

//试图删除
delect global_var;        //false
delect global_novar;        //true
delect global_fromfunc;        //true

//测试该删除
typeof global_var;        //"number"
typeof global_noval;        //"undefined"
typeof global_fromfunc;       //"undefined"

预解析:var散布的问题

JavaScript中,你可以在函数的任何位置声明多个var语句,并且它们就好像是在函数顶部声明一样发挥作用,这种行为称为 hoisting(悬置/置顶解析/预解析)。当你使用了一个变量,然后不久在函数中又重新声明的话,就可能产生逻辑错误。对于JavaScript,只 要你的变量是在同一个作用域中(同一函数),它都被当做是声明的,即使是它在var声明前使用的时候。

myname = "global";    //全局变量
function func(){
    alert(myname);    //'nudefined'
    var myname = "local";
    alert(myname);    //'local'
}
func();

在上述代码中,你可能会以为第一个alert弹出的是”global”,第二个弹出”loacl”。这种期许是可以理解的,因为在第一个alert 的时候,myname未声明,此时函数肯定很自然而然地看全局变量myname,但是,实际上并不是这么工作的。第一个alert会弹 出”undefined”是因为myname被当做了函数的局部变量(尽管是之后声明的),所有的变量声明当被悬置到函数的顶部了。因此,为了避免这种混乱,最好是预先声明你想使用的全部变量。

for循环

在for循环中,可以循环取得数组或是数组类似对象的值,例如arguments和HTMLcollection对象。通常循环形式如下:

for(var i=0; i< myarray.length; i++){
    ...
}

这种形式的循环的不足在于每次循环的时候都要去获取一次数组的长度,尤其是当myarray不是数组的时候,而是一个HTMLCollection对象(DOM方法返回的对象)的时候。要获取长度代价很高。由此可以如下优化:

for(var i=0, max=myarray.length; i<max; i++){
    ...
}

for-in循环

for-in循环应该用在非数组对象的遍历上,使用for-in 进行循环也被称为“枚举”

从技术上讲,for-in循环数组不被推荐,因为如果数组对象已被自定义的功能增强,就可能发生逻辑错误。另外,在for-in中,属性列表的顺序(序列)是不能保证的。所以最好数组使用正常的for循环,对象使用for-in循环。

有个很重要的hasOwnProperty()方法,当遍历对象属性的时候可以过滤掉从原型链上下来的属性。

var man = {
    hands: 2,
    legs: 2,
    head: 1
};

.......

if(typeof Object.prototype.clone ==='undefined'){
    Object.prototype.clone = function(){};
}

在这个例子中,我们有一个使用对象字面量定义的名叫man的对象。在man定义完成后的某个地方,在对象原型上增加了一个很有用的名叫 clone()的方法。此原型链是实时的,这就意味着所有的对象自动可以访问新的方法。为了避免枚举man的时候出现clone()方法,你需要应用hasOwnProperty()方法过滤原型属性。如果不做过滤,会导致clone()函数显示出来,在大多数情况下这是不希望出现的

for (var  i in man){
    if(man.hasOwnProperty(i)){    //过滤
        console.log(i, ":", map[i]);
    }
}

不扩展内置原型

属性添加到原型中,可能会导致不使用hasOwnProperty属性时在循环中显示出来,这会造成混乱。因此,不增加内置原型是最好的。你可以指定一个规则,仅当下面的条件均满足时例外:

  • 可以预期将来的ECMAScript版本或是JavaScript实现将一直将此功能当作内置方法来实现。例如,你可以添加ECMAScript 5中描述的方法,一直到各个浏览器都迎头赶上。这种情况下,你只是提前定义了有用的方法。
  • 如果您检查您的自定义属性或方法已不存在——也许已经在代码的其他地方实现或已经是你支持的浏览器JavaScript引擎部分。
  • 你清楚地文档记录并和团队交流了变化
  • if(typeof Object.prototype.myMethod !== "function"){
        Object.prototype.myMethod = function(){
            ...
        };
    }

    当满足那三个条件,就可以用上述代码进行自定义的添加。

避免隐式类型转换

JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false == 0 或 “” == 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用===和!==操作符。

var zero = 0;
if(zero === false){
    //不执行,不会进行强制转换
}

//反例
if(zero == false){
    //执行了
}

eval()是魔鬼。此方法接受任意的字符串,并当作JavaScript代码来处理。当有 问题的代码是事先知道的(不是运行时确定的),没有理由使用eval()。如果代码是在运行时动态生成,有一个更好的方式不使用eval而达到同样的目 标。例如,用方括号表示法来访问动态属性会更好更简单:

//反例
var prototype = "name";
alert(eval("obj." + property));

//更好的
var property = "name";
alert(obj[property]);

同样重要的是要记住,给setInterval(), setTimeout()和Function()构造函数传递字符串,大部分情况下,与使用eval()是类似的,因此要避免。使用新的Function()构造就类似于eval(),应小心接近。

console.log(typeof un);    //undefined
console.log(typeof deux);    //undefined
console.log(typeof trois);    //undefined

var jsstring = "var un = 1; console.log(un)";
eval(jsstring);                  //logs 1

jsstring = "var deux = 2; console.log(deux)";
new Function(jsstring)();        //logs 2

jsstring = "var trois = 3; console.log(trois)";
(function(){
    eval(jsstring);
}());    //log 3

console.log(typeof un);        //number
console.log(typeof deux);      //undefined
console.log(typeof trois);       //undefined

eval()和Function构造不同的是eval()可以干扰作用域链,它会访问和修改它外部作用域中的变量,而Function要好很多,它只会使用全局变量

(function(){
    var local = 1;
    eval("local = 3; console.log(local)");    //logs 3
    console.log(local);
}());

(function(){
    var local = 1;
    Function("console.log(typeof local);")();    //logs undefined
}());

ParseInt()下的数值转换

使用parseInt()你可以从字符串中获取数值,该方法接受另一个基数参数,这经常省略,但不应该。当字符串以“o“开头的时候就可能会出问题,例如,部分时间进入表单域,在ECMAScript 3中,开头为”0″的字符串被当做8进制处理了,但这已在ECMAScript 5中改变了。为了避免矛盾和意外的结果,总是指定基数参数。

var month = "06",
    year = "09";
month = parseInt(month,10);
year = parseInt(year,10);

此例中,如果忽略了基数参数,如parseInt(year),返回的值将是O,因为”09“被当做8进制(好比执行了parseInt(year, 8)),而而09在8进制中不是个有效数字。

左花口号的位置

这个实例中,仁者见仁智者见智,但也有个案,括号位置不同会有不同的行为表现。这是因为分号插入机制(semicolon insertion mechanism)——JavaScript是不挑剔的,当你选择不使用分号结束一行代码时JavaScript会自己帮你补上。这种行为可能会导致麻 烦,如当你返回对象字面量,而左括号却在下一行的时候:上一个Funtion等同于下一个

//警告:意外的返回值
function func(){
    return
    //下面代码不执行
    {
        name: "Batman";
    }
}

//警告: 意外的返回值
function func(){
    return  undefined;
    //下面的代码不执行
    {
        name:"Batman";
    }
}

空格

空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔。在JavaScript中,你可以按照同样的逻辑在列表模样表达式(相当于逗号)和结束语句(相对于完成了“想法”)后面添加间隔。

适合使用空格的地方:

1) for循环分号分开后的部分: 如for (var i =0; i < 10; i++ ) { ... }

2)  for循环中初始化的多变量:for ( var i = 0, max = 10; i < max; i += 1) { ... }

3) 分割数组项的逗号的后面 : var a = [ a, b, c] ;

4) 限定函数参数:myFunc(a, b, c)

5) 对象属性逗号的后面以及分隔属性名和属性值的冒号的后面: var o = {a: 1, b: 2}

6) 函数声明的花括弧的前面 : function myFunc() {}

7) 匿名函数表达式function的后面: var myFunc = function () {}

8) 使用空格分开所有的操作符和操作对象是另一个不错的使用,这意味着在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=等前后都需要空格。

其他命名方式

1. 由于javascript没有定义常量的方法,所以开发者都采用全部单词大写的规范来命名这个程序生命周期中都不会改变的变量

var PI = 3.14,
    MAX_WIDTH = 800;

2. 全局变量名字全部大写。全部大写命名全局变量可以加强减小全局变量数量的实践,同时让它们易于区分。(可选)

3. 另外一种使用规范来模拟功能的是私有成员。虽然可以在JavaScript中实现真正的私有,但是开发者发现仅仅使用一个下划线前缀来表示一个私有属性或方法会更容易些。

var Person = {
    getName: function() {
        return this._getFirst() + '' + this._getLast();
    },

    _getFirst: function() {
        //...
    }

    _getLast: function() {
        //...
    }
};

在此例中,getName()就表示公共方法,部分稳定的API。而_getFirst()_getLast()则表明了私有。它们仍然是正常的公共方法,但是使用下划线前缀来警告person对象的使用者这些方法在下一个版本中时不能保证工作的,是不能直接使用的。

下面是一些常见的_private规范:

  • 使用尾下划线表示私有,如name_和getElements_()
  • 使用一个下划线前缀表_protected(保护)属性,两个下划线前缀表示__private (私有)属性
  • Firefox中一些内置的变量属性不属于该语言的技术部分,使用两个前下划线和两个后下划线表示,如:__proto__和__parent__。

猜你喜欢

转载自blog.csdn.net/PINGER0077/article/details/82747585