Jun24

引爆你的Javascript代码进化

分类: Javascript | 转载请注明: 出自 海玉的博客
本文地址: http://www.hicss.net/evolve-your-javascript-code/

方才在程序里看到一段JS代码,写法极为高明,私心想着若是其按照规范来写,定可培养对这门语言的理解,对JS编程能力提高必是极好的。说人话:丫代码写的太乱,看的窝火!

最近闲暇无事,准备对自己JS学习做一个总结。众所周知,JS是一种语法极其灵活的语言,一千个人会有一千种JS书写方式。这造成的结果往往就是给项目日后的开发及维护留下一个不小的隐患,也对你和团队再次开发及阅读代码造成一定困难,个人认为良好的书写规范是应该首当其冲的。所以参考一些优秀前端开发团队的代码规范后,总结了几点,希望能让你的Javascript代码上升一个台阶。

变量命名:

变量名包括全局变量,局部变量,类变量,函数参数等等,他们都属于这一类。

变量命名都以类型前缀+有意义的单词组成,用驼峰式命名法增加变量和函式的可读性。例如:sUserName,nCount。

前缀规范:

每个局部变量都需要有一个类型前缀,按照类型可以分为:

s:表示字符串。例如:sName,sHtml;
n:表示数字。例如:nPage,nTotal;
b:表示逻辑。例如:bChecked,bHasLogin;
a:表示数组。例如:aList,aGroup;
r:表示正则表达式。例如:rDomain,rEmail;
f:表示函数。例如:fGetHtml,fInit;
o:表示以上未涉及到的其他对象,例如:oButton,oDate;
g:表示全局变量,例如:gUserName,gLoginTime;

当然,也可以根据团队及项目需要增加前缀规范,例如我们团队会用到:

$:表示Jquery对象。例如:$Content,$Module;
一种比较广泛的Jquery对象变量命名规范。
j:表示Jquery对象。例如:jContent, jModule;
另一种Jquery对象变量命名方式。
fn:表示函数。例如:fnGetName,fnSetAge;
和上面函数的前缀略有不同,改用fn来代替,个人认为fn能够更好的区分普通变量和函数变量。
dom:表示Dom对象,例如:domForm,domInput;
项目中很多地方会用到原生的Dom方法及属性,可以根据团队需要适当修改。

这里可以根据项目及团队需要,设计出针对项目需要的前缀规范,从而达到团队开发协作便利的目的。

例外情况:

1:作用域不大临时变量可以简写,比如:str,num,bol,obj,fun,arr。
2:循环变量可以简写,比如:i,j,k等。
3:某些作为不允许修改值的变量认为是常量,全部字母都大写。例如:COPYRIGHT,PI。常量可以存在于函数中,也可以存在于全局。

为什么需要这样强制定义变量前缀?正式因为javascript是弱语言造成的。在定义大量变量的时候,我们需要很明确的知道当前变量是什么属性,如果只通过普通单词,是很难区分的。

//普通代码
var checked = false;
var check = function() {
    return true;
}
/**
some code
**/

if(check) {//已经无法很确切知道这里是要用checked还是check()从而导致逻辑错误
    //do some thing
}

//规范后代码
var bChecked = false;
var fnCheck = function() {
    return true;
}
/**
some code
**/

if(bChecked) {
    // do some thing
}
if(fnCheck()) {
    // do other thing
}

函数命名:

统一使用动词或者动词+名词形式,例如:fnGetVersion(),fnSubmitForm(),fnInit();涉及返回逻辑值的函数可以使用is,has,contains等表示逻辑的词语代替动词,例如:fnIsObject(),fnHasClass(),fnContainsElment()。

如果有内部函数,使用_fn+动词+名词形式,内部函数必需在函数最后定义。例如:

function fnGetNumber(nTotal) {
    if (nTotal < 100) {
        nTotal = 100;
    }
    return _fnAdd(nTotal);

    function _fnAdd(nNumber) {
        nNumber++;
        return nNumber;
    }
}
alert(fGetNumber(10)); //alert 101

对象方法与事件响应函数:

对象方法命名使用fn+对象类名+动词+名词形式;例如 fnAddressGetEmail(),主观觉得加上对象类名略有些鸡肋,个人认为一个对象公开的属性与方法应该做到简洁易读。多增加一个对象类名单词,看着统一了,但有点为了规范而规范的味道,这里根据自身喜好仁者见仁智者见智吧。

