由于代码维护的成本是巨大的,你思如泉涌的时候,几个小时洋洋洒洒写出来的代码,在未来发生bug或者发生新需求或者须要移植的时候,可能须要一周的时间来阅读,再花费一周的时间来修改。
书写可维护的代码,须要作到如下几点:javascript
全局变量是魔鬼。
JavaScript经过函数来管理做用域,在函数内部生命的变量只能够在函数内部使用,反之,就是全局变量。
每一个JavaScript环境都有一个全局对象,能够经过this来访问这个全局对象,建立的每个全局变量,都是这个全局对象的一个属性,在浏览器中,为了方便起见,这个全局对象会有一个附加属性--window,一般this=window。java
global = "全局变量" // 建立一个全局变量 console.log(global); console.log(window.global); console.log(window["global"]); console.log(this.global); // 输出的所有都是“全局变量”
尽管可使用命名空间模式或者当即执行函数来避免产生全局变量,可是最好的办法仍是使用var来定义变量。
可是因为JavaScript的特性,很容易就不自觉的建立出全局变量了。正则表达式
所以,在如下例子中,很容易就会产生全局变量而不自觉。api
var getMax = function(x,y){ if( x > y ){ ret = x ; } else { ret = y ; } return ret; }; getMax(3,4); alert(ret); // 在这个例子中,由于ret没有被声明,所以,他变成了一个全局变量。 // 最终执行,会alert 4
另外一种产生全局变量的状况以下:数组
var getMax = function(){ var a = b = 0; }; getMax(); alert(b); // 以上代码中a是局部变量,而b则是全局变量 // 由于在从右向左的赋值过程当中,以上代码实际等于var a = (b=0) // 所以b变成了全局变量
在函数顶部,使用单var语句是比较好用的方法,例如:浏览器
var foo = function(){ var a = 0, b = 1, c = true, d = "hello", e = a + b, arr = [], obj = {}; }; //同时,也能够在单var语句的声明中作一些实际的事情例如: var modifyDom = function(){ var el = document.getElementById("id"), style = el.style;// 例如这里,取得element的样式 };
var global = 1 ; // 显示定义 global2 = 2 ; // 隐式 function foo () { global3 = 3;// 隐式 } delete global ; // false 显示定义的变量不能删除 delete global2; // true 隐式定义的能够删除 delete global3; // true 隐式定义的能够删除 alert (typeof global); // number alert (typeof global2); // undefined alert (typeof global3); // undefined
JavaScript中,能够在函数的任何地方进行声明,可是他们好像都在函数的顶部被声明过了(自上而下的运行方式中,即便声明在下方,使用在上方也不会有问题),这个现象称之为hoisting(悬置,置顶解析,预解析)
所以若是不注意这个问题,不在函数顶部单var模式预先声明的话就会遇到一些问题:缓存
temp = "hello"; function foo(){ alert(temp); var temp = "nihao"; alert(temp); } foo(); // 这段代码第一反应时弹出hello 和 nihao // 可是结果倒是undefined 和 nihao // 这是由于这段代码实际上等于 temp = "hello"; function foo(){ var temp ; alert(temp); var temp = "nihao"; alert(temp); } foo(); // 在函数顶部先执行了 var temp; // 这样全局变量就被冲掉了
// 看一下运行结果 temp = "hello"; alert(temp); // hello function foo(){ alert(temp); // undefined var temp = "nihao"; alert(temp); // nihao } foo(); // 该例子说明在函数内部,顶部的预编译
在for循环中,你能够循环取得数组或是数组相似对象的值,譬如arguments和HTMLCollection对象。一般的循环形式以下:函数
for (var i = 0; i < myarray.length; i++) { // 使用myarray[i]作点什么 }
这种形式的循环的问题在于,每次循环的时候,数组的长度都要从新去获取一次,这会下降效率,尤为是当循环的对象不是一个数组而是一个HTML Collections的时候,这也就是为何采用如下方法进行循环会比较好的缘由oop
for(var i= 0, max = myarray.length; i< max; i++){ // 使用myarray[i]作点什么 }
这样,在整个循环中,只检索了一次数组的长度。this
伴随着单var形式,以上代码能够进行如下修改
function loop(){ var i, max, myarray= []; for (i=0,max=myarray.length;i<max;i++){ //使用myarray[i]作点什么 } }
另外JSLint建议使用i=i+1或者i+=1来代替i++
由于这样会使代码看起来过于复杂,这点我的认为能够无视
以上代码还有两种变化:
var max,myarray=[]; for(max=myarray.length;max--){ //使用myarray[max]来作点什么 } var myarray=[], max = myarray.length; while(max--){ //使用myarray[i]作点什么 }
for-in循环应该运用在非数组对象的遍历上,使用for-in进行循环也被成为“枚举“。
虽然在技术上可使用for-in去循环遍历数组,可是并不推荐这样作。
有个很重要的方法:hasOwnProperty,当遍历对象属性时,能够过滤掉原型链上继承来的属性。
以下所示:
var man ={ heads:1, leg:2, hands:2 };// 定义一个名为man的对象字面量 if(typeof Object.prototype.clone == "undefined"){ Object.prototype.clone = function(){ //clone }; }// 在对象原型上增长clone方法 for (var i in man) { if (man.hasOwnProperty(i)) { // 过滤 console.log(i, ":", man[i]); } }
若是不使用hasOwnProperty方法来过滤,那么结果将会是
heads : 1 leg : 2 hands : 2 clone : (){ //clone }
另外一种实用hasOwnProperty的方法以下:
for(var i in man){ if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤 console.log(i, ":", man[i]); } }
这里还可使用单var模式,把Object.prototype.hasOwnProperty"缓存"起来
var i,has = Object.prototype.hasOwnProperty; for(i in man){ if(has.call(man,i)){ //过滤 console.log(i,":",man[i]); } }
能够经过如下形式写法加强代码健壮性
var inspect_me = 0, result = ''; switch (inspect_me) { case 0: result = "zero"; break; case 1: result = "one"; break; default: result = "unknown"; }
这个简单的例子中所遵循的风格约定以下:
JavaScript的变量在比较的时候会隐式类型转换。这就是为何一些诸如:false = = 0 或 “” = = 0 返回的结果是true。为避免引发混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用= = =和!==操做符。
好比:
var zero = 0; if (zero === false) { // 不执行,由于zero为0, 而不是false } // 反面示例 if (zero == false) { // 执行了... }
以大写字母写构造函数(Capitalizing Constructors)
JavaScript并无类,但有new调用的构造函数:
var adam = new Person(); //由于构造函数仍仅仅是函数,仅看函数名就能够帮助告诉你这应该是一个构造函数仍是一个正常的函数。 //命名构造函数时首字母大写具备暗示做用,使用小写命名的函数和方法不该该使用new调用: function MyConstructor() {...} function myFunction() {...}
当你的变量或是函数名有多个单词的时候,最好单词的分离遵循统一的规范,有一个常见的作法被称做“驼峰(Camel)命名法”,就是单词小写,每一个单词的首字母大写。
对于构造函数,可使用大驼峰式命名法(upper camel case),如MyConstructor()。对于函数和方法名称,你可使用小驼峰式命名法(lower camel case),像是myFunction(), calculateArea()和getFirstName()。
要是变量不是函数呢?开发者一般使用小驼峰式命名法,但还有另一种作法就是全部单词小写如下划线链接:例如,first_name, favorite_bands, 和 old_company_name,这种标记法帮你直观地区分函数和其余标识——原型和对象。
ECMAScript的属性和方法均使用Camel标记法,尽管多字的属性名称是罕见的(正则表达式对象的lastIndex和ignoreCase属性)。
JavaScript中没有定义常量的方法(尽管有些内置的像Number, MAX_VALUE),因此开发者都采用所有单词大写的规范来命名这个程序生命周期中都不会改变的变量,如:
// 珍贵常数,只可远观 var PI = 3.14, MAX_WIDTH = 800;
还有另一个彻底大写的惯例:全局变量名字所有大写。所有大写命名全局变量能够增强减少全局变量数量的实践,同时让它们易于区分。
另一种使用规范来模拟功能的是私有成员。虽然能够在JavaScript中实现真正的私有,可是开发者发现仅仅使用一个下划线前缀来表示一个私有属性或方法会更容易些。考虑下面的例子:
var person = { getName: function () { return this._getFirst() + ' ' + this._getLast(); }, _getFirst: function () { // ... }, _getLast: function () { // ... } }; 在此例中,getName()就表示公共方法,部分稳定的API。而_getFirst()和_getLast()则代表了私有。它们仍然是正常的公共方法,可是使用下划线前缀来警告person对象的使用者这些方法在下一个版本中时不能保证工做的,是不能直接使用的。注意,JSLint有些不鸟下划线前缀,除非你设置了noman选项为:false。 下面是一些常见的_private规范: 使用尾下划线表示私有,如name_和getElements_() 使用一个下划线前缀表_protected(保护)属性,两个下划线前缀表示__private (私有)属性 Firefox中一些内置的变量属性不属于该语言的技术部分,使用两个前下划线和两个后下划线表示,如:__proto__和__parent__。