编写高质量JavaScript代码的基本要点

书写可维护的代码(Writing Maintainable Code )
花时间学习和理解这个问题 化时间是了解应该解决的问题代码 
bug是暴露的 新功能被添加到应用程序 程序在新的环境下工作(例如,市场上出现新想浏览器) 代码改变用途 代码得完全从头重新,或移植到另一个架构上或者甚至使用另一种语言 
可读的 一致的 可预测的 看上去就像是同一个人写的 已记录 
最小全局变量(Minimizing Globals)
myglobal = "hello"; // 不推荐写法console.log(myglobal); // "hello"console.log(window.myglobal); // "hello"console.log(window["myglobal"]); // "hello"console.log(this.myglobal); // "hello"
全局变量的问题
第三方的JavaScript库 广告方的脚本代码 第三方用户跟踪和分析脚本代码 不同类型的小组件,标志和按钮 
function sum(x, y) {// 不推荐写法: 隐式全局变量result = x + y;return result;}
function sum(x, y) {var result = x + y;return result;}
// 反例,勿使用function foo() {var a = b = 0;// ...}
var a = (b = 0);
function foo() {var a, b;// ... a = b = 0; // 两个均局部变量}
然而,另外一个避免全局变量的原因是可移植性。如果你想你的代码在不同的环境下(主机下)运行,使用全局变量如履薄冰,因为你会无意中覆盖你最初环境下不存在的主机对象(所以你原以为名称可以放心大胆地使用,实际上对于有些情况并不适用)。 
忘记var的副作用(Side Effects When Forgetting var)
通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的。 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的。 
// 定义三个全局变量var global_var = 1;global_novar = 2; // 反面教材(function () {global_fromfunc = 3; // 反面教材}());// 试图删除delete global_var; // falsedelete global_novar; // truedelete global_fromfunc; // true// 测试该删除typeof global_var; // "number"typeof global_novar; // "undefined"typeof global_fromfunc; // "undefined"
访问全局对象(Access to the Global Object)
var global = (function () {return this;}());
单var形式(Single var Pattern)
提供了一个单一的地方去寻找功能所需要的所有局部变量 防止变量在定义之前使用的逻辑错误 帮助你记住声明的全局变量,因此较少了全局变量//zxx:此处我自己是有点晕乎的… 少代码(类型啊传值啊单线完成) 
function func() {var a = 1,b = 2,sum = a + b,myobject = {},i,j;// function body...}
function updateElement() {var el = document.getElementById("result"),style = el.style;// 使用el和style干点其他什么事...}
预解析:var散布的问题(Hoisting: A Problem with Scattered vars)
// 反例myname = "global"; // 全局变量function func() {alert(myname); // "undefined"var myname = "local";alert(myname); // "local"}func();
myname = "global"; // global variablefunction func() {var myname; // 等同于 -> var myname = undefined;alert(myname); // "undefined"myname = "local";alert(myname); // "local"}func();
为了完整,我们再提一提执行层面的稍微复杂点的东西。代码处理分两个阶段,第一阶段是变量,函数声明,以及正常格式的参数创建,这是一个解析和进入上下文 的阶段。第二个阶段是代码执行,函数表达式和不合格的标识符(为声明的变量)被创建。但是,出于实用的目的,我们就采用了”hoisting”这个概念, 这种ECMAScript标准中并未定义,通常用来描述行为。 
for循环(for Loops)
// 次佳的循环for (var i = 0; i < myarray.length; i++) {// 使用myarray[i]做点什么}
document.getElementsByName()document.getElementsByClassName()document.getElementsByTagName()
document.images: 页面上所有的图片元素document.links : 所有a标签元素document.forms : 所有表单document.forms[0].elements : 页面上第一个表单中的所有域
for (var i = 0, max = myarray.length; i < max; i++) {// 使用myarray[i]做点什么}
function looper() {var i = 0,max,myarray = [];// ...for (i = 0, max = myarray.length; i < max; i++) {// 使用myarray[i]做点什么}}
i = i + 1i += 1
如果你直接无视它,JSLint的plusplus选项会是false(默认是default)。
少了一个变量(无max) 向下数到0,通常更快,因为和0做比较要比和数组长度或是其他不是0的东西作比较更有效率 
//第一种变化的形式:var i, myarray = [];for (i = myarray.length; i–-;) {// 使用myarray[i]做点什么}//第二种使用while循环:var myarray = [],i = myarray.length;while (i–-) {// 使用myarray[i]做点什么}
for-in循环(for-in Loops)
// 对象var man = {hands: 2,legs: 2,heads: 1};// 在代码的某个地方// 一个方法添加给了所有对象if (typeof Object.prototype.clone === "undefined") {Object.prototype.clone = function () {};}
// 1.// for-in 循环for (var i in man) {if (man.hasOwnProperty(i)) { // 过滤console.log(i, ":", man[i]);}}/* 控制台显示结果hands : 2legs : 2heads : 1*/// 2.// 反面例子:// for-in loop without checking hasOwnProperty()for (var i in man) {console.log(i, ":", man[i]);}/*控制台显示结果hands : 2legs : 2heads : 1clone: function()*/
for (var i in man) {if (Object.prototype.hasOwnProperty.call(man, i)) {// 过滤console.log(i, ":", man[i]);}}
var i, hasOwn = Object.prototype.hasOwnProperty;for (i in man) {if (hasOwn.call(man, i)) {// 过滤console.log(i, ":", man[i]);}}
严格来说,不使用hasOwnProperty()并不是一个错误。根据任务以及你对代码的自信程度,你可以跳过它以提高些许的循环速度。但是当你对当前对象内容(和其原型链)不确定的时候,添加hasOwnProperty()更加保险些。 
// 警告:通不过JSLint检测var i, hasOwn = Object.prototype.hasOwnProperty;for (i in man) if (hasOwn.call(man, i)) {// 过滤console.log(i, ":", man[i]);}
(不)扩展内置原型((Not) Augmenting Built-in Prototypes)
可以预期将来的ECMAScript版本或是JavaScript实现将一直将此功能当作内置方法来实现。例如,你可以添加ECMAScript 5中描述的方法,一直到各个浏览器都迎头赶上。这种情况下,你只是提前定义了有用的方法。 如果您检查您的自定义属性或方法已不存在——也许已经在代码的其他地方实现或已经是你支持的浏览器JavaScript引擎部分。 你清楚地文档记录并和团队交流了变化。 
if (typeof Object.protoype.myMethod !== "function") {Object.protoype.myMethod = function () {// 实现...};}
switch模式(switch Pattern)
var inspect_me = 0,result = '';switch (inspect_me) {case 0:result = "zero";break;case 1:result = "one";break;default:result = "unknown";}
每个case和switch对齐(花括号缩进规则除外) 每个case中代码缩进 每个case以break清除结束 避免贯穿(故意忽略break)。如果你非常确信贯穿是最好的方法,务必记录此情况,因为对于有些阅读人而言,它们可能看起来是错误的。 以default结束switch:确保总有健全的结果,即使无情况匹配。 
避免隐式类型转换(Avoiding Implied Typecasting )
var zero = 0;if (zero === false) {// 不执行,因为zero为0, 而不是false}// 反面示例if (zero == false) {// 执行了...}
避免(Avoiding) eval()
// 反面示例var property = "name";alert(eval("obj." + property));// 更好的var property = "name";alert(obj[property]);
// 反面示例setTimeout("myFunc()", 1000);setTimeout("myFunc(1, 2, 3)", 1000);// 更好的setTimeout(myFunc, 1000);setTimeout(function () {myFunc(1, 2, 3);}, 1000);
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); // numberconsole.log(typeof deux); // "undefined"console.log(typeof trois); // "undefined"
(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}());
parseInt()下的数值转换(Number Conversions with parseInt())
var month = "06",year = "09";month = parseInt(month, 10);year = parseInt(year, 10);
+"08" // 结果是 8Number("08") // 8
编码规范(Coding Conventions)
缩进(Indentation)
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;}
花括号{}(Curly Braces)
// 糟糕的实例for (var i = 0; i < 10; i += 1)alert(i);
// 糟糕的实例for (var i = 0; i < 10; i += 1)alert(i);alert(i + " is " + (i % 2 ? "odd" : "even"));
// 好的实例for (var i = 0; i < 10; i += 1) {alert(i);}
// 坏if (true)alert(1);elsealert(2);// 好if (true) {alert(1);} else {alert(2);}
左花括号的位置(Opening Brace Location)
if (true) {alert("It's TRUE!");}//或if (true){alert("It's TRUE!");}
// 警告:意外的返回值function func() {return// 下面代码不执行{name : "Batman"}}
// 警告:意外的返回值function func() {return undefined;// 下面代码不执行{name : "Batman"}}
function func() {return {name : "Batman"};}
关于分号注:就像使用花括号,你应该总是使用分号,即使他们可由JavaScript解析器隐式创建。这不仅促进更科学和更严格的代码,而且有助于解决存有疑惑的地方,就如前面的例子显示。 
空格(White Space)
for循环分号分开后的的部分:如for (var i = 0; i < 10; i += 1) {...} for循环中初始化的多变量(i和max):for (var i = 0, max = 10; i < max; i += 1) {...} 分隔数组项的逗号的后面:var a = [1, 2, 3]; 对象属性逗号的后面以及分隔属性名和属性值的冒号的后面:var o = {a: 1, b: 2}; 限定函数参数:myFunc(a, b, c) 函数声明的花括号的前面:function myFunc() {} 匿名函数表达式function的后面:var myFunc = function () {}; 
// 宽松一致的间距// 使代码更易读// 使得更加“透气”var d = 0,a = b + 1;if (a && b && c) {d = a % c;a += d;}// 反面例子// 缺失或间距不一// 使代码变得疑惑var d = 0,a = b + 1;if (a&&b&&c) {d=a % c;a+= d;}
函数、if-else语句、循环、对象字面量的左花括号的前面({) else或while之间的右花括号(}) 
有一个经常被忽略的代码可读性方面是垂直空格的使用。你可以使用空行来分隔代码单元,就像是文学作品中使用段落分隔一样。 
命名规范(Naming Conventions)
以大写字母写构造函数(Capitalizing Constructors)
var adam = new Person();
function MyConstructor() {...}function myFunction() {...}
分隔单词(Separating Words)
其它命名形式(Other Naming Patterns)
// 珍贵常数,只可远观var PI = 3.14,MAX_WIDTH = 800;
var person = {getName: function () {return this._getFirst() + ' ' + this._getLast();},_getFirst: function () {// ... },_getLast: function () {// ... }};
使用尾下划线表示私有,如name_和getElements_() 使用一个下划线前缀表_protected(保护)属性,两个下划线前缀表示__private (私有)属性 Firefox中一些内置的变量属性不属于该语言的技术部分,使用两个前下划线和两个后下划线表示,如:__proto__和__parent__。 
注释(Writing Comments)
最重要的习惯,然而也是最难遵守的,就是保持注释的及时更新,因为过时的注释比没有注释更加的误导人。 
关于作者(About the Author )

评论