某事件响应函数命名方式为fn+触发事件对象名+事件名或者模块名,例如:fnDivClick(),fnAddressSubmitButtonClick()

补充一些函数方法常用的动词:

get 获取/set 设置, add 增加/remove 删除
create 创建/destory 移除 start 启动/stop 停止
open 打开/close 关闭, read 读取/write 写入
load 载入/save 保存, create 创建/destroy 销毁
begin 开始/end 结束, backup 备份/restore 恢复
import 导入/export 导出, split 分割/merge 合并
inject 注入/extract 提取, attach 附着/detach 脱离
bind 绑定/separate 分离, view 查看/browse 浏览
edit 编辑/modify 修改, select 选取/mark 标记
copy 复制/paste 粘贴, undo 撤销/redo 重做
insert 插入/delete 移除, add 加入/append 添加
clean 清理/clear 清除, index 索引/sort 排序
find 查找/search 搜索, increase 增加/decrease 减少
play 播放/pause 暂停, launch 启动/run 运行
compile 编译/execute 执行, debug 调试/trace 跟踪
observe 观察/listen 监听, build 构建/publish 发布
input 输入/output 输出, encode 编码/decode 解码
encrypt 加密/decrypt 解密, compress 压缩/decompress 解压缩
pack 打包/unpack 解包, parse 解析/emit 生成
connect 连接/disconnect 断开, send 发送/receive 接收
download 下载/upload 上传, refresh 刷新/synchronize 同步
update 更新/revert 复原, lock 锁定/unlock 解锁
check out 签出/check in 签入, submit 提交/commit 交付
push 推/pull 拉, expand 展开/collapse 折叠
begin 起始/end 结束, start 开始/finish 完成
enter 进入/exit 退出, abort 放弃/quit 离开
obsolete 废弃/depreciate 废旧, collect 收集/aggregate 聚集

上面讨论了基本的JS书写命名规范,按我个人看法,只要能够按照上面的规范写出来的代码,一般不会太糟糕,最不济没人会说你代码乱的难以阅读。代码规范对于大团队的维护建设是毋庸置疑的,当然对于个人的代码素养也是很有帮助的,希望你能够通过上文能够加强你的代码规范,写出易读易维护的代码。

代码的规范属于战术上的进化,下面我们再来看看如何让你的Javascript在战略上进化。

面向对象书写Javascript

面向对象书写Javascript想必你一定不会陌生,但我敢说,大多数前端一般不会通过面向对象来书写JS代码。第一是Javascript本身的语言机制原因造成面向对象的书写困难,由于Javascript是原型式继承又是一个类C的语言,他的面向对象的东西相对于C++/Java比较奇怪。第二是Javascript作为一种语法极其灵活的语言,直接导致了面向对象书写JS又有多种写法,让许多初学者分不清到底哪个才是正确的写法。基于上述和个人的经验推荐如下俩种书写方式:

第一类:

(function(){
	function Circle(nRadius){
		this.nR = nRadius;
	}
	Circle.prototype = {
		PI : 3.14,
		fnGetArea : function(){
			return this.PI * this.nR * this.nR;
		}
	}

	var c1 = new Circle(5);
	alert(c1.fnGetArea()); //78.5
})();

上面这种可以说是很标准的面向对象JS书写方式了我们又称之为工厂模式,优点就是简单容易上手,新手常常喜欢这么写。以上代码略微做些改动,会有如下这个变种:

(function(){
	function Circle(nRadius, sMessage){
		this.init.apply(this, arguments);
	}
	Circle.prototype = {
		init : function(nRadius, sMessage){
			this.nR = nRadius;
			this.sMessage = sMessage;
		},
		PI : 3.14,
		fnGetArea : function(){
			return this.sMessage + ": " + this.PI * this.nR * this.nR;
		}
	}

	var c = new Circle(5, "构造初始化 面积");
	alert(c.fnGetArea()); //构造初始化 面积: 78.5
})();

上面这个变种,就比较有意思了,this.init.apply(this, arguments);这行代码把初始化的任务交接给了init()方法,这么做的好处就是可以把所有初始化的东西都放在一个地方进行,增加可阅读性,需要理解一定的Javascript运行机制原理。

第二类:

