JavaScript高级程序设计第24章(最佳实践)

可维护性

背景:编写可维护的代码很重要,因为大部分开发人员都花费大量时间维护他人代码。很多情况下是以他人的工作成果为基础的。确保自己代码的可维护性,以便其他开发人员在此基础上更好的开展工作。

遵循四个特点:

a.可理解性:其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释

b.直观性:代码中的东西一看就明白,不管其操作过程多么复杂

c.可适应性:代码以一种数据上的变化不要求完全重写的方法撰写

d.可扩展性:在代码架构上已考虑到未来允许对核心功能进行扩展

e.可调试性:当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定稳定所在


①代码约定:

1.可读性:

和代码的缩进很大相关,通常会使用若干空格而非制表符来进行缩进,这是制表符在不同的文本编辑器中显示效果不同。

注释:在函数和方法包含一个注释,描述其目的和用于完成任务所可能使用的算法。

命名:变量名应为名词;函数名应该以动词开始。返回布尔类型值的函数一般以is开头,如isEnable()


2.变量类型透明

由于在JavaScript中变量时松散类型,很容易就忘记所包含的数据类型

a.初始化

当定义了一个变量后,它应该被初始化为一个值,来暗示它将来应该如何应用。但缺点是它无法用于函数声明中的函数参数

var found = false; //布尔值 var name = "";  //字符串  var person = null; //对象

b.匈牙利标记法

在变量名之前加上一个或多个字符来表示数据类型。缺点是没有了增加代码某种程度上的难以阅读

"o"代表对象,"s"代表字符串,"i"代表整数,"f"代表浮点数,“b”代表布尔型

var bFount, iCount, sName, oPerson;


②松散耦合

只要应用的某个部分过分依赖于另一部分,代码就是耦合过紧,难于维护。典型的问题如:对象直接饮用另一个对象,并且当修改其中一个的同时需要修改另外一个

1.解耦HTML/JavaScript

