幾天前參考了一本關於jQuery的國內的書,學習了如何用原生的javascript實現jQuery的"功能"——自訂函數擴充DOM功能,來仿製部分的jQuery效果。因為發現這樣的方法可以在無法使用jQuery的時候(例如和其他架構及自訂函數發生衝突)對想要的效果直接構造實現(只為一小部分功能)。這裡主要是關注構造和實現的過程。
首先來看事情的起因:n天前,在一個自己東拼西湊(主要是拼湊了兩個別人的首頁幻燈圖片切換js)幫別人做的一個頁面上,試圖用jQuery增加一些動態更美觀,於是對某個用於登入的表單視窗進行隱藏,並打算用slideDown()進行動態彈出出控制。在指令碼沒有任何錯誤的情況下,發現無論如何沒有效果。在firefox中開啟web控制台,調用動作沒有響應,感覺$初始化未起作用,於是更換為函數方法調用,出現這樣的報錯:
$(...).is is not a function
不得其解,後來逐一排除,發現當我刪掉其他幾個js的指令碼調用時,恢複了正常。最後把問題確認在了一個名為ntes_jslib_1.x.js的指令碼上:
<script language="javascript" src="js/ntes_jslib_1.x.js" type="text/javascript" charset=utf-8></script>
這是一個網上找來到滑動切換的指令碼,未開啟細看。發現把此指令碼去除就恢複正常。由此確定是jQuery與它發生的衝突。於是思考解決辦法。down來的指令碼沒有細看,更換其他架構又不求甚解(雖然本來也沒有去刨根問底)。於是想到用原生方法能否實現(僅僅為了一個小動態,不再使用jQuery)。這便回憶起之前在一本書上對擴充DOM的瞭解(書名為《犀利開發jQuery核心詳解與實踐》,作者:朱印宏)。於是找回書本進行嘗試。
對於擴充DOM函數功能來說,DOM與javascript具有不同的“範圍”,由書上的例子來說,下面的功能是無法實現的:
var appendTo()=function(e){e.appendChild(this);return this;}window.onload=function(){var div=document.getElementsByTagName("div")[0];var h1=document.createElement("h1");h1.appendTo(div);//調用自訂的方法,實際無法實現}
因此,擴充DOM的必要就在於在使用javascript擴充功能之前,為其提供作用的基礎。
對於DOM的擴充,因為瀏覽器的兩大派別(其實還是因為神IE的不支援HTMLElement類型),有兩種需要:對HTMLElement類型原型對象添加自訂方法(IE不支援),和對DOM節點綁定自訂方法(for IE)。
詳細的實現如下:
var DOMextend=function(name,fn){if(!document.all)//用於判斷非IEeval("HTMLElement.prototype."+name+"=fn");else{//IEvar _createElement=document.createElement;//儲存原方法document.createElement=function(tag){//重寫方法,為createElement()綁定自訂var _elem=_createElement(tag);//首先加入原方法eval("_elem."+name+"=fn");//綁定自訂,下同return _elem;}var _getElementById=document.getElementById;document.getElementById=function(id){//為getElementById()綁定自訂var _elem=_getElementById(id);eval("_elem."+name+"=fn");return _elem;}var _getElementsByTagName=document.getElementsByTagName;document.getElementsByTagName=function(tag){//為getElementsByTagName()綁定自訂var _arr=_getElementsByTagName(tag);for(var _elem=0;_elem<_arr.length;_elem++)eval("_arr[_elem]."+name+"=fn");return _arr;}}};
由此之後,在構造了DOMextend()方法的基礎上,就可以繼續擴充和構造其他的自訂函數:
我想要模仿的,是jQuery的slideDown()功能,因此我需要先構造的方法有:
getStyle()用於擷取元素樣式,offset()用於擷取絕對位移位置,fromStyle()用於將樣式值轉換為數值進行運算,setCSS()用於設定元素樣式,resetCSS()用於設定樣式之後恢複樣式,以及width()和height()用於擷取元素的寬和高。
詳細如下:
DOMextend("getStyle",function(n){var _this=this;if(_this.style[n]){return _this.style[n];}else if(_this.currentStyle){return _this.currentStyle[n];}else if(document.defaultView && document.defaultView.getComputedStyle){n=n.replace(/([A-Z])/g,"-$1");n=n.toLowerCase();var s=document.defaultView.getComputedStyle(_this,null);if(s)return s.getPropertyValue(n);}elsereturn null;})DOMextend("offset",function(){var _this=this;var left=0,top=0;while(_this.offsetParent){left+=_this.offsetLeft;top+=_this.offsetTop;_this=_this.offsetParent;}return{"left":left,"top":top};})DOMextend("fromStyle",function(w,p){var _this=this;var p=arguments[2];if(!p)p=1;if(/px/.test(w) && parseInt(w))return parseInt(parseInt(w)*p);else if(/\%/.tese(w)&&parseInt(w)){var b=parseInt(w)/100;if((p!=1)&&p)b*=p;_this=_this.parentNode;if(_this.tagName=="BODY")throw new Error("無尺寸!");w=_this.getStyle("width");//方法之間很多穿插使用return arguments.callee(_this,w,b);}else if(/auto/.test(w)){var b=1;if((p!=1)&&p)b*=p;_this=_this.parentNode;if(_this.tagName=="BODY")throw new Error("無尺寸!");w=_this.getStyle("width");return arguments.callee(_this,w,b);}elsethrow new Error("特殊單位!");})DOMextend("setCSS",function(o){var _this=this;var a={};for(var i in o){a[i]=_this.style[i];_this.style[i]=o[i];}return a;})DOMextend("resetCSS",function(o){var _this=this;for(var i in o){_this.style[i]=o[i];}})DOMextend("width",function(){var _this=this;if(_this.getStyle("display")!="none")return _this.offsetWidth||_this.fromStyle(_this.getStyle("width"));var r=_this.setCSS({display:"",position:"absolute",visibility:"hidden"});var w=_this.offsetWidth ||_this.fromStyle(_this.getStyle("width"));_this.resetCSS(r);return w;})DOMextend("height",function(){var _this=this;if(_this.getStyle("display")!="none")return _this.offsetHeight||_this.fromStyle(_this.getStyle("height"));var r=_this.setCSS({display:"",position:"absolute",visibility:"hidden"});var h=_this.offsetHeight ||_this.fromStyle(_this.getStyle("height"));_this.resetCSS(r);return h;})
最後,萬事俱備,構造slideDown():
DOMextend("slideDown",function(time,fn){var _this=this;var isShow=_this.getStyle("display");if(isShow!="none")return;var oldcss=_this.setCSS({display:"",visibility:"hidden"})var x=_this.offset().left;var y=_this.offset().top;var height=_this.height();var width=_this.width();_this.resetCSS(oldcss);_this.style.display="";var box=_this.cloneNode(true);for(var i=0;i<box.childNodes.length;i++){box.removeChild(box.childNodes[i]);}_this.parentNode.insertBefore(box,_this);_this=_this.parentNode.removeChild(_this);box.appendChild(_this);box.style.overflow="hidden";box.style.display="";box.style.height=0;box.style.width=width;var step=5;var stepheight=step*height/time;var curstep=0;var interval=setInterval(function(){if(curstep>=height){clearInterval(interval);_this=_this.parentNode.removeChild(_this);box.parentNode.insertBefore(_this,box);box.parentNode.removeChild(box);fn();}else{curstep+=stepheight;box.style.height=curstep+"px";}},step);})
在我需要調用的部分進行調用:
js部分:
function bonce(){var hiddes=document.getElementById("hiddenlog");hiddes.slideDown(300,function(){;}//暫不需要回呼函數,空語句 );}
頁面部分:
<!----login start--> <div id="login"> <div id="loginmen"> <img src="images/glydl.png" id="loginbonce" onclick="bonce()"/> </div> <div id="loginsub"> <div id="hiddenlog" style="display:none;"> <!--被隱藏的部分--> </div> </div> </div> <!--login end-->
由此我得以在不使用jQuery的條件下構造出slideDown()方法達到需要的效果。