《JavaScript模式》第2章 基本技巧
@by Ruth92(转载请注明出处)
第2章 基本技巧
一、编写可维护的代码
-
阅读性好
-
具有一致性
-
预见性好
-
看起来如同一个人编写
-
有文档
-
编写注释
-
编写 API 文档
@namespace
:用于命名包含以上对象的全局引用的名称。@class
:有些命名不当,实际上是指对象或者构造函数。@method
:定义对象中的方法和方法名。@param
:列举函数使用的参数。其中将参数类型用大括号括起来,并在其后注释参数名及描述。@return
:类似于@param
,用于描述返回值的,并且该方法没有名称。/** * 翻转一个字符串 * @param {String} input 输入需要翻转的字符串 * @return {String} 翻转后的字符串 */ var reverse = function(input) { // ... return output; };
-
编写可读性强的代码
在编写代码,甚至是编写某个 API 时,心里都要想着该代码可能是要提供给其他人阅读的。
-
同行互查
二、 尽量少用全局变量
☛ 【全局变量的问题】:
它们在整个 JavaScript 应用或 Web 页面内共享;它们生存于同一个全局命名空间内,总有可能发生命名冲突。
☛ 【创建全局变量的反模式】:
JavaScript 总是出人意料的创建全局变量的原因:
特性1,JavaScript 可直接使用变量,甚至无需声明;
特性2,JavaScript 有暗示全局变量的概念,即任何变量,如果未经声明,就为全局对象所有。
-
暗示全局变量
function sum(x, y) { // 反模式:暗示全局变量 result = x + y; return result; }
-
隐式创建全局变量——>带有
var
声明的链式赋值// 反模式,不要使用 function foo() { /** * 相当于 var a = (b = 0); * 原因:缘于从右至左的操作符优先级。 */ var a = b = 0; // a 是局部变量,b 是全局变量 // ... } // 对链式赋值的所有变量都进行声明 function foo() { var a, b; // ... a = b = 0; // 均为局部变量 }
☛ 【变量释放时的副作用】:
隐含全局变量与明确定义的全局变量有细微的不同之处:在于能否使用 delete
操作符撤销变量。
- 全局变量(这类变量在函数外部创建):使用
var
创建,不能删除; - 隐含全局变量(尽管它是在函数内部创建):不使用
var
创建,可以删除。
这表明:隐含全局变量严格来讲不是真正的变量,而是全局对象的属性。属性可以通过 delete
操作符删除,但变量不可以。
// 定义三个全局变量
var global_var = 1;
global_novar = 2; // 反模式
(function() {
global_fromfunc = 3; // 反模式
})();
// 企图删除
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// 测试删除情况
typeof global_var; // 'number'类型
typeof global_novar; // 'undefined' 类型
typeof global_fromfunc; // 'undefined' 类型
☛ 【*访问全局变量】:
-
如果需要访问不带硬编码处理的标识
window
,可以按如下方式,从内嵌函数的作用域访问:var global = (function() { // this 在函数内部作为一个函数调用时,总是指向该全局对象 return this; })();
-
代码运行在严格模式下时,如果你正在开发一个库,可以将库里的代码打包在一个直接函数中,然后在全局作用域中,传递一个引用给
this
,把this
看成传递到直接函数的一个参数。
三、变量相关的最佳实践
-
单一
var
模式; -
在声明变量的时候同时初始化,为变量赋初值也是一种好的做法;
-
为了避免由于变量提升造成的混乱,最好在开始就声明要用的所有变量。
四、循环最佳实践
-
for
循环:缓存数组(或容器)的长度。/** * for 循环 * 单变量模式,缺陷:创建代码时复制黏贴整个循环容易出错 */ function looper() { var i, max, myarray = []; // ... for (i = 0, max = myarray.length; i < max; i++) { // 处理 myarray[i]; } } /** * 改进1: * 逐步减至 0,通常更快 */ function looper() { var i, max, myarray = []; // ... for (i = myarray.length; i--) { // 处理 myarray[i]; } } /** * 改进2: * 使用 while 循环 */ function looper() { var myarray = [], i = myarray.length; while (i--) { // 处理 myarray[i] } }
-
for-in
循环(枚举,用来遍历非数组对象):使用hasOwnProperty
过滤原型链属性// 对象 var man = { hands: 2, legs: 2, heads: 1 }; if (typeof Object.prototype.clone === 'undefined') { Object.prototype.clone = function() {}; } /** * for-in 循环 */ for (var i in man) { if (man.hasOwnProperty(i)) { // filter console.log(i, ":", man[i]); } } // 在 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)) { 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]); }
五、编码约定
-
缩进
-
应该一直使用大括号并直到将开放的大括号放置在前面语句的同一行
-
使用分号
-
空格
六、命名约定
-
构造函数的首字母大写
构造函数:大骆驼峰式命名法 →
MyConstructor()
函数和方法名:小骆驼峰式命名法 →
myFunction()
、calculateArea()
、getFirstName()
-
分隔单词
基本变量和对象:下划线分隔开各个单词 →
first_name
、favorite_band
、sold_company_name
-
其他命名模式
常量:全部大写 →
var PI = 3.14
私有方法或私有属性:使用一个下划线前缀
var person = { getName: function() { return this._getFirst() + ' ' + this._getLast(); }, _getFirst: function() { // ... }, _getLast: function() { // ... } };
七、其他
-
不要增加内置的原型
如果有例外的情形,采用如下模式为原型增加自定义的方法:
if (typeof Object.prototype.myMethod !== 'function') { Object.prototype.myMethod = function() { // implementation... }; }
-
switch
模式可以使用以下模式来提高
switch
语句的可读性和健壮性var inspect_me = 0, result = ''; switch (inspect_me) { case 0: result = 'zero'; break; case 1: result = 'one'; break; default: result = 'unkown'; }
-
避免使用隐式类型转换,使用
===
和!==
JavaScript 在使用比较语句时会执行隐式类型转换,这也是为什么执行
false == 0
或"" == 0
这类比较语句时会返回true
-
避免使用
eval()
// 反模式 var property = 'name'; alert(eval('obj.' + property)); // 推荐的方法 alert(obj[property]);
通过
setInterval()
、setTimeout()
和function()
等构造函数来传递参数,在大部分情况下,也会导致类似eval()
的隐患,应该尽量避免使用这些函数。// 反模式 setTimeout('myFunc()', 1000); setTimeout('myFunc(1, 2, 3)', 1000); // 推荐的模式 setTimeout(myFunc, 1000); setTimeout(function() { myFunc(1, 2, 3); }, 1000);
使用
new Function()
构造函数与eval()
比较类似,因此该函数的使用也需要十分小心。-
如果一定需要使用
eval()
,可以考虑使用new Function()
来代替,好处:由于在new Function()
中的代码将在局部函数空间中运行,因此代码中任何采用var
定义的变量不会自动成为全局变量。 -
另一个避免自动成为全局变量的方法是将
eval()
调用封装到一个即时函数中。
-
-
使用
parseInt()
的数值约定不要忽略第二个进制参数。
parseInt('08 hello', 10) // 8
另一个将字符串转换为数值的方法:
+ "08" // 8 Number('08') // 8
-
在正式发布时精简代码
在正式发布之前精简脚本是非常重要的,因为这样可以大大缩小 JavaScript 文件,通常可以减少一半左右。
-
使用代码检查器检查代码,如 JSLint