编写高质量的JavaScript代码

一、编写高质量的JS代码

1、团队合作,如何避免js冲突

  1)用匿名函数将脚本包起来,可以有效控制全局变量,避免冲突隐患。

  2)为了不同代码段之间相互通信,需要定义全局变量

  3)当需要的全局变量太多时,为防止全局变量泛滥,定义一个全局对象,公用的变量作为全局对象的属性

  4)为防止相同属性相互覆盖的问题,引入命名空间的概念。GLOBAL对象的属性不要直接挂在GLOBAL对象上,而是挂在此匿名函数的命名空间下。

  5)如果同一个匿名函数中的程序非常复杂,变量名称很多,命名空间还可以进一步扩展,生成二级命名空间。

  6)将生成命名空间的功能定义成一个函数,方便调用。

  7)以上解决了代码冲突问题,但是可维护性并不强,我们需要添加适当的注释,以提高代码的可维护性。

总结:让JS不产生冲突,需要避免全局变量的泛滥,合理使用命名空间以及为代码添加可维护的注释。

2、给程序一个统一的入口——window.onload和DOMReady

 1)网页中的javaScript从功能上,应该分为两大部分——框架部分和应用部分,框架部分提供的是对javaScript代码的组织作用,包括定义全局变量,定义命名空间方法等。它和具体应用无关。

应用部分提供的是页面应用逻辑,不同页面会有不同功能。我们需要将应用部分的代码组织起来,给它们一个统一的入口。function init(){应用部分代码}

 2)javaScript是脚本语言,加载到哪儿执行到哪,如果程序控制某个节点,而该节点当时还没有被载入,程序就会报错。我们可以监听window对象的onload事件,当window触发onload事件后调用脚本

3)window.load需要当页面完全加载完成时才会触发包括图片、flash等富媒体,DOMReady只判断页面内所有DOM节点是否已经全部生成,至于节点的内容是否加载完成并不关心,所有DOMReady触发

速度比window.onload更快,更适用于来调用初始化函数。

4)DOMReady并不是原生javascript支持的事件,它不能像window.onload那样直接调用,一般我们都是结合JS框架来使用它。如jQuery中$(document).ready(init);如YUI中YAHOO.until.Event.onDOMReady(init)

5)在</body>之前调用入口函数也可以实现DOMReady的效果

6)在实际工作中,网站的头部和尾部通常会做成单独的文件,用服务器端语言include到网页中,所以我们通常将网页拆分成三个文件:头部文件、尾部文件、主体文件

   头部和尾部调用,如果仅仅为静态HTML的话,只能采用框架。

   如果采用动态语言,则可以用服务器端包含,把头部和尾部文件调用进来。<!--#include virtual="html/head.html"-->这种包含方式是ASP动态语言的。

  ASP的运行需要服务器的支持。不能直接在本地用浏览器浏览,但是可以在本地搭建一个ASP服务器,然后运行。网络上也有ASP本地测试工具,把该文件放到ASP站点的根目录下双击运行就可以浏览ASP网页了

7)if(init){

  init();

}

在调用init函数之前,先判断页面内是否已经定义了init函数,如果定义了才会去执行。增强代码健壮性。

3、CSS放在页头,JavaScript放在页尾

  将CSS放在页头,在载入HTMl元素之前,先载入它们的样式,这样可以避免HTML出现无样式状态;将JavaScript放在页尾,先将网页呈现给用户,再来加载页面内的脚本,避免JavaScript阻塞网页

  的呈现,减少页面空白的时间。

