一個禮拜沒動靜了,實在是懶惰。。 好了,不扯淡了,進入正題:框架封裝之選擇器模塊。 首先,我們為什麼要封裝框架? 淺顯的文字不具有良好的說服性,來做幾個題目吧: 求一個數組所有項之和 2. 求數組中最大值 3. 獲取數組中指定值 ok,題目做到這就行了,我們可以看出每道題目中都用到了for迴圈,每次 ...
一個禮拜沒動靜了,實在是懶惰。。
好了,不扯淡了,進入正題:框架封裝之選擇器模塊。
首先,我們為什麼要封裝框架?
淺顯的文字不具有良好的說服性,來做幾個題目吧:
-
求一個數組所有項之和
//求和 var sum=0; for(var i=0; i<arr.length;i++){ sum+=arr[i]; } console.log(sum);
2. 求數組中最大值
//求最大值
var max=arr[0]; for(var i=0; i<arr.length;i++){ if(max<arr[i]){ max=arr[i]; } } console.log(max)
3. 獲取數組中指定值
//獲取指定值 var index=0; function get(v){ for(var i in arr){ if(arr[i]===v){ index=i; break; } } }
ok,題目做到這就行了,我們可以看出每道題目中都用到了for迴圈,每次都寫一遍是不是很麻煩,這裡代碼不多,可能感覺還好,如果工作量大,冗餘度是很高的,所以我們就可以封裝這個for迴圈:
// arr: 目標數組 // fn: 自定義方法 var each= function ( arr , fn ) { for(var i=0; i<arr.length;i++){ // 使用call改變作用域,後面調用可直接使用this if( fn.call(arr[i],i,arr[i])===false) break; //做搜索操作,break必須寫到迴圈中,此處使用返回值檢索是否執行break } }
封裝後重寫求和方法
//1.封裝後求和 var sum=0; each(arr,function(i,v){ sum+=v; })
好了,其他就不寫了,比較簡單,所以框架主要是為了降低程式之間的依賴性和耦合性,使重用性達到最高,下麵說說我們的重點,如何封裝選擇器框架。
目標:實現常見選擇器框架的封裝,即id選擇器,類選擇器,標簽選擇器。
//傳統做法 function getId(id){ return document.getElementById(id); }
function getTagName(name){ return document.getElementsByTagName(name); }
//獲取id為box的節點,然後操作
var dom = getId('box');'red';
dom.style.background = 'red';
//獲取屬性名為div 的節點,然後操作
var tags = getTagName('div');
for(var i=0; i<tags.length;i++){
tags[i].style.background = 'red';
}
傳統做法如上,可以通過該方法實現普通需求,但是要是繼續想通過類名,標簽名獲得屬性,然後分別加樣式,豈不是很累,所以我們可以通過下麵方式取代原始方法:
//id 選擇器 function getId(id,node,arr){ arr.push(node.getElementById(id)); return arr; } // 屬性選擇器 function getName(name,node,arr){ [].push.apply(arr,node.getElementsByTagName(name)); return arr; } // 類選擇器 function getClass(clas,node,arr){ [].push.apply(arr,node.getElementsByClassName(classname)); return arr; }
緊接著我們再實現一個介面, 根據輸入,調用不同的選擇器
//多選擇器介面 // getType( tag , node , arr ) // 類型 父節點 數組容器 function getType(tag,node,arr){ node= node || document; arr= arr || [];
rag(1) rag(2) rag(3) rag(4) var rag=/^(?:#([\w\-]+)|\.([\w\-]+)|([\w\-]+)|(\*))$/; var type=rag.exec(tag); if(type){ if(type[1]) arr=getId(type[1],node,arr); else if(type[2]) arr=getClass(type[2],node,arr); else arr=getName(type[3]||'*',node,arr); } return arr; }
上文中用到了正則表達式,簡要解釋下,/^(?:#([\w\-]+)|\.([\w\-]+)|([\w\-]+)|(\*))$/
^表示已xx開頭,$表示已xx結尾;
(?:)其中'( )'表示分組,此處使用只是提升中間部分優先順序,使用 ?: 取消分組;
匹配 id:#([\w\-]+) 可通過tag(1)獲取除#外的id字元
匹配 類:\.([\w\-]+) tag(2)
匹配 標簽名:([\w\-]+) tag(3)
通配符:(\*) tag(4)
PS:
var arr1=[1,3,4];
var arr2=[3,4,5];
如果我們要把 arr2展開,然後一個一個追加到arr1中去,最後讓arr1=[1,3,4,3,4,5]
arr1.push(arr2)顯然是不行的。 因為這樣做會得到[1,3,4,[3,4,5]]
但是我們可以用 Array.prototype.push.apply(arr1,arr2) 實現需求
因此我們這樣做 [].push.apply(arr,node.getElementsByTagName(name));
但是,在IE8 及低於IE8以下的瀏覽器需要註意幾個問題.
1、 apply 傳參不接受類似 {0:'a',1:'b',length:2} 的對象,可以是 數組、arguments、 HTMLCollection 對象 和 Nodelist 對象等節點集合.
在這種情況下你也許想要把傳參對象轉換成數組.
2、節點集合無法調用數組的原型方法,但是 類似 {0:'a',1:'b',length:2} 的對象可以。
看似我們實現了所有需求,但是別忘記另我們頭疼的IE8瀏覽器,它不支持 getElementsByClassName 為此我們需要解決相容問題。
您也許會想到以下方法:
if(document.getElementsByClassName){ var results= document.getElementsByClassName('box'); } else { //自己實現的方法 }
但是存在兩個問題 1.性能問題 2.安全問題
先解決性能問題 :我們都知道原型鏈吧,當函數或對象執行某方法或使用屬性時,會從其自身開始搜尋,然後一直沿著原型鏈往上找,直至找到為止才停止尋找,每往上一層,都會增加搜尋時間,降低性能,getElementsByClassName是
document的屬性方法,因此最好將該屬性放在當前對象中,而且每次執行時都會判斷是否相容,也降低性能,因此我們可以用support對象緩存能力檢測結果,如下
1.提升性能
var support={}; //!! 裝換成boolean類型 support.getElementsByClassName =!!document.getElementsByClassName; if(support.getElementsByClassName){ var results= document.getElementsByClassName('box'); } else { //自己實現的方法 }
減少了性能問題,但是存在安全問題 ,看下麵代碼會輸出什麼?
document.getElementsByClassName=123; //註入代碼攻擊 var support={}; support.getElementsByClassName = !!document.getElementsByClassName; if(support.getElementsByClassName){ console.log('支持ByClassName'); } else { console.log('不支持ByClassName'); }
無論在什麼瀏覽器中都會輸出: 支持ByClassName.
因為document.getElementsByClassName=123;這句代碼,此時執行判斷是true,具體傳送門==>http://blog.csdn.net/writehappy/article/details/8970491
告訴你們一個大牛們使用註入代碼攻擊的做的事:在chrome上為自己的微博刷贊!!!
好了,轉回整體,如何解決這個問題呢?
繼續看操作:
2.提升安全性
// 在全局作用域(最終要變成沙箱模式),提供一個support對象,存儲屬性是否支持,不用每次檢測 var support={}; support.getElementsByClassName = (function () { istrue= !!document.getElementsByClassName; if(istrue&& (typeof document.getElementsByClassName==="function")){ //不僅判斷是否支持該方法,還要執行該方法檢驗 var div=document.createElement('div'), testDiv=document.createElement('div'); testDiv.className='test'; div.appendChild(testDiv); //檢驗 return div.getElementsByClassName('test')[0]===testDiv; } else return false; })();
很棒吧,jquery就是這麼實現的,以下
jquery實現
/** * Support testing using an element * @param {Function} fn Passed the created div and expects a boolean result */ function assert( fn ) { var div = document.createElement("div"); try { return !!fn( div ); } catch (e) { return false; } finally { // Remove from its parent by default if ( div.parentNode ) { div.parentNode.removeChild( div ); } // release memory in IE div = null; } } // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert(function( div ) { div.appendChild( doc.createComment("") ); return !div.getElementsByTagName("*").length; }); // Support: IE<9 support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
那麼我們的類選擇器可以重新寫了
類選擇器重寫
function getClass(clas,arr,node){ arr=arr||[];; [].push.apply(arr,getclass(clas,node)); return arr; } //getClas相容方法 function getclass(classname,node){ node=node || document; if(support.getElementsByClassName){ return node.getElementsByClassName(classname); } else { // 2.自己實現的代碼 var arr=document.getElementsByTagName('*'); var results=[]; each(arr, function (k,v) { if(this.className===classname){ results.push(this); } }); return results; } }
實現效果:
![](http://images2015.cnblogs.com/blog/927925/201604/927925-20160410234946390-846076467.png)
ok,這次就講到這兒吧,啰啰嗦嗦,希望各位不要嫌棄我的這點拙見,還希望多多指點,共同進步,未完待續。
2016-04-10