javascript编码规范
一. 一些导致js性能缓慢的例子
DOM访问执行DOM交互的代码比i一般的js代码要慢。DOM交互是不可避免的,但是尽量的减少。
例如:
- 动态的使用innerHTML插入HTML语句比创建DOM节点更快。
- eval无论什么时候,避免使用eval方法,因为执行这一方法会造成很大的开销。
- with尽量不要使用with
- for-in 循环
二. js陷阱
- 避免使用eval和Function constructor使用eval或者function constructor会加大开销因为每一次脚本引擎调用他们是必须将源码转换成可执行代码;另外,使用eval时,字符串会在执行时被打断
- 避免使用with
- 不要在对性能影响很大的地方使用:try-catch-finally
- 避免使用全局变量
- 避免在对性能影响较大的地方使用for-in,for in循环需要脚本引擎构建一个枚举属性列表,而且每次都要从头重复检查
- 使用字符串累加模式
- 原生的操作比函数更快
- 将函数而不是字符串传到setTimeout()和setInterval()中
- 避免对象里没必要的DOM引用
- 最大化对象解析速度,最小化作用域链
- 尽量让脚本声明的变量短一点,不要太长,特别是循环里面的变量
- 将自身引用存储在作用域外的变量中。当一个funciton被执行时,一个执行的上下文(context)会被创建,被激活的对象将所有自身变量push到上下文(context)的作用域链之前。离作用域链越远,解析的越慢,这也意味着作用域本地的变量时解析的最快的。 将自身引用用作用域之外的变量存储,读写都会变的更快,这在全局变量和一些深度查找的资源解析中特别明显。当然,作用域内定义的变量比使用对象自身访问的速度更快。假如你需要在一个大循环中访问一个dom,这样子会更快:
三. 加载性能一些可以提高的点
- 更快的加载和展现页面可以让js加载没有阻塞(采用异步加载js方法)
- 添加Experes或者Cache-Control HTTPheader
- Gzip javascript和css资源
- 利用YUI或者JSMin压缩代码
- 尽量减少资源的数目和大小
- 让脚本无阻赛并行下载
- 合并异步加载的脚本
- 将行内脚本放到样式表之上?有待考证,不科学
- 少用iframe
四. 鲜为人知的DOM
dom性能缓慢可以归结为一下3个原因:
- 大规模的DOM操作
- 脚本触发太多的重构和重绘
- 定位节点在DOM中的路径慢
解决方案
- 尽可能减小DOM的大小
- 使用文档的组件模板来进行复用动态的在dom中插入或者更新元素是代价很大的。一个有效的方法来解决这个问题是利用HTML模板来插入一些对话框或者UI组件。
- 最小化重绘和重构的次数重绘发生在一些元素可见或者隐藏的时候,但是没有改变document的布局重构发生在DOM的操作方式影响到布局。重构的代价比重绘大的多得多。重构表格比重构块状元素代价大绝对定位的元素不会对document的布局产生影响DOM的修改会触发重绘
- 参考资料:
- Repaint and reflow at Opera Developer Network
- Notes on HTML Reflow – more detailed information on the reflow process (archived)
- Reflows & Repaints: CSS Performance making your JavaScript slow
- Go With The Reflow
- Rendering: repaint, reflow/relayout, restyle
- 利用cloneNode()
- 利用HTML模板和innerHTML
- 设定元素不可见再进行改变(质疑,appendChild不会重发重绘,js执行完成以后才会)
- 尽量少的使用改变元素尺寸或位置的操作
- 使用className来完成多个预定义样式的的改变
- 利用设定属性来动态完成多个样式的设定
- css class name vs. style属性
- 不要遍历大量的节点,避免在遍历时改变dom结构
- 将DOM元素缓存在变量中使用
- 在dom元素使用完以后移除引用
五. 面向对象的Javascript
考虑使用继承机制
六. client-server对话
对XMLHttpRequest设定超时时间
考虑使用约定的数据来做大数据的处理,比如选择xml或者json
七. 动画
- 选择性的使用动画
- 使用scrollTo()方法来实现滚动动画
- 将动画元素的position设置为absolute或者fix
- 在同一时间使用一个timer来服务多个动画元素
- 让动画的速度更平滑
八. 事件
- 利用事件冒泡
- 不要对一些经常触发的event使用代理
- Javascript调用栈使用setTimeout不会溢出原因是setTimeout是伪异步的,把函数交给setTimeout处理后,原来的函数不会等待,会继续执行,函数会结束,资源也就可以释放。而不用setTimeout的时候,函数必须等待调用的函数返回后才能继续执行,但调用的函数又必须等待下一级函数,这样所有函数都不能结束,资源也就释放不出。换句话说,就是死锁。
九. 命名规范
目的:提高代码可预测性和可维护性的方法是使用命名约定,这就意味着采用一致的方法来对变量和函数进行命名。
变量名
变量名包括全局变量,局部变量,类变量,函数参数
构造函数(类)命名
首字母大写,驼峰式命名。
JS中没有类,但是可以用new调用构造函数:var man = new Person();
普通变量命名
首字母小写,驼峰式命名,匈牙利命名
如:nCheckCount 表示整形的数值
匈牙利命名法
匈牙利命名法语法:变量名=类型+对象描述
类型指变量的类型
对象描述指对象名字全称或名字的一部分,要求有明确含义,命名要容易记忆容易理解。
提示: 虽然JavaScript变量表面上没有类型,但是JavaScript内部还是会为变量赋予相应的类型
JavaScript变量起名类型 |
变量命名前缀 |
举例 |
Array 数组 |
a |
aList,aGroup |
Boolean 逻辑 |
b |
bChecked,bHasLogin |
Function 函数 |
f |
fGetHtml,fInit |
Integer 数字 |
n |
nPage,nTotal |
Object 对象 |
o |
oButton,oDate |
Regular Expression 正则 |
r |
rDomain,rEmail |
String 字符 |
s |
sName,sHtml |
其他前缀规范
可根据团队及项目需要增加
$:表示Jquery对象
例如:$Content,$Module,一种比较广泛的Jquery对象变量命名规范。
fn:表示函数
例如:fnGetName,fnSetAge;和上面函数的前缀略有不同,改用fn来代替,个人认为fn能够更好的区分普通变量和函数变量。
例外情况
以根据项目及团队需要,设计出针对项目需要的前缀规范,从而达到团队开发协作便利的目的。
作用域不大临时变量可以简写,比如:str,num,bol,obj,fun,arr。
循环变量可以简写,比如:i,j,k等。
某些作为不允许修改值的变量认为是常量,全部字母都大写。例如:COPYRIGHT,PI。常量可以存在于函数中,也可以存在于全局。必须采用全大写的命名,且单词以_分割,常量通常用于ajax请求url,和一些不会改变的数据。
函数命名
普通函数:首字母小写,驼峰式命名,统一使用动词或者动词+名词形式
例如: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 聚集
变量命名例子
为什么需要这样强制定义变量前缀?正式因为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
}
如何标明私有方法或私有属性?
var person = {
getName: function () {
return this._getFirst() + ' ' + this._getLast();
},
_getFirst: function () {
//...
},
_getLast: function (){
//...
}
};
在这个例子中,getName()以为这这是API的一个公开的方法,而_getFirst()和_getLast()意味着这是一个私有函数。尽管他们都是普通的公开方法,但是使用下划线前缀的表示方法可以提醒使用person对象的用户,告诉他们这些方法在其他地方不能确保一定能够正常工作,不能直接调用。
总结:下面是一些使用下划线约定的变量
使用下划线结尾来表明是私有变量,例如name_和getElements_()。
使用一个下划线前缀来表示受保护属性,使用两个下划线前缀来表示私有属性。
十. 编写注释
为代码编写注释是非常重要的。通常人们在深入思考一个问题时,会非常清楚这段代码的工作原理。但是当过一周后再次回到该代码时,可能会花上很长时间来回想起那段代码到底是干什么的。
公共组件维护者和各栏目WD都需要在文件头部加上注释说明:
/** *文件用途说明*作者姓名、联系方式(旺旺)*制作日期**/
大的模块注释方法:
//================// 代码用途//================
小的注释;
//代码说明
注释单独一行,不要在代码后的同一行内加注释。例如:
//姓名var name = “abc”; Vvar name =”abc”; //姓名 X
十一. 使用空格
使用空格有助于改善代码的可读性和一致性。在撰写英文文章时在逗号和区间范围后面使用空格。
在javascript采用同样的逻辑,可在列表表达式(等价于逗号)和语句结束(等价于完成一次“思考”)后面添加空格。
用处一:
- 在分开for循环的各个部分的分号之后:例如,for (var i = 0; i < 10; i +=1){…}
- 在for循环中初始化多个变量(i和最大值等):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() {}
- 在匿名函数表达式之后: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;}
十二. 编写API文档
生成API文档的步骤:
- 编写特殊格式的代码块(即一些注释块)
- 运行工具来解析代码和注释(工具如:JSDoc Toolkit和YUIDoc)
- 发布工具解析的结果,大多数情况是采用HTML格式发布(如网页版的API文档就是利用工具生成的)
简单举例:
/*** 翻转一个字符串** @param {String} 输入需要翻转的字符串* @return {String} 翻转后的字符串**/ var reverse = function (input) { //... return output;};
YUIDoc范例:
完整范例:本程序由一个文件(app.js)组成,该文件仅有一个模块(myapp)。
app.js:
/*** 我的javascript应用程序* * @module myapp*/ //使用命名空间来定义一个空对象var MYAPP = {}; //定义一个包含两个方法(sum()和multi())的math_stuff对象/*** @namespace MYAPP* class math_stuff*/ MYAPP.math_stuff = { /** * Sums two numbers * * @method sum * param {Number} 是第一个数 * param {Number} 是第二个数 * return {Number} 两个输入的总和 */ sum: function (a, b) { return a + b; }, /** * Multiplies two numbers * param {Number} 是第一个数 * param {Number} 是第二个数 * return {Number} 两个输入相乘后结果 */ multi: function (a, b) { return a * b; }};
@namespace:这里用于命名包含以上对象的全局引用的名称
@class:这里有些命名不当,他实际意思是指对象或者构造函数
@method:定义对象中的方法和方法名
@param:列举函数所使用的参数。其中将参数类型用大括号括起来,并在其后注释参数名及描述。
@return:类似于@param,这里用于描述返回值的,并且该方法没有名称。
@constructor:表明这个“类”实际上是一个构造函数
@property和@type描述了对象的属性。
2. 编写API目的:
为API编写注释不仅仅是一中提供参考文档的简便方法,而且还有其他用途——通过再次审查代码,提高代码质量。
在解决问题时写出的解决方案仅仅是一个初稿。该解决方案可以给出令人期待的输出,但是该方案是否是最佳方案呢?改代码是否可读、易于理解、维护和升级呢?当您再次审视代码时您将更加确定代码哪些部分可以改进——如何使得代码更容易继续更新,移除一些不足之处等。它可以极大地帮助您创建高质量的代码。
十三. 推荐写法
除了三目运算,if,else等禁止简写
// 正确的书写
if (true) {
alert(name);
}
console.log(name);
// 不推荐的书写 if (true) alert(name); console.log(name);
// 不推荐的书写 if (true) alert(name); console.log(name)
在需要以{}闭合的代码段前增加换行,如:for if
// 没有换行,小的代码段无法区分 if (wl && wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
// 有了换行,逻辑清楚多了 if (wl && wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { // 处理merge逻辑 if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
换行可以是空行,也可以是注释
使用Function进行类的定义,不推荐继承,如需继承采用成熟的类库实现继承
// 类的实现 function Person(name) { this.name = name; } Person.prototype.sayName = function() { alert(this.name); }; var me = new Person("Nicholas"); // 将this放到局部变量self function Persion(name, sex) { var self = this; self.name = name; self.sex = sex; }
平时咱们写代码,基本都是小程序,真心用不上什么继承,而且继承并不是JS的擅长的语言特性,尽量少用。如果非要使用的话,注意一点:
function A(){ //...}function B(){ //...}B.prototype = new A();B.prototype.constructor = B; //原则上,记得把这句话加上
继承从原则上来讲,别改变他的构造函数,否则这个继承就显得很别扭了~
使用局部变量缓存反复查找的对象(包括但不限于全局变量、dom查询结果、作用域链较深的对象)
// 缓存对象
var getComment = function() {
var dom = $("#common-container"),// 缓存dom
appendTo = $.appendTo, // 缓存全局变量
data = this.json.data; // 缓存作用域链较深的对象
}
//当需要缓存this时必须使用self变量进行缓存// 缓存
thisfunction Row(name) {
var self = this;
self.name = name;
$(".row").click(function() {
self.getName();
});
}
self是一个保留字,不过用它也没关系。在这里,看个人爱好吧,可以用_this, that, me等这些词,都行,但是团队开发的时候统一下比较好。
十四. 不规范写法
- 句尾没有分号
var isHotel = json.type == "hotel" ? true : false
这个是要引起注意的,比如:
a = b // 赋值(function(){ //....})() // 自执行函数
未加分号,结果被解析成
a = b(function(){//...})() //将b()()返回的结果赋值给a
- 变量命名各种各样
var is_hotel;var isHotel;var ishotel;
- if 缩写
if (isHotel) console.log(true)else console.log(false)
- 使用 eval
var json = eval(jsonText);
- 变量未定义到处都是
function() { var isHotel = 'true'; ....... var html = isHotel ? '<p>hotel</p>' : "";}
- 超长函数
function() { var isHotel = 'true'; //....... 此处省略500行 return false;}