js笔记 -- 最佳实践

来源:互联网 发布:怎么找网络水军公司 编辑:程序博客网 时间:2024/05/22 03:08

最佳实践

1.可维护性

  • 可理解性--其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释。
  • 直观性--代码中的东西一个就能明白,不管其操作过程多么复杂
  • 可适应性--代码以一种数据上的变化不要求完全重写的方法撰写
  • 可扩展性--在代码架构上已考虑到在未来允许对核心功能进行扩展
  • 可调试性--当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定问题所在

1.代码约定

1.可读性

缩进:通常使用4个空格,制表符在不同文本编辑器中显示效果不同

注释:

  • 函数和方法  --  每个函数或方法都应该包含一个注释,描述其目的和用于完成任务所可能使用的算法,参数意义,返回值
  • 大段代码  --  用于完成单个任务的多行代码应该在前面放一个描述任务的注释
  • 复杂的算法 --  如果使用了一种独特的方式解决某个问题,则要在注释中解释你是如何做的
  • Hack --  处理浏览器兼容 问题时添加注释

2.变量和函数命名

  • 变量名应为名词,如car或person
  • 函数名应以动词开始,如getName()。返回布尔类型值的函数一般以is开头,如isEnable()。
  • 变量和函数都应使用合乎逻辑的名字,不要担心长度。长度问题可以通过后处理和压缩来缓解

3.变量类型透明

一:定义了一个变量后,给定初始化值来暗示它将来应该如何应用。

var found = false; //布尔var count = 1;  //数字var name = "";  //字符串var person = null; //对象
二:使用匈牙利标记法来指定变量类型。在变量名之前加上一个或多个字符来表示数据类型。"o"对象,"s"字符串,"i"代表整数,"f"代表浮点数,“b”代表布尔型

var bFound = false; //布尔var iCount = 1;  //数字var sName = "";  //字符串var oPerson = null; //对象
三:添加指定类型的注释

var bFound /*:Boolean*/= false;  var iCount /*:int*/= 1;   var sName /*:string*/= "";   var oPerson /*:Object*/= null;

2.松散耦合

在Web上,HTML和JavaScript各自代表了解决方案中的不同层次:HTML是数据,JavaScript是行为。

1.解耦HTML/JavaScript

紧密耦合,如:

<script type="text/javascript">document.write("Hello world!");</script><input type="button" value="Click" onclick="doSomething()" />
避免在js中创建大量HTML。
function insertMessage(msg){var div = document.getElementById("div");container.innerHTML = "<div class=\"msg\">.....";}

2.解耦CSS/JavaScript

通过动态更改样式类而非特定样式。

element.style.color = "red";//直接修改样式,紧密耦合element.className = "td";//修改样式名

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

//定义事件处理程序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").style.display = block;}}}//应用逻辑与事件处理程序分开function validateValue(value){var value = 5*parseInt(target.value);if(value>10){document.getElementById("error").style.display = block;}}//事件处理程序function handleKeyPress(event){event = EventUtil.getEvent(event);//获取事件对象if(event.keyCode == 13){var target = EventUtil.getTarget(event);//应用逻辑validateValue(target.value);}}

好处:

  • 更容易修改触发特定过程的事件(如,由鼠标点击事件触发过程,改为按键也能触发)
  • 可以在不附加到事件的情况下测试代码,更容易单元测试或是自动化应用流程
原则:
  • 不要将event对象传递给其他方法;只传来自event对象中的数据
  • 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行
  • 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑

4.编程实践

1.尊重对象所有权

永远不要修改不是由自己所有的对象。如原生 Array,document对象等。

当需要在原对象上添加功能方法时:1.创建包含所需功能的新对象,并用它与相关对象进行交互

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

2.避免全局量

应尽可能避免全局变量和函数。

//两个全局量--避免var name = "zhangsan";function sayName(){console.log(name);}//一个全局量 -- 推荐var MyApplication = {name:"zhangsan",sayName:function(){console.log(this.name);}}

单一的全局量的延伸便是命名空间的概念,命名空间包括创建一个用于放置功能的对象。

  • ZHANGSAN.util.Dom --处理DOM的方法
  • ZHANGSAN.util.Event -- 与事件交互的方法
  • ZHANGSAN.lang -- 用于底层语言特性的方法