(function(){
	function Circle(){
	}
	Circle.prototype = {
		init : function(nRadius, sMessage){
			this.nR = nRadius;
			this.sMessage = sMessage;
		},
		PI : 3.14,
		fnGetArea : function(){
			return this.sMessage + ": " + this.PI * this.nR * this.nR;
		}
	}

	var c = new Circle();
	c.init(5, "手动构造初始化 面积");
	alert(c.fnGetArea()); //手动构造初始化 面积: 78.5
})();

这类写法是我现在比较喜欢的写法,简洁高效。从书写角度来看省去了构造函数初始化属性,改用其init()中初始化。另一个好处在于不在new Circle()构造之初传入参数,个人认为当new构造对象的时候,最好不要掺杂参数,这样做很“危险”,改为“手动型”初始化更易于代码排查修改。当然这种写法还有一个原因就是他可以很好的转换成一般前端接受的“封装型”代码,我们把上面的代码也略微改动一下:

(function(){
	var Circle = {
		init : function(nRadius, sMessage){
			this.nR = nRadius;
			this.sMessage = sMessage;
		},
		PI : 3.14,
		fnGetArea : function(){
			return this.sMessage + ": " + this.PI * this.nR * this.nR;
		}
	}

	Circle.init(5, "封装型 面积");
	alert(Circle.fnGetArea()); //封装型 面积: 78.5
})();

是不是对上面这类代码很熟悉,很多网站上的例子都是使用这类封装型代码书写的,优点是代码的封装性良好,可以有效的重用,多用于页面功能性效果实现,封装一个Tab控件、封装一个跑马灯效果等等。缺点就是不能很好的用作继承,这是和上面三种格式最大区别。可话又说回来一般JS代码很少会用到继承的地方,除非是写一个大型库(类似YUI)会用到继承外,一般写一个功能模块用到封装型代码就够用了,所以大多数前端喜欢使用这类封装型的书写风格。

上面介绍了2类4种面向对象的写法,一般面向对象书写格式基本都在上面了,熟悉面向对象书写可以有效的增加你对JS的理解。熟练使用上面4中写法也能够很好的在工作中给代码维护修改带来便利。最后我们再来谈一个技巧,让你的Javascript代码在技巧上进化。

用对象字面量构造对象

一个对象字面量就是包含在一对花括号中的0个或多个“名/值”对。上文在面向对象书写格式的时候我们就大量的使用了对象字面量的书写格式。

对象字面量书写Javascript可以很好的简化代码,又能极大的增加代码可读性,尤其作为参数使用可以有化腐朽为神奇的表现。我们看下面代码:

(function(){
	function Person(sName, nAge, nWeight, bSingle){
		this.sName = sName;
		this.nAge = nAge;
		this.nWeight = nWeight;
		this.bSingle = bSingle;
	}
	Person.prototype.showInfo = function(){
		return this.sName + " " + this.nAge + " " + this.nWeight + " " + this.bSingle;
	}
	var p = new Person("海玉", 25, 75, true);
	alert(p.showInfo()); //海玉 25 75 true
})();

上面是一个很标准的工厂模式,一般而言这类写法属于那种规规矩矩没有大错也没有亮点的代码,而且参数不少,一个不小心还会传入错误的参数,而应用对象字面量技巧可以很好的规避此类问题,我们来看改动过后的代码:

(function(){
    function Person(){
    }
    Person.prototype = {
        init : function(option){
            if(typeof option == "undefined"){
                option = {};
            }
            this.sName = option.sName || "海玉";
            this.nAge = option.nAge || 25;
            this.nWeight = option.nWeight || 75;
            this.bSingle = (typeof option.bSingle != "undefined") ? option.bSingle : true;
        },
        showInfo : function(){
            return this.sName + " " + this.nAge + " " + this.nWeight + " " + this.bSingle;
        }
    }
    var p = new Person();
    p.init({
        nWeight : 80,
        sName : "Hank"
    })
    alert(p.showInfo()); //Hank 25 80 true
})();

这里使用第三种面向对象写法,有兴趣的朋友可以自行尝试改成封装型写法。上面的改写看出哪里改动最大吗?对的,传入参数改成了一个对象字面量,而且传入参数可以是随意设置,位置颠倒也不会有任何问题。这里充分利用对象字面量优点,利用键值对代替原始的传参方式大大提升了可读性和容错性。还有一个改进就是默认值的处理,如果没有传入任何参数,此代码也能很好的运行下去,不会有任何问题。

