JS高级程序设计之代码的最佳实践
JS代码的最佳实践
一:可维护性
可维护性的代码,以下五点
- 可理解性——其他人可以接受代码兵并理解它的意图和一般途径,而无需原开发人员的完整解释。
- 直观性——代码中的东西一看就能明白,不管其操作过程多么复杂。
- 可适应性——代码以一种数据上的变化不要求完全重写的方法攥写。
- 可扩展性——在代码构架上已考虑到在未来允许对核心功能进行扩展。
- 可调试性——当有地方出错时,代码可以给予足够的信息来尽可能直接地确定问题所在
二:代码约定
可读性
- 函数和方法——每个函数或方法都应该包含一个注释描述其目的和 于完成任务所有可能使用的算法。
- 大段代码——用于完成单个任务的多行代码应该在前面放在一个描述任务的注释。
- 复杂的算法——如果用了一种独特的方式解决某个问题,要注释如何做的。
- Hack——因为存在浏览器差异,JS代码一般会包含一些hack,要明确注释代码中hack.
变量和函数命名
- 变量名应为car或person[尽量语义化]。
- 函数名应该动词开始,如GetName()
- 变量和函数都应该使用合乎逻辑的名字,不要担心长度。长度问题可以通过后其处理和压缩变量类型透明
- 【JS中变量时松散类型的,很容易就忘记变量所应包含的数据类型】
- 使其变量透明
//①通过初始化指定变量类型,缺点是无法用于函数声明中的函数//参数
Varfound = false; //布尔型
Varcount = 0; //数字
Var name= “ ”; //字符串
Varperson = “ ”; //对象//②使用匈牙利标记法来指定变量类型,缺点是让代码在某种程度//上难以阅读
VarbFound; //布尔型
VariCount; //整数
VarsName; //字符串
VaroPerson; //对象//③指定类型注释,不推荐这样的写法
Var found /*:Boolean*/ = false;
Varcount /*:int*/ =10;松散耦合
解耦HTML/JavaScript【尽量不要在HTML写JS函数,这样在出现错误时,会让你不知道错误部分是在HTML部分还是JavaScript部分;也尽量避免在JS中创建大量HTML,在动态生成HTML页面时,出错的话,可能会找不到那段错误】-----JS和HTML 分离
解耦CSS/JS JS在控制CSS时,尽量写成element.className = ’className’;[这样的话,如果将来过更样式表,只需更改所标注的类即可]
解耦应用逻辑/事件处理程序 【web程序中一般都有相当多的事件处理程序,监听着无数不同的事件,要将应用逻辑从事件处理程序分离出来】
Eg
function handleKeyPress(event){
event = EventUtil.getEvent(event);
if(event.keyCode == 13){
var target = EventUtil.getTarget(event);
var value = 5*parseInt(target.value);
if(value > 10){
Document.getElementById(“error-msg”).style.display="block”
}
}
}没有将应用逻辑和事件处理程序分离开,调试将不好调试。
改为【将应用逻辑和事件处理程序分开,容易调试和更改】
function validateValue(value){
value = 5*parseInt(value);
if(value > 10){
document.getElementById("error-msg").style.display="block";
}
}
function handleKeyPress(event){
event – EventUtil.getEvent(event);
if(event.keyCode == 13){
var target = EventUtil.getTarget(event);
validateValue(value);
}
}松散耦合的几条原则
- 勿将event对象传给其他方法,只传来自event对象中所需的数据。
- 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行。
- 任何事件处理程序都应该改处理事件,然后将处理转交给应用逻辑。
编程实践
【创建的web应用往往同时由大量人员一同创作,这种情况下是确保每个人所使用的浏览器环境都有一致和不变的原则】
尊重对象所有权
【不修改不属于你的对象,如果修改的话其他开发人员怎么办,会导致不可预料的错误】
- 不要为实例或原型添加属性。
- 不要为是理货员行添加方法。
- 不要重定义已存在的方法
【解决你的办法的方法如下】
- 创建包含所需功能的新对象,并用它与相关对象进行交互。
- 创建自定义类型,继承需要进行修改的类型,然后可以为自定义类型添加额外功能。
避免全局量
Eg
//两个全局变量,不推荐这种
var name = "jack";
function sayName() {
alert(name);
} //一个全局变量---推荐
var Myapplication = {
name: "jack",
sayName: function () {
alert(this.name);
},
};【命名空间的使用,确定每个人都同意使用的全局对象的名字,可以一直延伸下去】
Eg
//创建全局对象
var Wrox = {};
//为Professional JavaScript创建命名空间
//将书中用到的对象附加上去
Wrox.ProJS.EventUtil = {…}
Wrox.ProJS.EventUtil ={…}避免与null进行比较
与null值进行比较时,无法确定是否为种类型,比较会有bug存在,尝试用以下技术替换
- 如果值为一个引用类型,使用instanceof操作符检查其构造函数。
- 如果值为一个基本类型,使用typeof检查类型。
- 如果是希望对象包含某个特定的方法名,则使用typeof操作符确保指定名字的方法存在于对像上。
使用常量 【虽没有常量的正是概念,但将数据从应用逻辑分离出来,可以在不冒引入错误的风险的同时,就改变数据】 数据和使用它的逻辑分离,有如下几点
- 重复值——任何再多处用到的值都应该抽取一个常量。这就限制了当一个值变了,而另一个没变的时候会造成的错误,这也包含CSS。
- 用户界面字符串——任何用于显示给用户的字符串,都应该抽取出来方便国际化。
- 任意可能会改变的值——每当你在用到字面量值的时候,你都要问一下自己这个值在未来是不是会变化。如果答案“是”,那么这个值就应该被提取出来作为一个常量。
性能
注意作用域
避免全局查找
Eg
function updateUT(){
var doc = document;
var imgs =doc.getElementsByTagName(“img”);
for(var i =0,len =img.length;i<len;i++){
imgs[i].title = doc.title+”image”+i;
}
var msg = doc.getElementById(“msg”);
msg.innerHTML = “updateconmplete”;
}将document对象存在本地的doc变量中,然后在循环中只有一次全局查找
避免with语句
性能非常重要的地方避免使用with语句,和函数类似,with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。由于额外的作用域链查找,在with语句中执行的代码肯定会比外面执行的代码要慢。
选择正确的方法
【在计算机科学中,算法的复杂度是使用 O 符号来表示的,如下】
避免不必要的语句查找
标 记 名 称 描 述
- O(1) 常数 不管有多少值,执行的时间都是恒定的。一般表示简单值和存储在变量中的值
- O(log n) 对数 总的执行时间和值的数量相关,但是要完成算法并不一定要获取每个值。例如:二分查找
- O(n) 线性 总执行时间和值的数量直接相关。例如:遍历某个数组中的所有元素
- O(n 2 ) 平方 总执行时间和值的数量有关,每个值至少要获取n次。例如:插入排序
Eg
var query = window.location.href.substring(window.location.href.indexOf("?"));改为
var url = window.location.href;
var query = url.substring(url.indexOf("?"));有 6 次属性查找: window.location.href.substring() 有 3 次, window.location.href.indexOf() 又有 3 次。只要数一数代码中的点的数量,就可以确定属性查找的次数了。这段代码由于两次用到了 window.location.href ,同样的查找进行了两次,因此效率特别不好。
优化循环
※减值迭代——大多数循环使用一个从 0 开始、增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效。
※ 简化终止条件——由于每次循环过程都会计算终止条件,所以必须保证它尽可能快。也就是说避免属性查找或其他 O(n)的操作。
※ 简化循环体——循环体是执行最多的,所以要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算。
※ 使用后测试循环——最常用 for 循环和 while 循环都是前测试循环。而如 do-while 这种后测试循环,可以避免最初终止条件的计算,因此运行更快
展开循环【在已知的情况下】
避免双重解释
Eg
【当 JavaScript 代码想解析 JavaScript 的时候就会存在双重解释惩罚】
//某些代码求值——避免!!
eval("alert('Hello world!')");
//创建新函数——避免!!
var sayHi = new Function("alert('Hello world!')");
//设置超时——避免!!
setTimeout("alert('Hello world!')", 500);
应该改为;
//已修正
alert("Hello world!");
//创建新函数——已修正
var sayHi = function () {
alert("Hello world!");
};
//设置一个超时——已修正
setTimeout(function () {
alert("Hello world!");
}, 500);如果要提高代码性能,尽可能避免出现需要按照 JavaScript 解释的字符串性能的其他注意事项
- 原生方法较快——只要有可能,使用原生方法而不是自己用 JavaScript 重写一个。原生方法是用诸如 C/C++之类的编译型语言写出来的,所以要JavaScript 的快很多很多。JavaScript 中最容易被忘记的就是可以在 Math 对象中找到的复杂的数学运算;这些方法要比任何用 JavaScript 写的同样方法如正弦、余弦快的多。
- Switch 语句较快 —— 如果有一系列复杂的 if-else 语句,可以转换成单个 switch 语句则可以得到更快的代码。还可以通过将 case 语句按照最可能的到最不可能的顺序进行组织,来进一步优化 switch 语句。
- 位运算符较快 —— 当进行数学运算的时候,位运算操作要比任何布尔运算或者算数运算快。选择性地用位运算替换算数运算可以极大提升复杂计算的性能。诸如取模,逻辑与和逻辑或都可以考虑用位运算来替换。
最小化语句数
多个变量声明
var count = 5,
color = "blue",
values = [1, 2, 3],
now = newDate(); //【推荐】插入迭代值
var name = values[i++]; //【推荐】使用数组和对象字面量
//用 4 个语句创建和初始化对象——浪费
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.sayName = function () {
alert(this.name);
};
// 只用一条语句创建和初始化对象【推荐】优化DOM交互
最小化现场更新
一旦你需要访问的 DOM 部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。之所以叫现场更新,是因为需要立即(现场)对页面对用户的显示进行更新。每一个更改,不管是插入单个字符,还是移除整个片段,都有一个性能惩罚,因为浏览器要重新计算无数尺寸以进行更新。现场更新进行得越多,代码完成执行所花的 时间就越长
var list = document.getElementById("myList"),
fragment = document.createDocumentFragment(),
item,
i;
for (i = 0; i < 10; i++) {
item = document.createElement("li");
fragment.appendChild(item);
item.appendChild(document.createTextNode("Item" + i));
}
list.appendChild(fragment); //【推荐】第二种
var list = document.getElementById("myList");
var html = "",
i;
for (i = 0; i < 10; i++) {
html += "<li>Item " + i + "</li>";
}
list.innerHTML = html; //【推荐】注意 HTMLCollection
((varimages = document.getElementsByTagName("img")), i, len);
for (i = 0, len = images.length; i < len; i++) {
//处理
}编写JavaScript 的时候,一定要知道何时返回HTMLCollection 对象,这样你就可以最小化对他们的访问。
发生以下情况时会返回HTMLCollection 对象:
- 进行了对 getElementsByTagName() 的调用;
- 获取了元素的 childNodes 属性;
- 获取了元素的 attributes 属性;
- 访问了特殊的集合,如 document.forms 、 document.images 等
部署
构建过程
知识产权问题 —— 如果把带有完整注释的代码放到线上,那别人就更容易知道你的意图,对它再利用,并且可能找到安全漏洞。
文件大小 —— 书写代码要保证容易阅读,才能更好地维护,但是这对于性能是不利的。浏览器并不能从额外的空白字符或者是冗长的函数名和变量名中获得什么好处。
代码组织 —— 组织代码要考虑到可维护性并不一定是传送给浏览器的最好方式Ant 由于其简便的文件处理能力而非常适合JavaScript 编译系统。例如,可以很方便地获得目录中的所有文件的列表,然后将其合并为一个文件,如下所示:
<project name="JavaScript Project"default="js.concatenate">
<!-- 输出的目录 -->
<property name="build.dir"value="./js" />
<!-- 包含源文件的目录 -->
<property name="src.dir"value="./dev/src" />
<!-- 合并所有 JS 文件的目标 -->
<!-- Credit: Julien Lecomte,http://www.julienlecomte.net/blog/2007/09/16/ -->
<target name="js.concatenate">
<concatdestfile="${build.dir}/output.js">
<filelist dir="${src.dir}/js"files="a.js, b.js"/>
<fileset dir="${src.dir}/js"includes="*.js" excludes="a.js, b.js"/>
</concat>
</target>
</project>
SampleAntDir/build.xml该 build.xml 文件定义了两个属性:输出最终文件的构建目录,以及 JavaScript 源文件所在的源目录。
目标 js.concatenate 使用了元素来指定需要进行合并的文件的列表以及结果文件所要输出的位置。元素用于指定 a.js 和 b.js 要首先出现
在合并的文件中 元素指定了之后要添加到目录中的其他所有文件,a.js和 b.js 除外。结果文件最后输出到/js/output.js。如果安装了 Ant,就可以进入 build.xml 文件所在的目录,并运行以下命令:ant然后构建过程就开始了,最后生成合并了的文件。如果在文件中还有其他目标,可以使用以下代码仅执行 js.concatenate 目antjs.concatenate
可以根据需求,修改构建过程以包含其他步骤。在开发周期中引入构建这一步能让你在部署之前对JavaScript 文件进行更多的处理。
压缩
压缩器一般进行如下一些步骤:
删除额外的空白(包括换行);
删除所有注释;
缩短变量名
HTTP 压缩
配重指的是实际从服务器传送到浏览器的字节数。因为现在的服务器和浏览器都有压缩功能,这个字节数不一定和代码长度一样。所有的五大 Web 浏览器(IE、Firefox、Safari、Chrome 和 Opera)都支持对所接收的资源进行客户端解压缩。这样服务器端就可以使用服务器端相关功能来压缩 JavaScript 文件。一个指定了文件使用了给定格式进行了压缩的 HTTP 头包含在了服务器响应中。接着浏览器会查看该 HTTP 头确定文件是否已被压缩,然后使用合适的格式进行解压缩。结果是和原来的代码量相比在网络中传递的字节数量大大减少了。
以上为JS高级程序设计笔记,欢迎大家观看,分享,如有写错的地方,请指正出来,大家一同进步。