单一的全局对象ZHANGSAN作为一个容器。这种方式将功能组合在一起的对象,叫做命名空间。
开发中确定每个人使用的全局对象的名字尽可能唯一(如:公司名字)。
var SIN = {};SIN.ProJs = {};SIN.ProJs.EventUtil = {};SIN.ProJs.CookieUtil = {};
虽然命名空间会需要多写一些代码,但是对于可维护的目的而言是值得的。有助于确保代码可以在同一个页面上与其他代码以无害的方式一起工作。

3.避免与null进行比较

function sortArray(values){//避免,无法检测是否为Array类型,Stringif(values!=null){// values instanceof Array 更合适value.sort(comparator);}}
  • 如果值应为引用类型,使用instanceof操作符检查其构造函数
  • 如果值应用一个基本类型,使用typeof检查其类型
  • 如果是希望对象包含某个特定的方法名,则使用typeof操作符确保指定名字的方法存在于对象上
代码中的null比较越少,就越容易确定代码的目的,并消除不必要的错误。

4.使用常量

尽管JavaScript没有常量的正式概念,但它还是很有用的。这种将数据从应用逻辑分离出来的思想,可以在不冒引入错误的风险的同时就改变数据。如:
function validate(value){if(!value){console.log("Invalid value!");location.href = "/errors/invalid.action";}}//声明常量var Constants = {INVALID_VALUE_MSG:"Invalid value!",INVALID_VALUE_URL:"/errors/invalid.action";}function validate(value){if(!value){console.log(Constants.INVALID_VALUE_MSG);location.href = Constants.INVALID_VALUE_URL;}}
注意值的类型:
  • 重复值 --- 任何在多出用到的值都应该抽取为一个常量。这就限制了当一个值变了而另一个没变的时候会造成的错误,包括CSS类名。
  • 用户界面字符串 --- 任何用于显示给用户的字符串,都应被抽取出来以方便国际化。
  • URLs --- 在Web应用中,资源位置很容易变更,所以推荐用一个公共地方存放所有的URL。
  • 任意可能会更改的值 --- 每当你在用到字面量值的时候,你都要问一下自己这个值在未来是不是会变化。如果答案是“是”,那么这个值就应该被提取出来作为一个常量。

2.性能

JavaScript最初是一个解释型语言,执行速度比编译型语言慢得多。Chrome是第一款内置优化引擎,将JavaScript编译成本地代码的浏览器。

1.注意作用域

随着作用域链中的作用域数量的增加,访问当前作用域以外的变量的时间也在增加。访问全局变量总是要比访问局部变量慢,因为需要遍历作用域链。

1.避免全局查找

可能优化脚本性能最重要的就是注意全局查找。使用全局变量和函数肯定要比局部的开销更大,因为要涉及作用域链上的查找,如:

function updateUI(){var imgs = document.getElementsByTagName("myImg");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;//定义局部变量指向document对象,只需查找一次全局变量var imgs = doc.getElementsByTagName("myImg");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";}

2.避免with语句

在性能非常重要的地方必须避免使用with语句。和函数类型,with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。导致额外的作用域链查找。

2.选择正确的方法

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

O(1)常数,不管多少值,执行的时间都是恒定的。一般表示简单值和存储在变量中的值
O(log n) 对数,总的执行时间和值的数量有关,但是要完成算法并不一定要获取每个值。如:二分查找
O(n) 线性,总执行时间和值的数量直接相关。如:遍历某个数组中的所有元素
O(n2) 平方,总执行时间和值的数量有关,每个值至少要获取n词。如:插入排序

使用变量和数组要比访问对象上的属性更有效率:
var values = [2,3,4];var sum = values[0]+values[1];console.log(sum);
访问数组不管值有多少,都是直接访问,O(1)

var values = {first:1,second:2}
访问对象,需要根据原型链中对拥有该名称的属性进行一次搜索,属性查找越多,执行时间越长,O(n)
一旦多次用到对象属性,应该将其存储在局部变量中。第一次访问是O(n),但后续访问都会是O(1)。

一般说,只要能减少算法复杂度,就要尽可能减少,尽可能多地使用局部变量将属性查找替换为值查找。可以用数字化的数组位置进行访问,也可以使用命名属性,优先使用数字位置

2.优化循环

  1. 减值迭代 ---大多数循环使用一个从0开始,增加到某个特定值的迭代器。很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效
  2. 简化终止条件 --- 由于每次循环过程都会计算终止条件,所以必须保证它尽可能快。也就是说避免属性查找或其他O(n)的操作。
  3. 简化循环体 --- 循环体是执行最多的,所以要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算
  4. 使用后测试循环 --- 最常用for循环和while循环都是前测试循环。而如do-while这种后测试循环,可以避免最初终止条件的计算,因此运行更快
如:
//每次都获取values的length属性去比较,O(n)for(var i=0;i<values.length-1;i++){process(values[i]);}//只获取一次values的length属性和0进行比较,O(1)for(var i=values.length -1;i>=0;i--){}//改为后测试循环var i = values.length - 1;//属性访问定义成一个局部变量,避免不必要的属性查找if(i > -1){do{process(values[i]);}while(--i >=0);}
注意:后测试循环时必须确保要处理的值至少有一个。空数组会导致多余的一次循环。

3.展开循环

当循环次数是确定的,消除循环并使用多次函数调用往往更快。
process(values[0]);process(values[1]);process(values[2]);
加入values只有三个值,分开调用可以消除建立循环和处理终止条件的额外开销,使代码运行得更快。

Duff技术:通过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句。
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;//以后每次循环都是8个值}while(--iterations > 0);
如果数组中有10个值,startAt等于2,那么最开始的时候process()只会被调用2次。后面的循环中startAt被置为0,之后每次循环都会调用8次process()。

更快的Duff技术: 将do-while循环分成2个单独的循环。
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(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);}while(--iterations > 0);
剩余的计算部分不会在实际循环中处理,而是在初始化循环中进行除以8的操作。当处理掉了额外的元素,继续执行每次调用8次process()的主循环。这个方法几乎比原始的Duff实现快上40%。

4.避免双重解释

当使用eval()函数或者是Function构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况。
//某些代码求值 --避免!eval("console.log('Hello world')");//创建新函数 --避免!!var sayHi = new Function("console.log('Hello world!')");//设置超时 -- 避免!setTimeout("console.log('Hello world!')",200);//修正alert('Hello world');var sayHi = function(){alert('Hello world');}setTimeout(function(){console.log("hello world");},200);
前面的操作不能够在初始化的解析过程中完成,因为代码在字符串中,因此JS代码运行同时必须启动一个解析器来解析新的代码(额外开销)。

5.性能的其他注意事项

  • 原生方法较快 --只要有可能,使用原生方法而不是自己用JS重写一个。原生方法是用诸如C/C++之类的编译型语言写的,比JS快很多。Math对象中很多复杂的数学运算比JS写的同样方法快的多。
  • Switch语句较快 -- 如果有一系列复杂的if-else语句,可以转换成单个switch语句则可以得到更快的代码。还可以通过将case语句按照最可能的到最不可能的顺序进行组织,来进一步优化switch语句。
  • 位运算符较快 -- 当进行数学运算的时候,位运算操作要比任何布尔运算或者算术运算快。选择性地用位运算替换算术运算可以极大提升复杂计算的性能。诸如取摸,逻辑与和逻辑或都可以考虑用位运算来替换。

3.最小化语句数

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

1.多个变量声明

//4个语句var count = 1;var color = "red";var values = [1,2,3];var now = new Date();//一个语句var count = 1,color = "red",values = [1,2,3],now = new Date();

2.插入迭代值

当使用迭代值的时候,尽可能合并语句。如:

var name = values[i];i++;//合并var name = values[i++];

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

尽量使用数组和对象的字面量表达方式来消除不必要的语句。

var array = [1,2,3,4];var person = {name:"zhangsan",age:24,sayHi:function(){console.log("hi!");}};

4.优化DOM交互

在JS个方面中,DOM是最慢的一部分。DOM操作与交互要消耗大量时间,因为它们往往需要重新渲染整个页面或者某一部分。

1.最小化现场更新

一旦需要访问的DOM部分是已经显示的页面的一部分,那么就是在进行一个现场更新。不管是插入单个字符还是移除整个片段,都有一个性能惩罚,因为浏览器要重新计算无数尺寸以进行更新。现场更新进行得越多,代码完成执行所花的时间就越长;完成操作所需的现场更新越少,代码就越快。

var list = document.getElementById("list"),item,i;for(i=0;i<10;i++){item document.createElement("li");list.appendChild(item);item.appendChild(document.createTextNode("item"+i));}//改进var list = document.getElementById("list"),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);
改进后只有一次现场更新。

2.使用innerHTML

创建DOM节点的方法:1.使用createElement()和appendChild()之类的DOM方法。2.使用innerHTML

对于小的DOM更改而言,两种方法效率差不多。对于大的DOM更改,innerHTML比标准DOM方法创建同样的DOM结构快得多。


当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。

var list = document.getElementById("list"),html = "",i;for(i=0;i<10;i++){html += "<li>Item "+ i + "</li>";}list.innerHTML = html;

3.使用事件代理

页面上的事件处理程序的数量和页面响应用户交互的速度之间有个负相关。为了减少这种惩罚,最好使用事件代理。

利用事件冒泡的原理,在事件目标的父辈节点上进行统一处理。减少事件处理程序的个数

3.部署

写的JS代码就是在浏览器中测试的代码,代码不应该原封不动地放入浏览器中:
  • 只是产权问题 -- 如果把带有完整注释的代码放到线上,那别人就更容易知道你的意图,对它再利用,并且可能找到安全漏洞。
  • 文件大小 -- 书写代码要保证容易阅读,才能更好地维护,但是这对于性能是不利的。浏览器并不能从额外的空白字符串或者是冗长的函数名和变量名中获得什么好处。
  • 代码组织 --组织代码要考虑到可维护性并不一定是传送给浏览器的最好方式。
最好给JavaScript文件定义一个构建过程。
最好避免使用一个文件存放所有的JavaScript,遵循一下面向对象语言中的典型模式:将每个对象或自定义类型分别放入其单独的文件中。可以确保每个文件包含最少量的代码。

将代码分离成多个文件只是为了提高可维护性,并非为了部署。要进行部署的时候,需要将这些源代码合并成为一个或几个归并文件。推荐Web应用中尽可能使用最少的JS文件,是因为HTTP请求是Web中的主要性能瓶颈之一。通过<script>标记引用JS文件是一个阻塞操作,当代码下载并运行的时候会停止其他所有的下载。

1.压缩

压缩实际讨论的是:代码长度和配重(Wire weight)。长度:浏览器所需解析的字节数。配重:实际从服务器传送到浏览器的字节数。

1.文件压缩

JS并非编译为字节码,而是按照源代码传送的,代码文件通常包含浏览器执行所不需要的额外的信息和格式。注释,空白,以及长长的变量名和函数名。我们可以使用压缩工具减少文件的大小。

  • 删除额
  • 外空白(包括换行)
  • 删除所有注释
  • 缩短变量名

2.HTTP压缩

配重指的是实际从服务器传送到浏览器的字节数。因为现在的服务器和浏览器都有压缩功能,这个字节数不一定和代码长度一样。浏览器基本都支持对所接收的资源进行客户端解压缩。这样服务器端就可以使用服务器端相关功能来压缩JavaScript文件。文件压缩格式及相关信息,包含在服务器HTTP响应消息头中。浏览器会查看该HTTP头信息确定文件是否已被压缩,然后根据格式进行解压缩。


对Apache服务器,可以给httpd.conf文件或是.htaccess文件添加下面代码启用对JS的自动压缩:

#tell mod_deflate the JS that need to be include#AddOutputFilterByType DEFLATE application/x-javascript
虽然<script>的type属性用的是text/javascript,但是JavaScript文件一般还是用application/x-javascript作为其服务的MIME类型。

可以节省大约70%的文件大小,因为JS都是文本文件,可以非常有效地进行压缩。浏览器解压缩需要花一些时间,但是代价是值得的。

0 0