注1:这里形参命名为option,没有遵守上面的变量命名规范是为了方便书写与阅读,具体情况具体分析。
注2:由于||运算符对于布尔值默认赋值会出现赋值问题,所以要先进行判断是否为undefined,再利用三元运算符可以很好的完成布尔值的默认值赋值。

总的来说上面阐述的代码进化只能算的上是“修身”层面,想要真正的让代码“修心”还是得多写,多看,多练。只有这样你的代码才会更精练,更有扩展性,更好的维护性。

林林总总写了这些个总结,一来是对自己学习的记录,二来也是想同大家探讨JS还有哪些地方有潜力可挖,脑子里还有许多零零碎碎的片段,待日后再次整理验证再与大家一起分享吧。

25 个评论

  1. 从html学到js 我滴小脑袋都快炸咯 始终没有找到好的学习方法

  2. 这个叫匈牙利命名法好吧? 最早在微软的MFC使用,后来有人提出了简化版本,引入到了js之后又做了改动

  3. 函数、方法有必要加前缀fn吗?本身就是以动语开头,个人觉得没有必要~~~

    • 当你日后的代码涉及团队开发、二次维护或者交付于其他团队维护的时候,一份可读写性良好的代码规范文档会给你和他人带来意外的惊喜。这个问题仁者见仁智者见智吧。

      • 可读性也不是通过额外添加多余字符来解决的啊,比如说你上面提到的get、add之类的,没有细看,不过函数方法这种规范来说是以动词开头,玩儿js的看到了没有什么说的就都能理解了吧。

        • 同意,你这个命名类似于匈牙利.但是根本没有什么用.想C/C++这种强类型语言.都不用.JS就更没有必要用了.
          增加不了代码的可读性....

  4. 博主你好。这里发现一个问题,如果要使用默认的必须p.init({});这样才不会报错,一般不设置没人会在里面加个{}这个吧?,为啥。我菜鸟 求指导。。。

    • 恩,感谢指出,已修复这个问题。

      • susubanana Says:
        May 3, 2013 at 10:24 am

        乌龟书上建议,变量命名:命名前缀第一个单词是名词,使用小驼峰;函数或者方法命名:命名前缀第一个单词是动词,使用小驼峰;构造函数命名:命名单词是名词,使用大驼峰;常量命名:命名单词全是大写字母,单词之间用下划线分隔。
        楼主的关于JS对象的书写规范正是我想要的,O(∩_∩)O谢谢!!!

  5. 小小的规范,确实是经验之谈

  6. susubanana Says:
    May 3, 2013 at 6:48 pm

    对象直接量,缺点就是不能很好的用作继承,这是和上面三种格式最大区别,针对楼主说的这点,是否可以在直接量里面多增加一个创建对象的函数createObject,该函数调用Object.create(this);再用对象.createObject()创建继承即可。。。

  7. 博主讲的很好,学习了,有机会一定要请教请教一下。

  8. 违章建筑 Says:
    July 26, 2013 at 9:38 pm

    第一次来贵站,真是不错。博主加油,我会常来的

  9. 博主,在下对于Jiathis的运用有些不懂的地方,希望能向您请教一下。。。很着急,希望博主赏脸。如果您有空,给我发个E-mail通知我一下或者加我QQ:1530115733!!!!!十分感谢

  10. 总结的真的不错,转走咯,兄弟,嘿嘿

  11. 您好,您的网站很不错,我想和您换友情链接可以不,愿意的话请邮件我。

  12. 最近没更新啊

  13. 写的真不错,果断转一个!

  14. 大神 文章可以转载么

  15. 首当其冲不是这样用的

  16. [...] 出自 海玉的博客http://www.hicss.net/evolve-your-javascript-code/ [...]

  17. Js小浪子 Says:
    October 28, 2014 at 4:15 pm

    博主写的一些封装方法,从继承的角度来看,其实忽略了constructor属性,会丢失类的名称

  18. 有一个疑问:如果使用(function(){})();立即执行函数的方式写一个Tab控件模块和一个banner轮播模块,是将这两个模块封装在一个立即执行函数里面,还是写两个独立的立即执行函数。

  19. 面向对象有三大特性:封装,继承和多态。在js开发中(大项目或者小项目),多态使用的么?

发表评论