Javascript基础知识篇(7): 高质量开发准则(下)
1.用匿名函数(function(){})();将脚本包裹起来,有效控制全局变量,避免冲突隐患。
2.在解决JS冲突的前提下,如果需要进行多个模块(匿名函数)之间的通信,则需要采用唯一全局变量(系约定名:GLOBAL)+命名空间+属性的方式来解决,同时应该为你维护的模块添加必要的注释,以提高团队合作的效率。对于公共组件,不推荐加前缀(base.js)。对于单独由某成员负责的组件,推荐以成员缩写名添加前缀(如张三:zs_news.js)。
3.CSS放在页头,JS放在页尾。即将非脚本资源文件放在head标签中,而将脚本放到body的尾部。同时,请注意限制外部脚本的数量,在发布正式环境之前,将同一页面所需的多个外部脚本合并到一个脚本(使用YUI Build Tool:http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js)将放到body尾部。但可能合并脚本文件非常大,尽管此时http请求减少了,如果脚本执行很长一段时间还是会产生性能影响,因此建议添加更多的非阻止脚本,即执行脚本尽量添加到页面渲染完成之后。
4.可在JS内联脚本或外部脚本标签中加入defer属性(仅IE和Firefox支持),用于延迟脚本加载。即下载脚本后不会立即执行,而要等到其他脚本加载完毕后以及页面渲染完成后(window.onload之前)才执行。对于不支持defer的浏览器将忽略,仅当做普通的脚本按顺序执行。
5.发布正式前压缩JS文件:Packer(http://dean.edwards.name/packer)和YUI Compressor
(http://developer.yahoo.com/yui/compressor)。后者需安装JDK和配置java环境,下载jar文件并通过命名进行调用。常见命令:java -jar yuicompressor-x.y.z.jar myfile.js -o myfile.min.js。不过也可在线使用YUI Compressor: http://refresh-sf.com/yui。同时,可以使用反压缩工具http://jsbeautifier.org/对已压缩后的文件进行恢复。
6.利用JS分层对应用程序代码组织进行更好的控制:base、common、page。其中base层完成封装不同浏览器下JS的差异,提供统一的接口,位于三层中的最底层。common层依赖于base层,提供可复用的组件,和具体的页面功能没有直接关系,为page层提供组件。page层依赖于base和common层,提供具体的页面功能。
7.利用getElementsByClassName()和getElementsByTagName()都可获取一组具有"相似功能"的DOM节点,但后者适合稳定的HTML结构,而前者使得JS和HTML实现最大程序解耦。
8.要满足一组具有"相似功能"的DOM节点能够复用,则不能使用父容器的ID作为挂钩,而是对这组节点使用相同的类。
9.在同一页面使用多个内容结构相似的组件时,应分别为每个组件指定一个根节点,以保持每个组件之间的独立性。
10.如果一个函数内部某个因素非常不稳定,可将它从函数内部分离出来,以参数形式传入,实现将不稳定因素和函数解耦。
11.可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量。
12.为函数预留回调接口增加可扩展性。
13.通常利用function来定义类,一般函数名为大写(同时代表构造函数)。每定义一个类时,同时将产生一个该类对应的原型(hash对象),也可在原型中定义属性和行为,但构造函数中的属性和行为比原型优先级高,对于同名属性和行为,构造函数中的对应的属性和行为将覆盖原型中定义的同名属性和行为。通过this关键字实现构造函数和原型进行通信。
14.推荐将行为封装到类的函数原型里,属性封装到构造函数中。通过this定义公有属性和行为,通过var定义私有属性和行为(通过作用域实现)。私有属性和行为是不能在原型中被访问。通常情况下定义多实例情况下,构造函数中的属性和行为都会复制一份到每个实例中,而原型中的属性和行为都会在多实例中共享(不会复制)。
15.如要实现公有行为访问到私有成员,最简单的解决办法就是将所有的公有成员定义到构造函数中(与私有成员在同一作用域),但这样做会消耗内存,仅适合于对私有性要求非常高的项目(通常具有强制性要求),但一般情况下不推荐这样做。
16.定义在原型中的行为一定是公有的,如果项目对私有成员并没有强制性要求,推荐以下做法:约定私有成员的定义形式(通常在成员前加_以示区分公有成员),将私有属性定义到构造函数中,私有行为定义到原型中。但注意:既然只是约定,就不能真正实现私有化,只是团队成员调用私有成员属于不合乎规范而已(不能强制避免,只是约定而已)。
17. 极端的OO采用get和set访问实现对私有属性的完全私有化,同样会带来很大的内存消耗,但可以定制私有属性(如只读等)。对于比较简单的应用,直接采用this.**对属性进行读写,而对于较复杂的应用,则采用get和set访问器实现。
18.在接触到19之前,有必要说下JS传值和传址的问题。所谓传值,即复制一份副本传递,而不改变其本身,适合于基本数据类型(如:int,bool,string等);所谓传址,即将当前变量的地址传递给新变量,此时两个变量都引用同一个地址,对新变量的更改也会影响原变量本身,适合于数组、对象等复杂数据类型。那如何实现给复杂数据类型(其中包含基本数据类型)传值呢?首先定义一个新变量,可通过for in循环其中的基本数据类型,来分别将每个基本类型赋值给新变量即可完成。对于数组来说,还可通过自身方法slice或concat来实现。
19.简单的说,要实现类继承,无外乎就是将父类构造函数和原型中的属性和行为复制到子类的过程。但不能通过直接复制或调用父类构造函数就能完成,必须通过call或apply调整当前对象指向(window->this)来完成调用。对于原型继承,也不可直接将父类的原型直接拷贝到子类,我们都知道原型实际上可看成一个hash对象,在JS中传值方式分为传值和传址两种方式,对于hash对象来说当然是传址(即子类原型和父类原型指向同一个对象),这时要是在子类中添加了新的行为(实际上正是因为有了继承,子类肯定会添加新的行为),同时也将改变父类的原型,当然就违背继承的初衷了。我们可以让父类原型以传值的方式给子类(即只给一个副本给子类,子类原型改变而不影响父类自身),通常有两种方式可以实现:定义子类新原型,通过for in循环父类原型,将其中的值(其实都已经是基本类型)逐个拷贝到子类新原型中。其次利用构造函数特性,new一个父类对象给子类原型,同时调整子类原型构造函数为子类本身,就可简单实现父类原型中的成员复制到子类。
20.JS在获取HTML属性时,在各浏览器存在一定差异,对于HTML节点node常规属性时
(如<a id="blog" href="http://www.cnblogs.com/hmiinyu/" class="blog" blogType="js">Miracle's Blog</a>),使用node.**比node.getAttribute("**")时更具兼容性,如a.innerHTML="Miracle's Blog"(IE,FireFox),而a.getAttribute("innerHTML")="Miracle's Blog"(IE),null(FireFox),另外由于class是关键字,因此获取class属性时应使用className;但对于自定义属性时,使用node.getAttribute("**")更具兼容性,如a.getAttribute("blogType")="js"(IE,FireFox), 因此总结:获取常规属性,统一使用node.**;获取自定义属性,同一使用node.getAttribute("**")。
21.刚才20提到自定义属性可以保存客户端需要的字符串,如果我们稍加技巧,还能利用它来保存数组、hash对象等复杂数据类型。秘诀在于:虽然自定义属性看起来只能保存字符串,但如果我们把字符串设计成如下这样:<a id="blog" href="http://www.cnblogs.com/hmiinyu/" class="blog" blogInfo="{name: 'Miracle',type: '前端开发'}">Miracle's Blog</a>,我们发现自定义属性blogInfo:{name: 'Miracle',type='前端开发'}跟hash对象(或json)长得很像,利用eval函数来反序列化将字符串转化hash对象,具体做法:var blog = a.getAttribute("blogInfo"), var b = eval("("+blog+")"),然后就可分别访问b.name, b.type了,是不是很巧妙呢。
22.JS事件机制:对IE来说,event对象是作为window的全局属性存在;对FireFox来说,event对象作为function的事件参数传入,因此我们在事件处理函数中返回arguments.length,IE返回0,FF返回1,常见的兼容事件对象这样写:btn.onlick = function(e) { e = window.e || e;***};。但对于内联事件(即在事件属性中赋值,如:<input id="btn" type="button" value="click me" onclick="handler();">, function handler(){alert(arguments.length)})来说,IE和FF都返回0,也即此时内联事件处理函数并未被替换为btn.onclick = handler,而是btn.onclick = function() { handler() };但如果内联事件和外部事件同时存在时,外部事件将覆盖内联事件,除非通过attachEvent(IE)或addEventListener(FF)来附加内联事件。另外,值得一提的是,在内联事件中也可写注释和代码(极力不推荐这么做,除非只有极少的代码时,如***;return false;等)。
23.JS数据有四种存放方式:字面量、本地变量、数据和对象。相对于数据和对象而言,字面量和变量的存取速度非常快速,可以忽略。以访问20万个数据值在各大浏览器中进行比较:推荐尽量多使用字面量和本地变量,以及限制数组和对象的大量使用,以提高代码执行的速度。
这里有一段代码的两个版本来说明本地变量的性能:第一个版本三次使用document(存在于执行上下文作用域链的global objects中,根据标识符解析规则,一般都是先查找activation objects,如果查找不到再到global objects中最后查找,这里要进行三次查找document);第二个版本首先将document存放于本地变量中,只进行一次查找,细微的改动(可能真实的应用程序会有多次查找)就显著提升了应用程序的性能。因此,将频繁使用的(特别是作用域之外的)变量首先存放在本地变量中,然后再使用本地变量。
function initUI() { var bd = document.body, links = document.getElementsByTagName("a"), i = 0, len = links.length; while(i < len){ update(links[i++]); } document.getElementById("go-btn").onclick = function() { start(); }; bd.className = "active"; }
function initUI() { var doc = document, bd = doc.body, links = doc.getElementsByTagName("a"), i= 0, len = links.length; while(i < len){ update(links[i++]); } doc.getElementById("go-btn").onclick = function() { start(); }; bd.className = "active"; }
可能有人提出with也可解决这个问题,首先来解释一下with的用法,with将创建一个在作用域链上的临时对象区(提供该对象的公共访问点),并将对临时对象区移动到作用域链的最前端,这是本地变量的对象区将相应的下移,导致本地变量的查找出现二次查找,影响了程序性能,此时作用域链发生变化,也即动态作用域链。因此,一般情况下无特殊要求不建议使用with来解决公用全局对象的作用域链查找性能问题。
function initUI() { with (document){ //avoid! var bd = body, links = getElementsByTagName("a"), i= 0, len = links.length; while(i < len){ update(links[i++]); } getElementById("go-btn").onclick = function() { start(); }; bd.className = "active"; } }
24. 基于23提出的with性能问题,其实JS中还有很多类似的特性。如try-catch中catch块,当在try块中遇到异常时会马上跳转至catch块中,这时函数执行上下文作用域链会新增一个catch异常对象区,且提升到作用域链的最前端,以供异常对象的查找。但异常处理在JS中是非常有意义的,因此不能像with那样不使用就可以了。推荐做法:一般不使用try-catch来解决JS本身的错误(语法错误等),将catch块中内容封装成单一函数,以减少在catch块中本地变量的二次查找性能开销。
try { methodThatMightCauseAnError(); } catch (ex) { handleError(ex); //delegate to handler method }
25. 尽量减少document重绘过程(repaint&reflow)而带来的布局变动(如样式变动),可采用打包(batches)这些变动一次性提交(即仅完成一次重绘)。主要有以下三种方式:1.先隐藏元素,提交变动,再打开元素;2. 将变动临时存储到文档片段中,然后将文档片段插入到对应元素;3. 复制需要插入位置的原始元素,然后将变动提交到该复制元素,再次将复制元素替换原始元素。用一个示例来说明这些方式,假设已经有如下片段,需要将已有数据插入到该片段中。
<ul id="websites">
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.sina.com">新浪</a></li>
</ul>
var data = [ { "name": "搜狐", "url": "http://www.sohu.com" }, { "name": "腾讯", "url": "http://www.qq.com" } ];
首先定义公共函数appendDataToElement。
function appendDataToElement(appendToElement, data) { var a, li; for (var i = 0, max = data.length; i < max; i++) { a = document.createElement('a'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li = document.createElement('li'); li.appendChild(a); appendToElement.appendChild(li); } };
1.先隐藏元素,提交变动,再打开元素。
var ul = document.getElementById('websites'); ul.style.display = 'none'; appendDataToElement(ul, data); ul.style.display = 'block';
2.将变动临时存储到文档片段中,然后将文档片段插入到对应元素(推荐)。
var fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); document.getElementById('websites').appendChild(fragment);
3.复制需要插入位置的原始元素,然后将变动提交到该复制元素,再次将复制元素替换原始元素。
var old = document.getElementById('websites'); var clone = old.cloneNode(true); appendDataToElement(clone, data); old.parentNode.replaceChild(clone, old);
26. JS包含四种循环体结构(while, do...while, for, for...in),其中for...in是用来遍历对象的命名属性列表,返回对象的实例属性和从原型继承而来的属性。但相对于其他三种循环来说,由于每次循环都需要迭代搜索实例属性或原型属性,因此性能损耗很大(几乎要慢7倍之多)。因此除非需要对数目不详的对象属性操作,否则避免使用for...in循环,反之可采用其他三种循环来代替。
我这里用实例来说明以上的准则(完整版,):
<html> <head> <style type="text/css"> ul {padding:0;margin:0;} .tab {width:400px;} .tab .tab-currentMenu {background-color:#333;color:#fff;} .tab .tab-currentMenu2 {background-color:blue;color:#fff;} .underline {text-decoration:underline;} .tab-menu {padding-left:20px;} .tab-menu li {float:left;display:inline;padding:5px;border:1px solid #333;border-bottom:none;margin-right:5px;} .tab-content {border:1px solid #333;clear:left;padding:5px;} </style> </head> <body> <div class="tab J_tab"> <ul class="tab-menu"> <li class="J_tab-menu">menu1-1</li> <li class="J_tab-menu">menu1-2</li> <li class="J_tab-menu underline">menu1-3</li> </ul> <div class="tab-content"> <div class="J_tab-content"><div>content1-1</div><ul><li>abc</li></ul></div> <div class="J_tab-content" style="display:none;"><p>content1-2</p><div>abc</div></div> <div class="J_tab-content" style="display:none;">content1-3</div> </div> </div> <div class="tab J_tab"> <ul class="tab-menu"> <li class="J_tab-menu tab-currentMenu">menu2-1</li> <li class="J_tab-menu">menu2-2</li> </ul> <div class="tab-content"> <div class="J_tab-content"><div>content2-1</div><ul><li>abc</li></ul></div> <div class="J_tab-content" style="display:none;"><p>content2-2</p><div>abc</div></div> </div> </div> <div class="tab J_tab"> <ul class="tab-menu"> <li class="J_tab-menu tab-currentMenu2">menu3-1</li> <li class="J_tab-menu">menu3-2</li> <li class="J_tab-menu">menu3-3</li> <li class="J_tab-menu">menu3-4</li> <li class="J_tab-menu">menu3-5</li> </ul> <div class="tab-content"> <div class="J_tab-content"><div>content3-1</div><ul><li>abc</li></ul></div> <div class="J_tab-content" style="display:none;"><p>content3-2</p><div>abc</div></div> <div class="J_tab-content" style="display:none;">content3-3</div> <div class="J_tab-content" style="display:none;"><p>content3-4</p><div>abc</div></div> <div class="J_tab-content" style="display:none;">content3-5</div> </div> </div> <script type="text/javascript"> var GLOBAL = {}; GLOBAL.namespace = function (str) { var arr = str.split("."), o = GLOBAL; for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) { o[arr[i]] = o[arr[i]] || {}; } o = o[arr[i]]; }; GLOBAL.namespace("dom"); GLOBAL.dom.getElementsByClassName = function(str, parent, tag) { if(parent) { parent = typeof parent == "string" ? document.getElementById(parent) : parent; } else { parent = document.body; } tag = tag || "*"; var elems = parent.getElementsByTagName(tag); var array = []; for(var i = 0, len = elems.length; i < len; i++) { for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) { if(k[j]==str) { array.push(elems[i]); break; } } } return array; } GLOBAL.dom.addClass = function(node, str) { if (!new RegExp("(^|\\s+)" + str).test(node.className)) { node.className = node.className + " " + str; } } GLOBAL.dom.removeClass = function(node, str) { node.className = node.className.replace(new RegExp("(^|\\s+)" + str), ""); } GLOBAL.namespace("event"); GLOBAL.event.on = function(node, eventType, handler, scope) { node = typeof node == "string" ? document.getElementById(node) : node; scope = scope || node; //可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量 if(document.all) { //node.attachEvent("on" + eventType, handler); node.attachEvent("on" + eventType, function() { handler.apply(scope, arguments); }); } else { //node.addEventListener(eventType, handler, false); node.addEventListener(eventType, function() { handler.apply(scope, arguments); }, false); } } function setTab(config) { var parent = config.parent; var currentClass = config.currentClass; var trigger = config.trigger || "click"; var handler = config.handler; var autoPlay = config.autoPlay; var playTime = config.playTime || 3000; var menus = GLOBAL.dom.getElementsByClassName("J_tab-menu", parent); var contents = GLOBAL.dom.getElementsByClassName("J_tab-content", parent); var currentIndex = 0; function showItem(n) { for(var i = 0, len = contents.length; i < len; i++) { contents[i].style.display = "none"; } contents[n].style.display = "block"; if(currentClass) { var currentMenu = GLOBAL.dom.getElementsByClassName(currentClass, parent)[0]; if (currentMenu) { GLOBAL.dom.removeClass(currentMenu, currentClass); } GLOBAL.dom.addClass(menus[n], currentClass); } //预留回调接口 if(handler) { handler(n); } } function autoHandler() { currentIndex++; if(currentIndex >= menus.length) { currentIndex = 0; } showItem(currentIndex); } if(autoPlay) { setInterval(autoHandler, playTime); } for (var i = 0, mLen = menus.length; i < mLen; i++) { menus[i]._index = i; GLOBAL.event.on(menus[i], trigger, function() { showItem(this._index); currentIndex = this._index; }); } } var tabs = GLOBAL.dom.getElementsByClassName("J_tab"); setTab({ parent: tabs[0], trigger: "mouseover" }); setTab({ parent: tabs[1], currentClass: "tab-currentMenu", autoPlay: true, playTime: 4000 }); setTab({ parent: tabs[2], currentClass: "tab-currentMenu2", trigger: "mouseover", handler: function (index) { alert("你激活的是第" + (index + 1) + "个标签"); } }); </script> </body> </html>
<html> <head> <style type="text/css"> ul {padding:0;margin:0;} .tab {width:400px;} .tab .tab-currentMenu {background-color:#333;color:#fff;} .tab .tab-currentMenu2 {background-color:blue;color:#fff;} .underline {text-decoration:underline;} .tab-menu {padding-left:20px;} .tab-menu li {float:left;display:inline;padding:5px;border:1px solid #333;border-bottom:none;margin-right:5px;} .tab-content {border:1px solid #333;clear:left;padding:5px;} </style> </head> <body> <div class="tab J_tab"> <ul class="tab-menu"> <li class="J_tab-menu">menu1-1</li> <li class="J_tab-menu">menu1-2</li> <li class="J_tab-menu underline">menu1-3</li> </ul> <div class="tab-content"> <div class="J_tab-content"><div>content1-1</div><ul><li>abc</li></ul></div> <div class="J_tab-content" style="display:none;"><p>content1-2</p><div>abc</div></div> <div class="J_tab-content" style="display:none;">content1-3</div> </div> </div> <div class="tab J_tab"> <ul class="tab-menu"> <li class="J_tab-menu tab-currentMenu">menu2-1</li> <li class="J_tab-menu">menu2-2</li> </ul> <div class="tab-content"> <div class="J_tab-content"><div>content2-1</div><ul><li>abc</li></ul></div> <div class="J_tab-content" style="display:none;"><p>content2-2</p><div>abc</div></div> </div> </div> <div class="tab J_tab"> <ul class="tab-menu"> <li class="J_tab-menu tab-currentMenu2">menu3-1</li> <li class="J_tab-menu">menu3-2</li> <li class="J_tab-menu">menu3-3</li> <li class="J_tab-menu">menu3-4</li> <li class="J_tab-menu">menu3-5</li> </ul> <div class="tab-content"> <div class="J_tab-content"><div>content3-1</div><ul><li>abc</li></ul></div> <div class="J_tab-content" style="display:none;"><p>content3-2</p><div>abc</div></div> <div class="J_tab-content" style="display:none;">content3-3</div> <div class="J_tab-content" style="display:none;"><p>content3-4</p><div>abc</div></div> <div class="J_tab-content" style="display:none;">content3-5</div> </div> </div> <script type="text/javascript"> var GLOBAL = {}; GLOBAL.namespace = function (str) { var arr = str.split("."), o = GLOBAL; for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) { o[arr[i]] = o[arr[i]] || {}; } o = o[arr[i]]; }; GLOBAL.namespace("dom"); GLOBAL.dom.getElementsByClassName = function(str, parent, tag) { if(parent) { parent = typeof parent == "string" ? document.getElementById(parent) : parent; } else { parent = document.body; } tag = tag || "*"; var elems = parent.getElementsByTagName(tag); var array = []; for(var i = 0, len = elems.length; i < len; i++) { for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) { if(k[j]==str) { array.push(elems[i]); break; } } } return array; } GLOBAL.dom.addClass = function(node, str) { if (!new RegExp("(^|\\s+)" + str).test(node.className)) { node.className = node.className + " " + str; } } GLOBAL.dom.removeClass = function(node, str) { node.className = node.className.replace(new RegExp("(^|\\s+)" + str), ""); } GLOBAL.namespace("event"); GLOBAL.event.on = function(node, eventType, handler, scope) { node = typeof node == "string" ? document.getElementById(node) : node; scope = scope || node; //可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量 if(document.all) { //node.attachEvent("on" + eventType, handler); node.attachEvent("on" + eventType, function() { handler.apply(scope, arguments); }); } else { //node.addEventListener(eventType, handler, false); node.addEventListener(eventType, function() { handler.apply(scope, arguments); }, false); } } //面向对象 function Tab(config) { this._parent = config.parent; this._currentClass = config.currentClass; var trigger = config.trigger || "click"; this._handler = config.handler; var autoPlay = config.autoPlay; var playTime = config.playTime || 3000; this._menus = GLOBAL.dom.getElementsByClassName("J_tab-menu", this._parent); this._contents = GLOBAL.dom.getElementsByClassName("J_tab-content", this._parent); this.currentIndex = 0; var $this = this; if(autoPlay) { setInterval(function() { $this._autoHandler(); }, playTime); } for (var i = 0, mLen = this._menus.length; i < mLen; i++) { this._menus[i]._index = i; GLOBAL.event.on(this._menus[i], trigger, function() { $this.showItem(this._index); this.currentIndex = this._index; }); } } Tab.prototype = { showItem: function(n) { for(var i = 0, len = this._contents.length; i < len; i++) { this._contents[i].style.display = "none"; } this._contents[n].style.display = "block"; if(this._currentClass) { var currentMenu = GLOBAL.dom.getElementsByClassName(this._currentClass, this._parent)[0]; if (currentMenu) { GLOBAL.dom.removeClass(currentMenu, this._currentClass); } GLOBAL.dom.addClass(this._menus[n], this._currentClass); } //预留回调接口 if(this._handler) { this._handler(n); } }, _autoHandler: function() { this.currentIndex++; if(this.currentIndex >= this._menus.length) { this.currentIndex = 0; } this.showItem(this.currentIndex); } }; var tabs = GLOBAL.dom.getElementsByClassName("J_tab"); new Tab({ parent: tabs[0], trigger: "mouseover" }); new Tab({ parent: tabs[1], currentClass: "tab-currentMenu", autoPlay: true, playTime: 4000 }); new Tab({ parent: tabs[2], currentClass: "tab-currentMenu2", trigger: "mouseover", handler: function (index) { alert("你激活的是第" + (index + 1) + "个标签"); } }); </script> </body> </html>
<html> <head> </head> <body> <h1>我的饭店</h1> <p>卫生:<p> <p class="J_rate"> <img src="star_gray.png" title="很烂" /> <img src="star_gray.png" title="一般" /> <img src="star_gray.png" title="还好" /> <img src="star_gray.png" title="较好" /> <img src="star_gray.png" title="很好" /> </p> <p>价格:<p> <p class="J_rate"> <img src="star_gray.png" title="很烂" /> <img src="star_gray.png" title="一般" /> <img src="star_gray.png" title="还好" /> <img src="star_gray.png" title="较好" /> <img src="star_gray.png" title="很好" /> </p> <p>味道:<p> <p class="J_rate"> <img src="star_gray.png" title="很烂" /> <img src="star_gray.png" title="一般" /> <img src="star_gray.png" title="还好" /> <img src="star_gray.png" title="较好" /> <img src="star_gray.png" title="很好" /> </p> <script type="text/javascript"> var GLOBAL = {}; GLOBAL.namespace = function (str) { var arr = str.split("."), o = GLOBAL; for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) { o[arr[i]] = o[arr[i]] || {}; } o = o[arr[i]]; }; GLOBAL.namespace("dom"); GLOBAL.dom.getElementsByClassName = function(str, parent, tag) { if(parent) { parent = typeof parent == "string" ? document.getElementById(parent) : parent; } else { parent = document.body; } tag = tag || "*"; var elems = parent.getElementsByTagName(tag); var array = []; for(var i = 0, len = elems.length; i < len; i++) { for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) { if(k[j]==str) { array.push(elems[i]); break; } } } return array; } GLOBAL.namespace("event"); GLOBAL.event.on = function(node, eventType, handler, scope) { node = typeof node == "string" ? document.getElementById(node) : node; scope = scope || node; //可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量 if(document.all) { //node.attachEvent("on" + eventType, handler); node.attachEvent("on" + eventType, function() { handler.apply(scope, arguments); }); } else { //node.addEventListener(eventType, handler, false); node.addEventListener(eventType, function() { handler.apply(scope, arguments); }, false); } } //Rating function Rating(parent) { var rate = typeof parent == "string" ? document.getElementById(parent) : parent; var items = rate.getElementsByTagName("img"); var imgs = ["star_gray.png", "star_gold.png"]; var flag; for(var i = 0, len = items.length; i < len; i++) { items[i]._index = i; GLOBAL.event.on(items[i], "mouseover", function(e) { if(flag) return; var target = e.target || e.srcElement; if(target.tagName.toLowerCase() != "img") return; for(var j = 0; j < len; j++) { if(j <= this._index) { items[j].src = imgs[1]; } else { items[j].src = imgs[0]; } } }); GLOBAL.event.on(items[i], "mouseout", function() { if(flag) return; for(var j = 0; j < len; j++) { items[j].src = imgs[0]; } }); GLOBAL.event.on(items[i], "click", function() { if(flag) return; flag = true; alert("你的分数为:" + (this._index + 1) +"," + this.title); }); } } var rates = GLOBAL.dom.getElementsByClassName("J_rate"); for(var i = 0, len = rates.length; i < len; i++) { new Rating(rates[i]); } </script> </body> </html>
可以参照完整版代码去查看整个重构的完整过程。