这一篇就会讨论如何写成高质量的JavaScript代码,如避免使用全局变量,仅使用一个var声明变量,提前获取length在循环的时候,编码约定等;
还有一些其他的习惯和技巧,写JavaScript API 文档等。
myglobal = "hello"; // antipattern console.log(myglobal); // "hello" console.log(window.myglobal); // "hello" console.log(window["myglobal"]); // "hello" console.log(this.myglobal); // "hello"
function sum(x, y) { // antipattern: implied global result = x + y; return result; }
上面这段代码中,result未经声明就直接使用,这段代码功能是完全可以的,但当函数调用结束之后,就多了一个result全局变量,这可能会造成其他问题;
经验告诉我们声明一个变量一定要带上var,像下面这样;
function sum(x, y) { var result = x + y; return result; }但要注意的是,即使你使用var声明变量了,但也有可能无意中造就一个全局变量,就想下面一样(实际中可能真会这么干),a是局部的,但b是全局的;
// antipattern, do not use function foo() { var a = b = 0; // ... }
表达式b=0赋值,在这种情况下b是未声明(定义)的,所以b会成为一个全局变量;
返回结果是0然后再赋值给a(使用var声明的)这个局部变量;
上面的代码就等同于:var a = (b = 0);
如何你已经声明了变量,使用链式赋值就没有问题了,就不会无意中产生新的全局变量;
function foo() { var a, b; // ... a = b = 0; // both local }
另一个原因避免全局变量是为了移植性,如果你想你的代码运行在不一样的环境(host)中,使用全局变量是非常危险的,因为它可能无意中
重写global对象的属性,而在你原来的环境中不存在。你认为变量的名称(name)是安全的,但其实不然。
// define three globals var global_var = 1; global_novar = 2; // antipattern (function() { global_fromfunc = 3; // antipattern } ()); // attempt to delete delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // test the deletion typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
var global = (function() { return this; } ());
function func() { var a = 1, b = 2, sum = a + b, myobject = {}, i, j; // function body... }可以使用一个var声明多个变量,中间用逗号隔开;这也是个好机会去初始化变量,可以防止一些逻辑错误(所有声明但未初始化的变量,初始值都是undefined),增加代码可读性;
function updateElement() { var el = document.getElementById("result"), style = el.style; // do something with el and style... }
// antipattern myname = "global"; // global variable function func() { alert(myname); // "undefined" var myname = "local"; alert(myname); // "local" } func()在这个例子中,你可能期望第一个alert()提示“global”并且第二个alert()提示“local”;这是个合情合理的判断,因为在第一次alert()时,myname并没有被声明,
myname = "global"; // global variable function func() { var myname; // same as -> var myname = undefined; alert(myname); // "undefined" myname = "local"; alert(myname); // "local" } func();
// sub-optimal loop for (var i = 0; i < myarray.length; i++) { // do something with myarray[i] }这种模式有个问题就是在每次循环都要访问一次数组的长度,这会减慢你的代码执行的速度,当myarray不是一个数组而是一个HTMLCollection对象时;
for (var i = 0, max = myarray.length; i < max; i++) { // do something with myarray[i] }这种方法,只需要访问一次length的值,在整个for循序过程中都可以使用;
function looper() { var i = 0, max, myarray = []; // ... for (i = 0, max = myarray.length; i < max; i++) { // do something with myarray[i] } }这样的好处就是严格遵守使用一个var声明变量的原则,缺点就是复制和粘贴整个for循序变的复杂,好比重构的时候,当你从一个函数复制一个for循序到另外一个函数,
var i, myarray = []; for (i = myarray.length; i--;) { // do something with myarray[i] }
var myarray = [], i = myarray.length; while (i--) { // do something with myarray[i] }
// the object var man = { hands: 2, legs: 2, heads: 1 }; // somewhere else in the code // a method was added to all objects if (typeof Object.prototype.clone === "undefined") { Object.prototype.clone = function() {}; }在这个例子中,我们用对象字面量(object literal)声明的简单对象man;在其它什么地方或者在man声明后面,Object对象的原型增加了一个有用方法clone();
// 1. // for-in loop for (var i in man) { if (man.hasOwnProperty(i)) { // filter console.log(i, ":", man[i]); } } /* result in the console hands : 2 legs : 2 heads : 1 */ // 2. // antipattern: // for-in loop without checking hasOwnProperty() for (var i in man) { console.log(i, ":", man[i]); } /* result in the console hands : 2 legs : 2 heads : 1 clone: function() */ 18另外一种调用hasOwnProperty()方法是从Object.prototype对象身上调用;
for (var i in man) { if (Object.prototype.hasOwnProperty.call(man, i)) { // filter console.log(i, ":", man[i]); } }
var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) { if (hasOwn.call(man, i)) { // filter console.log(i, ":", man[i]); } }
if (typeof Object.protoype.myMethod !== "function") { Object.protoype.myMethod = function() { // implementation... }; }
var inspect_me = 0, result = ''; switch (inspect_me) { case 0: result = "zero"; break; case 1: result = "one"; break; default: result = "unknown"; }
var zero = 0; if (zero === false) { // not executing because zero is 0, not false } // antipattern if (zero == false) { // this block is executed... }也有人认为当使用 == 是足够的的时候使用 === 是多余的,好比你在使用typeof的时候,明确知道它会返回一个字符串,所以没有理由去使用严格的相等(===);
// antipattern var property = "name"; alert(eval("obj." + property)); // preferred var property = "name"; alert(obj[property]);使用eval()也会有安全性的影响,因为你有可能执行被篡改的代码(比如从网上获取到的),有一个常见的不好的模式就是处理Ajax请求的JSON反馈(response);
// antipatterns setTimeout("myFunc()", 1000); setTimeout("myFunc(1, 2, 3)", 1000); // preferred setTimeout(myFunc, 1000); setTimeout(function() { myFunc(1, 2, 3); }, 1000)使用new Function()构造函数和eval()类似,要格外小心;这是个强大的构造方法但是又经常被滥用;如果你就绝对必须要使用eval(),你可以考虑用new Function() 替代;
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); } ()); // logs "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); // logs 3 } ()); (function() { var local = 1; Function("console.log(typeof local);")(); // logs undefined } ());eval()就是执行传入的代码,和写在该处的效果一模一样;而Function则是声明了一个新的函数(在全局作用域中定义的),并且该处调用它;
var month = "06", year = "09"; month = parseInt(month, 10); year = parseInt(year, 10);在这个例子中,如果你忽略了基数,好比parseInt(year),那么返回的是0,因为“09”被假定为8进制,但“09”不是一个合法的8进制数;
+"08" // result is 8 Number("08") // 8这些通常都比parseInt()快,就像parseInt()名字一样,parse(解析)而不是简单的转换(convert),但是如果你想把“08 hello”传递给parseInt()将会返回一个整数,但是其它的都会返回NaN。
function outer(a, b) { //我的的的确确缩进了,难看不是我的错 var c = 1, d = 2, inner; if (a > b) { inner = function() { return { r: c - d }; }; } else { inner = function() { return { r: c + d }; }; } return inner; }
// bad practice for (var i = 0; i < 10; i += 1) alert(i);但是,稍后如果你在循环体中又加入一行会怎样呢?
// bad practice for (var i = 0; i < 10; i += 1) alert(i); alert(i + " is " + (i % 2 ? "odd" : "even"));第二个alert就会在for循环体之外,尽管这个缩进可能会欺骗你;
// better for (var i = 0; i < 10; i += 1) { alert(i); }if也是类似的:
// bad if (true) alert(1); else alert(2); // better if (true) { alert(1); } else { alert(2); }
if (true) { alert("It's TRUE!"); } if (true) { alert("It's TRUE!"); }在这个特定的例子中,括号在什么地方都没有影响,但有些情况下大括号的位置不同会导致不同的结果;
// warning: unexpected return value function func() { return { name: "Batman" }; }如果你想这个函数返回一个对象——有个一个name属性,你会非常吃惊!因为分号插入机制,这个函数会返回undefined,上面的代码等同于:
// warning: unexpected return value function func() { return undefined; // unreachable code follows... { name: "Batman" }; }在函数结束的时候,要使用大括号的话,一定要将大括号放在同一行;
function func() { return { name: "Batman" }; }
for (var i = 0;i < 10;i += 1) {...} for (var i = 0,max = 10; i < max; i += 1) {...} var a = [1, 2, 3]; var o = {a:1, b: 2}; myFunc(a, c) function myFunc() {} var myFunc = function() {};
// generous and consistent spacing // makes the code easier to read // allowing it to "breathe" var d = 0, a = b + 1; if (a && b && c) { d = a % c; a += d; } // antipattern // missing or inconsistent spaces // make the code confusing var d = 0, a = b + 1; if (a && b && c) { d = a % c; a += d; }
常见的命名方式有骆驼命名法:
大写骆驼命名法,变量或函数名每个单词首字母大写,一般适用于构造函数与其它普通方法以示区别;
var PI = 3.14, MAX_WIDTH = 800;还有用命名规范去表示私有成员,虽然你使用JavaScript实现真正的私有成员,但是程序猿发现使用一个前缀去标识私有属性和方法更加简单;
var person = { getName: function() { return this._getFirst() + ' ' + this._getLast(); }, _getFirst: function() { // ... }, _getLast: function() { // ... } }在这个例子中,getName()是一个公共方法,_getFirst()和_getLast()有意作为私有方法;
/** * Reverse a string * * @param {String} input String to reverse * @return {String} The reversed string */ var reverse = function(input) { // ... return output; };
/** * Constructs Person objects * @class Person * @constructor * @namespace MYAPP * @param {String} first First name * @param {String} last Last name */ MYAPP.Person = function(first, last) { /** * Name of the person * @property first_name * @type String */ this.first_name = first; /** * Last (family) name of the person * @property last_name * @type String */ this.last_name = last; }; /** * Returns the name of the person object * * @method getName * @return {String} The name of the person */ MYAPP.Person.prototype.getName = function() { return this.first_name + ' ' + this.last_name; };