ajax标签导航实例详解
之前整理发表了《[url=http://www.]XMLHTTPRequest的属性和方法简介[/url]》,它是ajax要使用的核心的技术之一,现在就来实际运用它。这个Ajax标签导航,是我很久前就写的一个脚本,很实用的(还被很多网站收录了哦),现在拿它来做实例讲解吧!当然个人能力有限,有什么不对的地方还请多包含!演示地址:[url=http://www.]http://www.[/url]
效果大家看到了,核心功能有:
1.将当前选中标签以特殊的样式显示
2.将异步加载的页面信息显示到指定的DOM节点中
我们来看看处理脚本的代码吧:
ajaxtab.js
程序代码:
<!-- // 判断是否支持ActiveX var useActiveX=function(){return (typeof ActiveXObject != "undefined");} // 判断是否支持DOM var useDom=function(){return document.implementation && document.implementation.createDocument;} // 判断是否支持XMLHttpRequest对象 var useXmlHttp=function(){return (typeof XMLHttpRequest != "undefined");} // XMLHttpRequest对象版本 var ARR_XMLHTTP_VERS = ["MSXML2.XmlHttp.6.0","MSXML2.XmlHttp.3.0"]; // DOM对象版本 var ARR_DOM_VERS = ["MSXML2.DOMDocument.6.0","MSXML2.DOMDocument.3.0"]; /* =========================================================== * 函数名称:$(i) * 参数说明:i - 目标节点名称 * 函数功能:获取指定的目标DOM节点 * 返 回 值:返回要搜索的目标DOM节点 * 使用方法:$("frmSearch") ============================================================ */ function $(i){ if(!document.getElementById)return false; if(typeof i==="string"){ if(document.getElementById && document.getElementById(i)) { // W3C DOM return document.getElementById(i); } else if (document.all && document.all(i)) { // MSIE 4 DOM return document.all(i); } else if (document.layers && document.layers[i]) { // NN 4 DOM.. note: this won't find nested layers return document.layers[i]; } else { return false; } } else{return i;} } /* =========================================================== * 函数名称:createXMLHTTPRequest() * 参数说明:无参数 * 函数功能:创建XMLHttpRequest对象 * 返 回 值:XMLHTTPRequest对象 * 使用方法:var oXmlHttp = createXMLHTTPRequest(); ============================================================ */ function createXMLHTTPRequest(){ // 非IE浏览器(Firefox,Opera),XMLHttpRequest对象是浏览器内置的一个对象 if (useXmlHttp){ return new XMLHttpRequest(); } else if (useActiveX) { //在IE(IE< 7.0 = use ActiveX)浏览器中,XMLHttpRequest对象是以ActiveX控件的形式存在的 if (!XMLHTTP_VER) { for (var i=0; i < ARR_XMLHTTP_VERS.length; i++){ try { new ActiveXObject(ARR_XMLHTTP_VERS[i]); XMLHTTP_VER = ARR_XMLHTTP_VERS[i]; // 获取本地IE浏览器相应的XMLHttpRequest对象版本 break; } catch (oError) {} } } if (XMLHTTP_VER) { return new ActiveXObject(XMLHTTP_VER); } else { throw new Error("无法创建XMLHttpRequest对象!"); } } else { throw new Error("您的浏览器不支持XMLHttpRequest对象!"); } } /* =========================================================== * 函数名称:ajaxUpdater(tarObj,sMethod,URL,parameters) * 参数说明:tarObj - 异步获取信息希望显示的目标节点ID * sMethod - 数据提交方法,两个可选值get,post * URL - 提交的目标URL地址 * parameters - URL后面接(传递)的参数 * 函数功能:将异步传递的目标URL地址返回的信息,无刷新的写到目标 * 节点(tarObj)中 * 返 回 值:new Error() - 运行错误时返回一个报错信息 * 使用方法:var myAjax = ajaxUpdater(msgBox,"get",URL,para); ============================================================ */ function ajaxUpdater(tarObj,sMethod,URL,parameters){ var oXmlHttp = createXMLHTTPRequest(); oXmlHttp.open(sMethod, URL+parameters, true); oXmlHttp.onreadystatechange = function () { if (oXmlHttp.readyState == 4) { if (oXmlHttp.status == 200) { if($(tarObj)){ $(tarObj).innerHTML = oXmlHttp.responseText; } else{ return false; } } else { throw new Error("有一个错误产生!"); } } } oXmlHttp.send(null); } /* =========================================================== * 函数名称:ajaxRequest(sMethod,URL,parameters,func) * 参数说明:sMethod - 数据提交方法,两个可选值get,post * URL - 提交的目标URL地址 * parameters - URL后面接(传递)的参数 * func - 页面成功加载后的处理函数(指针) * 函数功能:当异步传递的目标URL地址成功加载时,指定相应的处理函数 * 返 回 值:func(oXmlHttp) - 返回处理函数 * new Error() - 运行错误时返回一个报错信息 * 使用方法:var myAjax = ajaxUpdater("get",URL,para,showMsg); ============================================================ */ function ajaxRequest(sMethod,URL,parameters,func){ var oXmlHttp = createXMLHTTPRequest(); oXmlHttp.open(sMethod, URL+parameters, true); oXmlHttp.onreadystatechange = function() { if (oXmlHttp.readyState == 4) { if (oXmlHttp.status == 200) { return func(oXmlHttp); } else { throw new Error("有一个错误产生!"); } } } oXmlHttp.send(null); } /* =========================================================== * 函数名称:tabsEvent() * 参数说明:要设置事件的DOM节点ID * 函数功能:为导航TAB菜单(li)设置onclick处理方法(函数), * 屏蔽掉a标签默认的处理(打开新链接)事件 * 返 回 值:false - 屏蔽掉a标签默认的处理(打开新链接)事件 * 使用方法:tabsEvent("news","sports"); ============================================================ */ function tabsEvent(){ for(var i=0;i<arguments.length;i++){ var tabs = $(arguments[i]); // DOM节点(tabs)不存在或者浏览器不支持getElementsByTagName()方法 // 函数不执行 if(!tabs || !document.getElementsByTagName) return false; var theList = tabs.getElementsByTagName("li"); // 搜寻导航标签(ID为tabs)里的所有li标签 var theLink = tabs.getElementsByTagName("a"); // 搜寻导航标签(ID为tabs)里的所有a标签 for(var j=0;j<theList.length;j++){ var theTab = theList[j]; if(theTab.parentNode!=tabs) continue; var theA = theLink[j]; // 屏蔽掉a标签默认的处理(打开新链接)事件 theA.onclick = function(){ return false; } // 为导航TAB菜单(li)设置onclick处理方法(函数) theTab.onclick = function(){ var theClass = this.className; if(theClass!="current" && theClass!="first"){ var objId = this.getAttribute("id").split("-")[1]; // 当前选中标签(li)在菜单(ul)中的索引值 var tarObj = this.getAttribute("id").split("-")[0]; // 要显示信息的目标DOM节点ID值 var theURL = tarObj + "/" + tarObj + objId + ".htm"; // 要异步加载的URL地址 ajaxInject($(tarObj),objId,tarObj,theURL); return false; } } } } } /* =========================================================== * 函数名称:ajaxInject(ListName,tabId,tarObj,URL) * 参数说明:ListName - 标签菜单DOM节点ID * tabId - 选中的标签(在ListName中的)索引值 * tarObj - 要显示返回信息的目标DOM节点ID值 * URL - 要异步处理的URL地址 * 函数功能:设置当前选中标签(li)的样式, * 将返回信息写到指定DOM节点中。 * 返 回 值:无 * 使用方法:tabsEvent("news","sports"); ============================================================ */ function ajaxInject(ListName,tabId,tarObj,URL){ if(!ListName || !document.getElementsByTagName) return false; var Tabs = ListName; var theLi = Tabs.getElementsByTagName("li"); for(var i=0;i<theLi.length;i++){ // 设置当前选中标签的样式 if(i==tabId){ if(i==0){ theLi[tabId].className = "first"; // 当选中第一项的样式 } else{// theLi[tabId].className = "current"; // 选中其他项的样式 } var msgBox = tarObj+"Cnt"; var loadstatustext="<div class='loading'><img src='img/loading.gif' alt='正在加载内容, 请稍候...' />正在加载内容, 请稍候...</div>"; $(msgBox).innerHTML = loadstatustext; // 加载信息时的提示信息 var para = "?d=" + Math.random(); // URL后的参数,接Math.random()(一个随机数),目的是处理ajax的缓存问题 var myAjax = ajaxUpdater(msgBox,"get",URL,para); } else{// 设置其他标签的样式 theLi[i].className = ""; if(tabId!=0){ theLi[tabId-1].className = "off"; // 当不是第一项时,隐藏选中项的前一项的分隔标签 } } } } //-->
inde.htm
程序代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www. xmlns="http://www. http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>AjaxTab导航</title> <link href="css/ajaxtab.css" rel="stylesheet" type="text/css" /> <script language="javascript" type="text/javascript" src="js/ajaxtab.js"></script> </head> <body> <div class="clearfix cotainer"> <ul class="tabs" id="news"> <li class="first" id="news-0"><a href="news/news0.htm">网站重构</a><span></span></li> <li id="news-1"><a href="news/news1.htm">CSS布局实录</a><span></span></li> <li id="news-2"><a href="news/news2.htm">海啸的地盘</a><span></span></li> <li id="news-3"><a href="news/news3.htm">Ajax高级编程</a><span></span></li> </ul><br class="clear" /> <div class="clearfix cnt" id="newsCnt"> <img src="img/girl-1.jpg" alt="林志琳" /> <ul> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> </ul> </div> </div> <div class="clearfix cotainer"> <ul class="tabs" id="sports"> <li class="first" id="sports-0"><a href="sports/sports0.htm">网站重构</a><span></span></li> <li id="sports-1"><a href="sports/sports1.htm">CSS布局实录</a><span></span></li> <li id="sports-2"><a href="sports/sports2.htm">海啸的地盘</a><span></span></li> <li id="sports-3"><a href="sports/sports3.htm">Ajax高级编程</a><span></span></li> </ul><br class="clear" /> <div class="clearfix cnt" id="sportsCnt"> <img src="img/girl-5.jpg" alt="林志琳" /> <ul> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> </ul> </div> </div> <script language="javascript" type="text/javascript"> <!-- tabsEvent("news","sports"); //--> </script> </body> </html>
ajaxtab.css
程序代码:
<!-- *{ margin:0; padding:0; } body{ text-align:center; background-color:#FFF; color:#18397C; font:normal 12px "宋体", Arial, sans-serif; } img{border:0;} ul,li{list-style-type:none;} a:link, a:visited{ color:#18397C; text-decoration:none; } a:hover{ color:#F00; text-decoration:underline; } div,span,p,li,ul,h1,h2,h3,h4,h5,h6{text-align:left;} /*clear both*/ .clearfix:after { content: "."; display:block; height:0; clear: both; visibility: hidden; } .clearfix {display: inline-block;} /* Hides from IE-mac \*/ * html .clearfix {height: 1%;} *+html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */ .clear{ clear: both; font-size:1px; width:1px; height:1px; visibility: hidden; } .cotainer{ margin:0 auto; margin-top:10px; width:506px; height:auto; border:solid #B0BEC7; border-width:0 1px 1px 1px; } .tabs{ float:left; width:506px; height:22px; background-image:url(../img/tab_bg.gif); } .tabs li{ float:left; display:inline; text-align:center; width:120px; height:12px; padding:4px 0 6px 0; overflow:hidden; letter-spacing:1px; position:relative; } .tabs li.first{ background-image:url(../img/tab_active1.gif); } .tabs li.current{ background-image:url(../img/tab_active2.gif); } .tabs li.current, .tabs li.first{ font-weight:bold; } .tabs li.current a, .tabs li.first a{ color:#D45417; } .tabs li span{ position:absolute; right:0; top:3px; width:2px; height:16px; overflow:hidden; font-size:1px; background-image:url(../img/tab_sline.gif); } .tabs li.first span, .tabs li.current span, .tabs li.off span{ display:none; } .cnt{ margin:0 auto; width:496px; padding:5px; height:auto; } .cnt img{ float:left; width:154px; height:115px; border:1px solid #B0BEC7; margin-right:5px; display:inline; } .cnt ul{ float:right; width:335px; height:117px; } .cnt ul li{ float:left; width:335px; height:12px; overflow:hidden; color:#999; padding:5px 0 2px 0; } .loading{ margin:0 auto; width:506px; height:16px; padding:51px 0 50px 0; overflow:hidden; text-align:center; } .loading img{ width:16px; height:16px; border:0; float:none; vertical-align:middle; } -->
CSS技巧篇(position属性的运用技巧)
下面开始我们的分析了,先来看看这段XHTML代码:
程序代码:
<ul class="tabs" id="news"> <li class="first" id="news-0"><a href="news/news0.htm">网站重构</a><span></span></li> <li id="news-1"><a href="news/news1.htm">CSS布局实录</a><span></span></li> <li id="news-2"><a href="news/news2.htm">海啸的地盘</a><span></span></li> <li id="news-3"><a href="news/news3.htm">Ajax高级编程</a><span></span></li> </ul> <div class="clearfix cnt" id="newsCnt"> <img src="img/girl-1.jpg" alt="林志琳" /> <ul> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> <li><a href="#">PRG全棉短袖衬衣最后的抢购机抢购机</a> 2006-08-15</li> </ul> </div>
id="news" - news就是我们的导航标签的ID;
id="newsCnt" - newsCnt就是我们要写入信息的目标DOM节点;
class="first" - first当前(第一个)标签的样式;
id="news-0" - news-0 通过”-“分开,我们就分别可以得到news(导航标签ID),0(标签[li]在导航标签中的索引值)
<a href="news/news0.htm">网站重构</a> - 超链接
<span></span> - 标签间的分割线
我罗列的这些东西,相信大家开始看出了些头绪了,呵呵,不过别急!在我们看处理的脚本之前,先让我们来看看导航标签的样式,主要是看看我们对分割线的处理(一点CSS处理的技巧)。
程序代码:
/* 导航标签的样式 */ .tabs li{ float:left; display:inline; text-align:center; width:120px; height:12px; padding:4px 0 6px 0; overflow:hidden; letter-spacing:1px; position:relative; } /* 标签分割线的样式 */ .tabs li span{ position:absolute; right:0; top:3px; width:2px; height:16px; overflow:hidden; font-size:1px; background-image:url(../img/tab_sline.gif); }
技巧就是.tab li中的”position:relative;“和.tabs li span中”position:absolute;“结合使用的技巧(呵呵,其实我也是看了YAHOO的标签后才这么用的)。现在我们就来了解下position属性吧(CSS讲座开始,不过是好东西哦,呵呵!):
position:绝对定位,指本体对上级的定位(本人理解的说法),有3个可选值static(静态定位-默认值)、relative(绝对相对定位)、absolute(绝对定位)。
relative:他是参照父级的原始点为原始点,无父级则以BODY的原始点为原始点,配合top、right、bottom、left进行定位,当父级内有padding等CSS属性时,当前级的原始点则参照父级内容区的原始点进行定位。如下图一:
" border="0" />
absolute:他的意思是绝对定位,他是参照浏览器的左上角,配合TOP、RIGHT、BOTTOM、LEFT(下面简称TRBL)进行定位,在没有设定TRBL,默认依据父级的做标原始点为原始点。如果设定TRBL并且父级没有设定position属性,那么当前的absolute则以浏览器左上角为原始点进行定位,位置将由TRBL决定。如下图二:
" border="0" />
不知道你看出些其他的什么特性出来了没有?我们仔细看下图二,你发现没有,在用absolute定位的时候,它可以覆盖在与其相邻的节点上(不是因为它设置了z-index属性),而是它的一个特性--不占布局或者说不影响邻居节点的布局。而relative则不一样,它会影响邻居节点的布局。我们通过图一还看不出来,来看图三:
" border="0" />
大家注意到图片中的灰色部分没有?这个就是我要说的,这块灰色的部分的大小就是#relative的大小,这个说明了什么?表明它会影响邻居节点的布局,而且邻居节点接下来的位置就是#relative使用一般margin定位的末端。讲详细点,就是说虽然#relative显示的在屏幕的位置是红色块的地方,但是实际仍然要占据它起始位置所在的布局(它的宽和高)的大小。这里#relative的起点是body,那么它站的布局就是从body起点开始width:250px;height:250px;(加padding:5px)的布局(大小),也就是我们看到图中灰色部分。呵呵,好绕是吗?仔细看看,多用下就明白了。
好了,现在就我们ajax标签导航中使用的是relative+absolute的结合,当一个absolute的节点包含在一个relative的节点中时,它的”原始点“就是relative节点了,而不是“参照浏览器的左上角-body”了,而它又不影响其邻居节点的布局,所以它就不会影响<li></li>中间文字(文本节点)的布局了(这里li的空间够大)。这样以来,就得到了我们标签中,每个标签后有一个分隔线的样式了。如图四:
" border="0" />
OK,我们对position属性和它的值的使用搞清楚了。接下来就来看看是怎么来更改当前选中标签(li)的样式吧:
程序代码:
.tabs li{ float:left; display:inline; text-align:center; width:120px; height:12px; padding:4px 0 6px 0; overflow:hidden; letter-spacing:1px; position:relative; } /* 第一项被选中的样式 */ .tabs li.first{ background-image:url(../img/tab_active1.gif); } /* 其他项被选中的样式 */ .tabs li.current{ background-image:url(../img/tab_active2.gif); } /* 被选中项的字体样式 */ .tabs li.current, .tabs li.first{ font-weight:bold; } /* 被选中项的链接颜色样式 */ .tabs li.current a, .tabs li.first a{ color:#D45417; } .tabs li span{ position:absolute; right:0; top:3px; width:2px; height:16px; overflow:hidden; font-size:1px; background-image:url(../img/tab_sline.gif); } /* 选中项和选中项前一项的样式 */ .tabs li.first span, .tabs li.current span, .tabs li.off span{/* 选中项前一项 */ display:none; }
这里要简单说的就是样式表CSS继承(层叠)的顺序,一定要是先写标签(li)标签默认(背景)样式再写选中时的(背景)样式,然后是默认分隔线(span)-> 选中时 -> 失去焦点时这样一个顺序。至于CSS的继承顺序的具体只是,大家可以google一下。
Javascript技巧篇(arguments对象的运用技巧)
好了,我们现在已经把CSS样式写好了,现在就开始用脚本来控制了。通过刚才讲解标签样式的时候,其实我们也基本把脚本控制的逻辑流程分析了下:
1. 选中当前标签的背景要区别显示;
2. 选中标签和其前一个标签的分隔线要隐藏;
不过在改变标签样式这个步骤开始之前我们要给我们的标签菜单(ul)来设置onclick事件(功能函数),从而触发改变当前选中项的样式的事件。到我们的主题了,呵呵!快来看看代码吧:
程序代码:
/* =========================================================== * 函数名称:tabsEvent() * 参数说明:要设置事件的DOM节点ID * 函数功能:为导航TAB菜单(li)设置onclick处理方法(函数), * 屏蔽掉a标签默认的处理(打开新链接)事件 * 返 回 值:false - 屏蔽掉a标签默认的处理(打开新链接)事件 * 使用方法:tabsEvent("news","sports"); ============================================================ */ function tabsEvent(){ for(var i=0;i<arguments.length;i++){ var tabs = $(arguments[i]); // DOM节点(tabs)不存在或者浏览器不支持getElementsByTagName()方法 // 函数不执行 if(!tabs || !document.getElementsByTagName) return false; var theList = tabs.getElementsByTagName("li"); // 搜寻导航标签(ID为tabs)里的所有li标签 var theLink = tabs.getElementsByTagName("a"); // 搜寻导航标签(ID为tabs)里的所有a标签 for(var j=0;j<theList.length;j++){ var theTab = theList[j]; if(theTab.parentNode!=tabs) continue; var theA = theLink[j]; // 屏蔽掉a标签默认的处理(打开新链接)事件 theA.onclick = function(){ return false; } // 为导航TAB菜单(li)设置onclick处理方法(函数) theTab.onclick = function(){ var theClass = this.className; if(theClass!="current" && theClass!="first"){ var objId = this.getAttribute("id").split("-")[1]; // 当前选中标签(li)在菜单(ul)中的索引值 var tarObj = this.getAttribute("id").split("-")[0]; // 要显示信息的目标DOM节点ID值 var theURL = tarObj + "/" + tarObj + objId + ".htm"; // 要异步加载的URL地址 ajaxInject($(tarObj),objId,tarObj,theURL); return false; } } } } }
上面这段脚本,我们使用了$(i)函数获取DOM节点,方法就是$("DOMId"),这里就不多说了。这里要花些时间讲的是arguments对象,恩,...,恩,开始讲arguments对象了,注意听讲(不是在卖弄哦,这个我们经常要用到的,也很重要的一个知识点):
Arguments是进行函数调用时,除了指定的参数外,还另外创建的一个隐藏对象。Arguments是一个类似数组但不是数组的对象,说它类似数组是因为其具有数组一样的访问性质及方式,可以由arguments[n]来访问对应的单个参数的值,并拥有数组长度属性length。还有就是arguments对象存储的是实际传递给函数的参数,而不局限于函数声明所定义的参数列表,而且不能显式创建 arguments 对象。arguments 对象只有函数开始时才可用。
”隐藏对象“,怎么个隐藏法呢?看看我们函数的写法吧:
程序代码:
function tabsEvent(){ ... }
而我在调用这个函数是确是这么写的:
程序代码:
<script language="javascript" type="text/javascript"> <!-- tabsEvent("news","sports"); //--> </script>
“tabsEvent("news","sports");”, 我使用了参数,而我定义tabsEvent时,却没有使用形参(形参个数为零),就是这么个隐藏法。
它像数组,而又不是数组,怎么解释了?还是看个说明arguments不是数组(Array类)的代码:
程序代码:
Array.prototype.selfvalue = 1; alert(new Array().selfvalue); function testAguments(){ alert(arguments.selfvalue); }
运行代码你会发现第一个alert显示1,这表示数组对象拥有selfvalue属性,值为1。而当你调用函数testAguments时,你会发现显示的是“undefined”,说明了selfvalue不是arguments的属性,即arguments并不是一个数组对象。
呵呵,又说了这么多,要将就讲彻底些:caller、callee、apply、call都讲讲吧,^-^!
caller - 返回一个对函数的引用,该函数调用了当前函数。
对于函数来说,caller 属性只有在函数执行时才有定义。如果函数是由顶层调用的,那么 caller 包含的就是 null 。如果在字符串上下文中使用 caller 属性,那么结果和 functionName.toString 一样,也就是说,显示的是函数的反编译文本。 下面的例子说明了 caller 属性的用法:
程序代码:
function callerDemo() { if (callerDemo.caller) { var a = callerDemo.caller.toString(); alert(a); } else { alert("this is a top function"); } } function handleCaller() { callerDemo(); } handleCaller(); callerDemo();
我们通过handleCaller();调用执行callerDemo();时callerDemo.caller才定义,可以看到一个警告框,显示的反编译的handleCaller()的文本。而直接使用callerDemo();时,我们callerDemo函数的caller是没有定义的,所以你会看到”this is a top function“提示字符。
callee - 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。 用法[function.]arguments.callee,可选项 function 参数是当前正在执行的 Function 对象的名称。
callee 属性是 arguments 对象的一个成员,它表示对函数对象本身的引用,这有利于匿名函数的递归或者保证函数的封装性,例如下边示例的递归计算1到n的自然数之和。而该属性仅当相关函数正在执行时才可用。还有需要注意的是callee拥有length属性,这个属性有时候用于验证还是比较好的。arguments.length是实参长度,arguments.callee.length是形参长度,由此可以判断调用时形参长度是否和实参长度一致。
程序代码:
//callee可以打印其本身 function calleeDemo() { alert(arguments.callee); } //用于验证参数 function calleeLengthDemo(arg1, arg2) { if (arguments.length==arguments.callee.length) { alert("验证形参和实参长度一致!"); return; } else { alert("实参长度:" +arguments.length); alert("形参长度: " +arguments.callee.length); } } //递归计算 var sum = function(n){ if (n <= 0) { return 1; } else{ return n + arguments.callee(n - 1); } }
调用alert(sum(9));时,其中函数内部包含了对sum自身的引用,函数名仅仅是一个变量名,在函数内部调用sum即相当于调用一个全局变量,不能很好的体现出是调用自身,这时使用callee会是一个比较好的方法。
apply 和 call 它们的作用都是将函数绑定到另外一个对象上去运行,两者仅在定义参数方式时有所区别:
程序代码:
apply(thisArg,argArray); call(thisArg[,arg1,arg2…] ]);
即所有函数内部的this指针都会被赋值为thisArg,这可实现将函数作为另外一个对象的方法运行的目的
apply的说明:如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。如果没有提供 argArray 和 thisArg 任何一个参数,那么 Global 对象将被用作 thisArg,并且无法被传递任何参数。
call的说明:call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisArg 指定的新对象。 如果没有提供 thisArg 参数,那么 Global 对象被用作 thisArg。
应用call和apply还有一个技巧在里面,就是用call和apply应用另一个函数(类)以后,当前的函数(类)就具备了另一个函数(类)的方法或者是属性,这也可以称之为“继承”。看下面示例:
程序代码:
// 继承的演示 function base() { this.member = "dnnsun_Member"; this.method = function() { alert(this.member); } } function extend() { base.call(this); alert(member); alert(this.method); }
上面的例子可以看出,通过call之后,extend可以继承到base的方法和属性。
呵呵,你可能发现了,在javascript框架prototype里就使用apply来创建一个定义类的模式,其实现代码如下:
程序代码:
var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } }
从代码看,该对象仅包含一个方法:Create,其返回一个函数,即类。但这也同时是类的构造函数,其中调用initialize,而这个方法是在类创建时定义的初始化函数。通过如此途径,就可以实现prototype中的类创建模式,示例代码:
程序代码:
var vehicle=Class.create(); vehicle.prototype={ initialize:function(type){ this.type=type; } showSelf:function(){ alert("this vehicle is "+ this.type); } } var moto=new vehicle("Moto"); moto.showSelf();
呵呵,越扯越多了,现在清楚了arguments对象的用法了吧!哈哈,还是有些收获的吧?^-^!!! 不过该回到我们的主题了,怎么实现点击标签(li)触发更改样式的事件。还是接着看我们的ajaxtab.js的代码吧:
程序代码:
for(var i=0;i<arguments.length;i++){ var tabs = $(arguments[i]); }
看看上面讲的arguments的用法,呵呵,这里我们就获取了全部的标签导航菜单了。arguments.length = ?等于2,arguments[i]是什么,就是我们传的参数本身(tabsEvent("news","sports");),那么$(arguments[i])就是我们的标签菜单,具体点就是$("news")和$("sports")了。
DOM技巧篇(DOM基础知识)
讲到这里,我们就要了解一下DOM的一些基础知识了。
DOM(Document Object Model 文档对象模型)是HTML和XML的应用程序接口(API)。DOM将把整个页面规划成有节点层级构成的文档。HTML或XML页面的每一个部分都是一个节点的衍生物。
光说可能还不怎么好理解,那么来看看我们ajax标签导航的DOM结构吧,如图五(截取的FIXFOX中的DOM图象):
" border="0" />
DOM通过创建树来表示文档,从而使开发着对文档的内容和结构具有空前的控制力。用DOM API可以轻松地删除,添加和替换节点。
简单的了解了什么是DOM后(要想了解更多的Javascript DOM 编程知识,推荐大家看看《Javascript DOM 编程艺术》和《Javascript高级编程》这两本书,还有到作者的网站去看看,也可以直接到W3C去查询相关信息。),来看看我们这个程序里需要用到的DOM知识吧:document.getElementsByTagName()还有之前提到的$(i)函数,它们都是做什么用的呢?
document.getElementById("DOMId"):它返回的是以ID为表示的节点,而大家都知道id在页面中是唯一的,所以getElementById是在页面中搜索DOM节点最直接的方法,很常用。不过它只能查寻单个的DOM节点,我们要查询一组怎么办呢? 我们可以使用getElementsByTagName和getElementsByName。
从方法名字中的“Elements”我们也可以知道,这两个方法返回的是一组元素(数组)
getElementsByTagName:(核心[XML]DOM)用来返回一个包含所有tagName(标签名)特性等于某个指定值的元素的NodeList。
getElementsByName:(HTML DOM)用来获取所有name特性等于指定值的元素。但是这个方法在IE6和opera7.5中支持不是很好,会有错误产生,所以(个人)建议一般不要用。
OK,在对使用DOM来查寻节点的知识有了了解后,我们再来看这段代码:
程序代码:
/* =========================================================== * 函数名称:$(i) * 参数说明:i - 目标节点名称 * 函数功能:获取指定的目标DOM节点 * 返 回 值:返回要搜索的目标DOM节点 * 使用方法:$("frmSearch") ============================================================ */ function $(i){ if(!document.getElementById)return false; if(typeof i==="string"){ if(document.getElementById && document.getElementById(i)) { // W3C DOM return document.getElementById(i); } else if (document.all && document.all(i)) { // MSIE 4 DOM return document.all(i); } else if (document.layers && document.layers[i]) { // NN 4 DOM.. note: this won't find nested layers return document.layers[i]; } else { return false; } } else{return i;} }
这个函数主要是来查找指定的DOM节点的,主要是通过document.getElementById()方法,但是我们又看到了document.all()和document.layers[]方法,这个就是浏览器大战时期(各个浏览器对DOM标准支持不程度不同),各大浏览器提供商制定的各自的DOM支持规范而造成的,我们的CSS HACKS其实也是由于这个原因才会出现的。扯远了,document.all()是IE浏览器(IE5以上版本)中特有的查询节点的方法,而document.layers[i]则是其他浏览器(主要是NetScape的)的浏览器特有的。我们通过$(i)函数来统一调用,从而解决了浏览器兼容的问题。
而下面这里的代码:
// DOM节点(tabs)不存在或者浏览器不支持getElementsByTagName()函数不执行 if(!tabs || !document.getElementsByTagName) return false;
这里if(!tabs || !document.getElementsByTagName) return false;这么写是很有必要的,这里是一种预留退路的思想(在《Javascript DOM 编程艺术》一书一直灌输的思想),这么写在不支持getElementsByTagName()方法时我们的函数就不会执行,在不tabs($("news")和$("sports")),不存在的时候(可能是我们把参数名写错了),函数也不执行了,从而避免了弹出或显示脚本错误的信息。下面我们还将看到这一思想的体现,不过我们还是先来看看紧接的代码:
var theList = tabs.getElementsByTagName("li"); // 搜寻导航标签(ID为tabs)里的所有li标签 var theLink = tabs.getElementsByTagName("a"); // 搜寻导航标签(ID为tabs)里的所有a标签
为什么要找出所有的li和a标签呢?呵呵,这个是由于我这里采取的设置样式的结构决定的(其实我也老是觉得不怎么好,不过这个思维个人比较好理解--头疼医头,脚疼医脚的方法)。看看我们上边的CSS样式,大家看到了,我们是用改变标签菜单(li)的样式来实现特别显示当前标签的。那么我这里就自然要获取所有的li标签,然后给他添加onclick来调用ajaxInject(ListName,tabId,tarObj,URL),从而改变标签的样式。看看我来实现这个功能的代码吧:
程序代码:
for(var j=0;j<theList.length;j++){ var theTab = theList[j]; if(theTab.parentNode!=tabs) continue; var theA = theLink[j]; // 屏蔽掉a标签默认的处理(打开新链接)事件 theA.onclick = function(){ return false; } // 为导航TAB菜单(li)设置onclick处理方法(函数) theTab.onclick = function(){ var theClass = this.className; if(theClass!="current" && theClass!="first"){ var objId = this.getAttribute("id").split("-")[1]; // 当前选中标签(li)在菜单(ul)中的索引值 var tarObj = this.getAttribute("id").split("-")[0]; // 要显示信息的目标DOM节点ID值 var theURL = tarObj + "/" + tarObj + objId + ".htm"; // 要异步加载的URL地址 ajaxInject($(tarObj),objId,tarObj,theURL); return false; } } }
我们通过使用一个for循环来遍历所有的li和a标签(由于它们个数相同,所以索引的值相同),然后分别为它们设置onclick事件。先看我们给a标签添加的事件的代码:
程序代码:
var theA = theLink[j]; // 屏蔽掉a标签默认的处理(打开新链接)事件 theA.onclick = function(){ return false; }
一个简单的return false,可别小看它哦,有几个作用哦,首先a标签是嵌套在li标签里的,我们点li时就一定会出发a标签的默认行为,打开链接的页面。而我们ajax标签导航就是希望不刷(打开)新页面,在指定的DOM节点显示信息。当然就不能让a标签的默认行为启动了,而简单的一个return false;就解决了这个问题。哦,这里还有个问题就是DOM事件的冒泡的顺序,详细的介绍大家可以在《Javascript高级编程》一书中查到。
那有些朋友会问,为什么要在里面加个a标签呢?反正你是改变的是li的样式,点了li,改变li的样式,然后刷新指定DOM节点的信息不就完成了ajax标签导航的功能(效果)了吗?
是啊,不过我在这里要提的就是刚才提到的一个预留退路的思想,如果想上面说的那样做了,当然是没有什么问题,但前提是用户的浏览器支持javascript脚本,或者说用户打开了执行脚本的权限。一旦用户的浏览器不支持javascript或者出于安全原因关闭了脚本执行功能。这个时候,当用户点li时是没有任何反映的。而我这里的处理就考虑到了当javascript执行不了的情况,这时候,用户点链接就可以打开我们原本要用ajax加载的内容了。
其实这里还有各个比较简单的方法来达成我提到的相同的效果,就是对li使用onmouseover事件,想想为什么?因为只要鼠标划过时,就触发了ajaxInject($(tarObj),objId,tarObj,theURL);改变了标签的样式,刷新了内容。当点击链接时,就可以弹出页面了。现在网易和雅虎中国就是这么处理的。其实我这么做主要是处于个人习惯,比较喜欢用onclick,还有就是这里有个分隔线的效果,看上去比网易的只用一个背景图片酷,当然我的效果是学的雅虎(不是雅虎中国)的。不过雅虎的标签样式的处理方式要比我现在的更巧妙,下次有时间再跟大家分析下雅虎的标签导航效果。
又扯远了,OK,接下来我们就是要改变样式和ajax刷新内容了。不过在这个之前,看看我做了什么准备。
程序代码:
<ul class="tabs" id="news"> <li class="first" id="news-0"><a href="news/news0.htm">网站重构</a><span></span></li> <li id="news-1"><a href="news/news1.htm">CSS布局实录</a><span></span></li> <li id="news-2"><a href="news/news2.htm">海啸的地盘</a><span></span></li> <li id="news-3"><a href="news/news3.htm">Ajax高级编程</a><span></span></li> </ul> var objId = this.getAttribute("id").split("-")[1]; // 当前选中标签(li)在菜单(ul)中的索引值 var tarObj = this.getAttribute("id").split("-")[0]; // 要显示信息的目标DOM节点ID值 var theURL = tarObj + "/" + tarObj + objId + ".htm"; // 要异步加载的URL地址
看看我文章前面部分罗列的东西,现在就在这里有了回应了
id="news" - news就是我们的导航标签的ID; id="news-0" - news-0 通过”-“分开,我们就分别可以得到news(导航标签ID),0(标签[li]在导航标签中的索引值)
好现在就要改变标签的样式了
程序代码:
/* =========================================================== * 函数名称:ajaxInject(ListName,tabId,tarObj,URL) * 参数说明:ListName - 标签菜单DOM节点ID * tabId - 选中的标签(在ListName中的)索引值 * tarObj - 要显示返回信息的目标DOM节点ID值 * URL - 要异步处理的URL地址 * 函数功能:设置当前选中标签(li)的样式, * 将返回信息写到指定DOM节点中。 * 返 回 值:无 * 使用方法:ajaxInject($(tarObj),objId,tarObj,theURL); ============================================================ */ function ajaxInject(ListName,tabId,tarObj,URL){ if(!ListName || !document.getElementsByTagName) return false; var Tabs = ListName; var theLi = Tabs.getElementsByTagName("li"); for(var i=0;i<theLi.length;i++){ // 设置当前选中标签的样式 if(i==tabId){ if(i==0){ theLi[tabId].className = "first"; // 当选中第一项的样式 } else{// theLi[tabId].className = "current"; // 选中其他项的样式 } var msgBox = tarObj+"Cnt"; var loadstatustext="<div class='loading'><img src='img/loading.gif' alt='正在加载内容, 请稍候...' />正在加载内容, 请稍候...</div>"; $(msgBox).innerHTML = loadstatustext; // 加载信息时的提示信息 var para = "?d=" + Math.random(); // URL后的参数,接Math.random()(一个随机数),目的是处理ajax的缓存问题 var myAjax = ajaxUpdater(msgBox,"get",URL,para); } else{// 设置其他标签的样式 theLi[i].className = ""; if(tabId!=0){ theLi[tabId-1].className = "off"; // 当不是第一项时,隐藏选中项的前一项的分隔标签 } } } }
这里又跟前面的
id="newsCnt" - newsCnt就是我们要写入信息的目标DOM节点; class="first" - first当前(第一个)标签的样式;
对应起来了。
程序代码:
for(var i=0;i<theLi.length;i++){ // 设置当前选中标签的样式 if(i==tabId){ if(i==0){ theLi[tabId].className = "first"; // 当选中第一项的样式 } else{ theLi[tabId].className = "current"; // 选中其他项的样式 } } else{// 设置其他标签的样式 theLi[i].className = ""; if(tabId!=0){ theLi[tabId-1].className = "off"; // 当不是第一项时,隐藏选中项的前一项的分隔标签 } } }
上面这段代码就是具体改变样式的,i==tabId比较当前标签的索引值(作用就是确认是否是选中的标签),相等了就给标签设置样式了。i==0表明第一项被选种(由于我的第一项的背景特殊的)给它加上“first”样式,其余项被选中则加上“current”样式。
接这就是处理分隔钱的样式了,跟设置背景大同小意,这里要说的是我们在写CSS的时候要把li(选中和失去焦点)的样式设置好。还是我之前提到的,YAHOO的做得很好,我记得网上也有关于滑动门技术CSS写法的介绍,大家可以看看是怎么来设置样式的,还有这里给大家推荐个小软件《CSS Tab Designer 2》。
恩,现在要刷新指定DOM节点的内容了,用一个简单的var myAjax = ajaxUpdater(msgBox,"get",URL,para);就解决问题了。不过我们看看在把信息写到指定DOM节点前,我做了什么:
程序代码:
var msgBox = tarObj+"Cnt"; var loadstatustext="<div class='loading'><img src='img/loading.gif' alt='正在加载内容, 请稍候...' />正在加载内容, 请稍候...</div>"; $(msgBox).innerHTML = loadstatustext; // 加载信息时的提示信息
这里做了一个提示的处理,因为ajax是异步的加载,在获取很长(信息量很大)的内容时,会有一个延时,如果在这个期间不给任何提示信息的话,我们要刷新的DOM节点会出现白白的一片,这个当然是不美观的。所以在这之前,我们给用户一个提示信息,说明正在加载信息会显得更人性化些。当然,大家知道的网易的处理会更好些,做一个延迟的window.setTimeout效果。