4、引入编译的概念——文件压缩

 1)JavaScript压缩工具有Packer和YUI Compressor;

    Packer的网址是:http://dean.edwards.name/packer/  它的使用非常方便

   YUI Compressor的网址是:http://developer.yahoo.com/yui/compressor/(如果不想安装JDK可以通过http://refresh-sf.com/yui/在线使用YUI Compressor)

   YUI Compressor除了可以压缩javascript代码还可以用来压缩CSS代码

  2) JS压缩通常的做法是去掉空格和换行,去掉注释,将复杂变量名替换成简单的变量名。但影响了代码的可读性,对于代码维护非常不利。所以除了压缩后的文件,我们还需要保留压缩前的原始文件,以备维护之需。

    为了维护方便,源文件的文件名和压缩文件的文件名应建立起对应关系,比如源文件叫做head.js,压缩后的文件可以叫做head-min.js,以此来表明它们之间的关系。

    除了压缩,我们还可以对JavaScript进行反压缩,http://jsbeautifier.org/这个工具就是一个在线反压缩工具,它可以把压缩后的JavaScript文件重新格式化。但需要注意的是,它仅仅是从缩进上对JavaScript进行了反压缩,已经去掉的注释和已经改名的变量是无法恢复的。

  3)JavaScript的分层概念和JavaScript库

  分层可以让我们的代码组织条理更清晰,减少冗余,提高代码重用率。和CSS一样,把JavaScript也分成三层。从上往下依次是base层、common层和page层

   (1)base层

    位于三层的最底端,这层有两个职责:一是封装不同浏览器下JavaScript的差异,提供统一的接口,我们可以依靠它来完成跨浏览器兼容的工作。职责二是扩展JavaScript语言底层的接口,让它提供更多更为易用的接口。base层的功能是给common层和page层提供接口

 base层提供接口,解决浏览器兼容问题:

  (1)如:在使用寻找下一个节点时,Firefox会将包括空白、换行等文本信息也当做childnodes中的一员,而IE会忽略它,只将DOM元素当做是childnodes的一员。如此一来,我们编写的程序就无法兼容IE和Firefox了。

  解决办法:用getNextNode函数封装IE和Firefox的差异。代码如下:(document.all是IE支持的属性,Firefox不支持)

      

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li id="item1">1</li>
<li id="item2">2</li>
<li id="item3">3</li>
</ul>
<script>
function getNextNode(node){
node=typeof node=="string" ? document.getElementById(node) : node;
var nextNode=node.nextSibling;
if(!nextNode)return null;
if(!document.all){
while(true){
if(nextNode.nodeType==1){
break;
}else{
if(nextNode){
nextNode=nextNode.nextSibling;
}else{
break;
}
}
}
}
return nextNode;
}
var nextNode=getNextNode("item1");
alert(nextNode.id);
</script>
</body>
</html>

  通过getNextNode函数我们轻松实现了跨浏览器兼容,这里的getNextNode函数就是我所说的接口。base层的作用之一就是提供大量类似的接口,用来屏蔽原生Javascript在不同浏览器下的差异。

 (2)IE下透明度是通过滤镜实现的,而在Firefox下透明是通过CSS的opacity属性实现的。封装setOpacity函数。

<style>
#text1{
background:blue;
height:100px;
}
#text2{
background:green;
height:100px;
}
</style>
<div id="text1"></div>
<div id="text2"></div>
<script>
function setOpacity(node,level){
node=typeof node=="string" ? document.getElementById(node) : node;
alert(node);
if(document.all){
node.style.filter='alpha(opacity='+level+')';
}else{
node.style.opacity=level/100;
}
}

</script>

 (3)event对象

   不同浏览器中,除了DOM的表现会有所不同,事件的表现也有很大不同。在IE下,event对象是作为window对象的属性作用于全局作用域的,而在Firefox中,event对象作为事件参数存在的。

  event对象的部分属性名在IE和Firefox下也是不同的,比如派生事件的对象在IE下是通过event对象的srcElement属性访问的,而在Firefox下,是通过event对象的target属性访问的

   封装getEventTarget函数

      function getEventTarget(e){

    e=window.event||e;

          return e.srcElement || e.target;

  }

 (4)event对象的冒泡

   当父元素中包含子元素时,为父元素和子元素分别绑定点击事件,当去点击子元素时,结果是子元素的事件先触发,父元素的点击事件随后触发,JavaScript对这种先触发子容器监听事件,后触发父容器事件监听事件的现象称为事件的冒泡。如何解决这个问题:

  我们需要阻止点击按钮时的事件冒泡。阻止事件冒泡在IE下是通过设置event对象的cancelBubble属性为true实现的,而在Firefox下则是通过调用event对象的stopPropagation方法实现的。

  封装阻止事件冒泡的动作:   

function stopPropation(e){
e=window.event||e;
if(documnet.all){
e.cancelBubble=true;
}else{
e.stopPropagation();
}
}

(5)on、attachEvent、addEventListener
如果我们要让某个DOM节点监听事件,最简单的方法就是使用on XXX 方法,如:
var btn=document。getElementById('btn');
btn.onclick=function(){
  alert(1);
}
如果我们需要让click事件再次触发另一个监听函数,
 var btn=document。getElementById('btn');