a.直接写在HTML中的JavaScript,使用包含内联代码的<script>元素或者是使用HTML属性来分配事件处理程序,都是过于紧密的耦合(出现错误很难判断是出在HTML文件还是JavaScript文件中

b.JavaScript中包含大量HTML也是属于紧密耦合的情况。这通常会出现在使用innerHTML来插入一段HTML文本到页面上的这种情况中。一般来说,你应该避免在JavaScript中创建大量HTML。HTML呈现应该尽可能与JavaScript保持分离。

当JavaScript用于插入数据时,尽量不要直接插入标记。一般可以在页面中直接包含并隐藏标记,然后等到整个页面渲染好了之后,就可以用JavaScript显示该标记,而非生成它。(个人觉得,不能什么本不该放出来的内容也隐藏起来,有些显示内容就是应该是到了特定的时候才显示,这种内容就应该动态生成)。另一种方法是进行Ajax请求并获取更多要显示的HTML,这个方法可以让同样的渲染层(PHP、JSP、Ruby等等)来输出标记,而不是直接嵌在JavaScript中。(???这样的理由是??)

总结:将HTML和JavaScript解耦可以在调试过程中节省时间,更加容易确定错误的来源,也减轻维护的难度:更改行为只只要在JavaScript文件中进行,而更改标记只要在渲染文件


2.解耦CSS/JavaScript

现代Web应用常常要使用JavaScript来更改样式,虽然不可能完全将CSS和JavaScript解耦,但是还是能将耦合更松散。这是通过动态更改样式类而非特定样式来实现。

element.classNmae = "edit";


3.解耦应用逻辑/事件处理程序

function handleKeyPress(event){
    event = EventUtil.getEvent(event);
    if(event.keyCode == 13){
        var target = EventUtil.getTarget(evnt);
        var value = 5 * parseInt(target.value);
        if(value > 10){
            document.getElementById("erroe-msg").style.display = "block";
        }
    }
}
 改成

function validateValue(value){
    value = 5 * parseInt(target.value);
    if(value > 10){
        document.getElementById("erroe-msg").style.display = "block";
    }  
}

function handleKeyPress(event){
    event = EventUtil.getEvent(event);
    if(event.keyCode == 13){
        var target = EventUtil.getTarget(evnt);
        validateValue(target.value);
    }
}

是不是好多了,不仅增加可调试性(可以判断事件处理程序没有被调用还是应用逻辑失败,并且不附加到事件的情况下测试代码),也增加了可适应性(有需要同样应用逻辑的就可以复用了,就上面那些代码来看,点击enter键和鼠标click事件的应用逻辑很可能一样的)。

以下是要牢记的应用和业务逻辑之间逻辑耦合的几条原则

a.勿将event对象传给其他方法;只穿来自event对象中所需的数据

b.任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行

c.任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。

牢记这几条可以在任何代码中都获得极大的可维护性的改性,并且为进一步的测试和开发制造了很多可能


③编程实践

背景:在企业环境中创建的Web应用往往是同时由大量人员一同创作。这种情况下的目标是确保每个人所使用的浏览器环境都有一致和不变的规则,因为,最好坚持以下一些编程实践

1.尊重对象所有权

意思是以不能修改不属于你的对象。更具体地说:

不要为实例或原型(不仅包括自定义类型和对象,也包括Object、String等原生类型和对象)添加属性、不要为实例或圆形添加方法、不要冲定义已存在的方法

解决方法:

  a.创建包含所需功能的新对象,并用它与相关对象进行交互

  b.创建自定义类型,继承需要进行修改的类型。然后为自定义类型添加额外功能


2.避免全局量

尽可能避免全局变量和函数,最多创建一个全局变量,让其他对象和函数存在其中

var name = "Nicholas";
function satName(){
  alert(name);
}
改成

var MyApplication = {
  name: "Nicholas";
  sayName: function(){
    alert(this.name);
  }
}

这样是不是好多了。而且还消除了一些问题。首先,变量name覆盖了window.name属性,可能会与其他功能产生冲突;其次,它有助于消除功能作用域之间的混淆。


3.避免与null进行比较

4.使用常量

重复值——任何在多处用到的值都应抽取为一个常量。这就限制了当一个值变了而另一个没变的时候造成的错误。这也包含了CSS类名

用户界面字符串——任何用于显示给用户的字符串,都应被抽取出来以方便国际化

URLs——在Web应用中,资源位置很容易变更,所以推荐用一个公共地方存放所有的URL

任何可能会更改的值——每当你在用到字面量值的时候,你都要问一下自己这个值在未来是不是会变化。如果答案是“yes”,那么这个值就应该被提取出来作为一个常量

var Constants = {
  INVALID_VALUE_MSG: "Invalid value!";
  INVALID_VALUE_URL: "/errprs/invalid.php"
};

function validate(value){
  if(!value){
    alert(Constants.INVALID_VALUE_MSG);
    location.href = Constants.INVALID_URL;
  }
}


这样写的话,就算URL改变要修改也轻松多了


2.性能

背景:因为JavaScript最初是一个解释型语言,执行速度要比编译型语言慢很多。

①注意作用域

随着作用域链中的作用域数量的增加,访问当前作用域以外的变量的时间也在增加,访问当前作用域以外的变量的时间也在增加。访问全局变量总是要比访问局部变量慢,因为需要遍历作用域链。只要能减少化肥在作用域链上的时间,就能增加脚本的整体性能。

a.避免全局查找

function updateUI(){
  var imgs = document.getElementsByTagName("img");
  for (var i=0, len=imgs.length; i<len; i++){
    imgs[i].title = document.title + " image" + i;
  }
  var msg = document.getElementById("msg");
  msg.innerHTML = "Update complete";
}

这个函数可能看上去完全正常,但是它包含了三个对于全局document对象的引用。如果for执行上百次, 每次都会要进行作用域链查找 。通过创建一个指向document对象的局部变量,就可以 通过限制一次全局查找来改进这个函数的性能

function updateUI(){
  var doc = document;
  var imgs = doc.getElementsByTagName("img");
  for (var i=0, len=imgs.length; i<len; i++){
    imgs[i].title = doc.title + " image" + i;
  }
  var msg = doc.getElementById("msg");
  msg.innerHTML = "Update complete";
}

将在一个函数中会用到多次的全局对象存储为局部变量总是没错的

b.避免with语句

with语句增加了作用域链的长度,而且必须使用with语句的情况很少,因为它主要用于消除额外的字符。在大多数情况下,可以用局部变量完成相同的事情而不引入新的作用域。

②选择正确方法

1.避免不必要的属性查找

O(1):访问常量和数组元素

O(n):访问对象上的属性(window.location.href.indexof("?")),代码上3个点,属性查找3此

var url = window.location.href;
var query = url.substring(url.indexOf("?"));

var query = window.location.href.substring(window.location.href.indexOf("?"));
节省33%,因为属性查找从6次降到6次。一两次属性查找不会导致显著的性能问题,但是进行成百上千此则肯定会增加执行速度。


2.优化循环

在其他语言中对于循环优化有大量研究,这些技术也可以应用于JavaScript。

一个循环的基本优化步骤如下所示:

(1)减值迭代——大多数循环使用一个从0开始、增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更多高效(其实也是简化终止条件,因为变成了i>=0)

(2)简化终止条件——由于每次循环过程都会计算终止条件,所以必须保证它尽可能地块。也就是说避免属性查找或其他O(n)的操作

(3)简化循环体——循环体是执行最多,所以要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算

(4)使用后测试循环——最常用for循环和while循环都是前测试循环。而如do-while这种后测试循环,可以避免最初终止条件的计算,因此运行更快(不过要确保要处理的值至少有一个。空数组会导致多余的一个循环而"前测试"循环可以避免)


3.展开循环

当循环次数是确定的,消除循环并使用多次函数调用往往更快

//消除循环
process(values[0]);
process(values[1]);
process(values[2]);

这样展开循环可以消除建立循环和处理终止条件的额外开销

如果循环中的迭代次数不能事先确定,那可以考虑一种叫做Duff装置的技术。Duff装置的基本概念是通过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句

//credit: Jeff Grenenberg for JS implementation of Duff's Device
//假设values.length > 0
var iterations = Math.ceil(values.length /8);
var startAt = values.length % 8;
var i = 0;
do {
  switch(startAt){
    case 0: process(values[i++]);
    case 7: process(values[i++]);
    case 6: process(values[i++]);
    case 5: process(values[i++]);
    case 4: process(values[i++]);
    case 3: process(values[i++]);
    case 2: process(values[i++]);
    case 1: process(values[i++]);
  }
  startAt = 0;
} while(--iterations > 0);
展开循环可以提升大数据集的处理速度

更快的Duff装置技术

//credit: Speed Up Your Site (New Riders, 2003)
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;

if(leftover > 0){
  do {
    process(values[i++]);
  } while(--leftover > 0);
}
do {
  process(value[i++]);   //执行8次
  process(value[i++]);
  process(value[i++]);
  process(value[i++]);
  process(value[i++]);
  process(value[i++]);
  process(value[i++]);
  process(value[i++]);
} while(--iterations > 0);

针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销可能得不偿失。它是要花更多的代码来完成同样的任务。如果处理的不是大数据集,一般来说并不值得


4.避免双重解释

当JavaScript代码想解析JavaScript的时候就会存在双重解释惩罚。当使用eval()函数或者是Function构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况

//某些代码求值——避免!!
eval("alert('hell world!')");

//创建新函数——避免!!
var sayhI = new Function("alert('hello world!')");

//设置超时——避免!!
setTimeout("alert('hello world!')", 500);
在以上这些例子中,都要解析包含了JavaScript代码的字符串。这个操作时不能在初始的解析过程中完成的,因为代码是包含在字符串中的,也就是说在JavaScript代码运行的同时必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,所以这种代码要比直接解析慢很多。如果要提高代码性能,尽可能避免出现需要按照JavaScript解释的字符串。


5.性能提高的其他注意事项

a.原生方法更快——原生方法是用C/C++之类的编译型语言写出来的。JavaScript中最容易被忘记的就是可以在Math对象中找到复杂的数学运算;这些方法

b.Switch语言较快——如果有一系列复杂的if-else语言,可以转换为的那个switch语言则可以得到更快的代码。还可以通过将case语句按照最可能的到最不可能的顺序进行组织,来进一步优化switch语句

c.位运算符较快——当进行数学运算的时候,位运算操作比任何布尔运算或者算数运算快。诸如取模,逻辑与和逻辑或都可以考虑用位运算来替换。(位运算直接对内存数据进行操作,不用转成十进制,因此处理速度非常快,不过感觉好麻烦,操作不简单粗暴,还是算了吧)


③最小化语句数

JavaScript代码中的语句数量也影响所执行的操作的速度。完成多个操作的单个语句要比完成单个操作的多个语句块。所以找出可以组合在一起的语句,以减少脚本整体的执行事件。

1.多个变量声明

var count = 5; color = "blue"

利用JavaScript弱语言类型的特点,可以这样声明

var count = 5, color = "blue";

2.插入迭代值

var name = values[i];

i++;

变成

var name = values[i++];

3.使用数组和对象字面量

//用4个语句创建和初始化数组——浪费
var values = new Array();
values[0] = 123;
values[1] = 124;
values[2] = 125;

//用4个语句创建和初始化对象——浪费
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.sayName = function(){
  alert(this.name);
}

简化后

//简化成一条语句
var values = [123, 124, 125];

//变成一条语句
var person = {
  name: "Nicholas";
  age: 29;
  sayName: function(){
    alert(this.name);
  }
}


4.优化DOM交互

•最小化现场更新

•使用innerHTML

•使用事件代理

•注意HTMLCollection(编写JavaScript的时候,一定要知道何时返回HTMLCollection对象,这样你可以最小化他们的访问)

会返回HTMLCollections对象:

进行了对getElementsByTagName的调用;

获取了元素的childNodes属性

获取了元素的attributes属性

访问了特殊的集合,如document.forms、document.images等


部署

推荐Web应用中尽可能使用最少的JavaScript文件,是因为HTTP请求是Web中的主要性能瓶颈之一。记住通过<script>标记JavaScript文件,当代码下载并运行的时候会停止其他所有的下载。

JS部署过程:验证-》压缩-》合并

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值