btn.onclick=function(){
  alert(1);
}
btn.onclick=function(){
  alert(2);
}
我们希望点击按钮时,能将两个监听函数都触发,先弹出1,接着弹出2.但事实上,后面的btn.onclick会把前面的btn.onclick覆盖掉,点击按钮时,只会弹出2
on XXX的监听方式没有叠加效果,最后定义的on XXX会将前面的覆盖掉,所以这种写法很危险,特别是在多人合作时,不仅全局变量会让多人合作中产生意外的冲突,on XXX监听也会产生冲突。
为解决这个问题,可以使用attachEvent和addEvenntListener方法代替on XXX监听事件。其中attachEvent是支持IE的方法,addEventListener是Firefox支持的方法。
attachEvent和addEvenntListener方法支持监听处理函数的叠加,而不是覆盖
 var btn=document。getElementById('btn');
if(document.all){
  btn.attachEvent("onclick",function(){
  alert(1);
});
}else{
  btn.addEventListener("click",function(){
  alert(1);
}
if(document.all){
  btn.attachEvent("onclick",function(){
  alert(2);
});
}else{
  btn.addEventListener("click",function(){
  alert(2);
}
在IE和Firefox下点击按钮都可以顺利弹出1和2.只是可读性太差,来进行一下封装

function on(node,eventType,handler){
node=typeof node=="string"?document.getElementById(node):node;
if(document.all){
node.attachEvent('on'+eventType,handler);
}else{
node.addEventListener(eventType,handler,false);
}
}
var btn=document.getElementById('btn');
on('btn','click',function(){
alert(1);
});
on('btn','click',function(){
alert(2);
});

base层的第二个职责是“扩展JavaScript语言底层的接口”。我们可以自定义一些常用方法来弥补原生JavaScript的缺漏。常用方法:
(1)trim()
在做表单验证的时候,我们常常需要检查输入框是否为空,一个简单的做法是判断inputNode.value=‘’,但如果输入空格、缩进字符等字符,就可以绕过这个判断了。我们可以定义这么一个函数:
function trim(ostr){
  return ostr.replace(/^s+|s+$/g,"");//将开头一个或多个字符或者结尾一个或多个字符替换为空
}

定义类型判断函数:
function isNumber(s){
return !isNaN(s);
}
function isString(s){
return typeof s==='string';
}
function isBoolean(s){
return typeof s==='boolean';
}
function isFunction(s){
return typeof s==='function';
}
function isNull(s){
return typeof s===null;
}
function isUndefined(s){
return typeof s==="undefined";
}
function isEmpty(s){
return /^s*$/.test(s);
}
function isArray(s){
return s instanceof Array;
}

(2)在写JavaScript代码时,最常用的函数就是document.getElementById(),但它实在是太长了,所以很多人干脆用一个简单的函数来代替它,用的比较多的是get()或者$(),还可以再稍微往前一步,参数除了DOM节点外,
还可以直接使用DOM节点本身。
function get(node){
node=typeof node=="string"?document.getElementById(node):node;
}
function $(node){
node=typeof node=="string"?document.getElementById(node):node;
}
alert(get('test1').innerHTML);
alert($('test2').innerHTML);

getElementByClassName()以及extend后续补充

   (2)common层

  位于三层的中间,依赖于base层提供的接口。common层提供可供复用的组件,它是典型的mvc模式中的m,和页面内的具体功能没有直接关系。common层的功能是给page层提供组件。

common层本身依赖base层提供的接口,所以如果要使用common层的组件,必须先加载base层的代码。

common层主要封装组件:如GLOBAL.Cookie,为用户提供更好用的read、set、del方法。对于用户来说,并无需了解GLOBAL.Cookie的内部实现,不用和原生JavaScript中的Cookie相关的API打交道,只需要知道GLOBAL.Cookie提供了那些接口就可以了。

base层提供的接口和任何具体的功能无关,非常底层,所以也非常常用。common层提供的组件并不像base层那么通用,它和具体的功能相关,如果页面里不需要相关的功能,就没必要加载。而且一个易用性、重用性、扩展性都非常好的组件,代码量往往偏高,所以common层的JS需要按功能分成一个个单独的文件,例如:common_cookie.js等,按需加载

common层的组件不仅依赖base层接口,有时也依赖common层的其他组件,在加载时注意导入JS文件的顺序。

  (3)page层

  位于三层的最顶端。这一层和页面里的具体功能需求直接相关,是mvc模式中的c。page层依赖于base层和commom层。page层的功能是完成页面内的功能需求。

原文地址:https://www.cnblogs.com/WEI-web/p/6491790.html