天天看點

JS魔法堂:追憶那些原始的選擇器

一、前言                                                                                                   

  首先這裡說的原始選擇器是指除 queryselector 、 queryselectorall 外的其他選擇器。從前我隻使用 getelementbyid 擷取元素并沒有覺得有什麼問題,但随着參與項目的前端規模逐漸擴大,踩的坑就越來越多,于是将踩過的和學習過的經驗教訓記錄在這裡,供以後好查閱。

二、htmldocument和htmlelement下的正常選擇器                                    

1. htmldocument的選擇器: getelementbyid 、 getelementsbyname 、 getelementsbytagname、 getelementsbyclassname 

2. htmlelement的選擇器: getelementsbytagname 、 getelementsbyclassname 

三、被遺忘的小夥伴getelementsbyclassname                                            

  對于像我這樣被專注于管理類背景系統開發的僞前端碼農來說, getelementsbyclassname 确實是見都沒見過,因為ie5678原生就不支援它。但從命名可知其功能就是,它是通過類名選擇元素。那麼我們就可以polyfill一下了。

  注意:上述的polyfill僅僅是表面填補泥而已,傳回的為節點數組并非htmlcollection類型對象,是以缺失隻讀、實時同步、item和nameditem等特性。

四、ie567下getelementbyid的詭異行為                                                     

  通過望文生義,getelementbyid理應隻傳回id屬性值比對的元素,而ie8+、webkit和molliza也是這樣做的。但ie567卻不遵循這一法則,它們會擷取id屬性值或name屬性值比對的元素,然後以第一個比對的元素作為傳回值。

示例:

html

javascript

針對上述ie的bug我們可以進行簡單的修複

五、ie56789下getelementsbyname的怪異行為                  

經踩坑發現在ie56789下使無法通過getelementsbyname來擷取table、td、th、tr、tbody、thead、tfoot的

對象引用,查閱w3c表示這些元素的固有屬性本來就沒有name,是以最初認為ie這一行為是正确的。但經過試驗發現同樣沒有name固有屬性的

colgroup、caption和col卻能通過getelementsbyname擷取,于是開始頭大了。然後轉向ie10+、webkit和

molliza進行同樣的測試,均可成功擷取,于是判斷這是ie56789的怪異行為。

發現這一問題後我想到的是對ie56789下getelementsbyname的傳回值進行加工,将name屬性值比對的table、td、th、

tr、tbody、thead和tfoot對象都加上去,雖然這樣就解決了對象缺失的問題,但又引入了新的問題,那就是

getelementsbyname的傳回值不再是htmlcollection類型,是以失去了與文檔節點資訊實時同步、隻讀、item成員方法、

nameditem成員方法的特性。

  失去得顯然比得到的少,于是我決定不修複這一怪異行為。

六、無法更改執行上下文的this引用?                        

錯誤的示例:

根據現象推測,getelementid内部實作可能是針對特定的dom對象而工作的,是以當強行改變this引用時,就會跑異常。

七、ie5678下選擇器的原型鍊上少了function?                                                     

  也許你看到這個标題的時候會認為這是不可能的事,因為 document.getelementbyid.call 是真實存在的呀。但 document.getelementbyid instanceof function 居然傳回false,現在頭大了吧。讓我們再通過下面對function原型增強來驗證一下吧!

事實證明ie5678下選擇器的原型鍊沒有function,那選擇器就無法共享各種對function原型的增強了,是以我們需要通過一層薄薄的封裝來處理。

八、ie獨創的選擇器                                                                                        

  上面說到的選擇器是各大浏覽器廠商都支援,而ie獨創的選擇器我想大家都會想到是 document.all ,但這個類函數水可不淺,下面讓我們來踩一下吧!

總結一句,若要使用那就使用 document.all[{string} id或name] 就好了(其他傳回的是正常的nodelist嘛),其它用法能不用就堅決不用吧。

  另外,除了document擁有all屬性外,其實直接繼承node類型的都擁有all屬性,也就是說素有dom對象均有all屬性用于擷取其所有子節點。

0級dom武士刀                          

  0級dom:在w3c标準dom起草前,由網景公司定義的節點操控api,并後來作為w3c标準的0級dom規範。

九、隐藏的武士刀一: document.forms                                                                     

  無論是在w3c還是其他管道查閱都被告知該函數用于擷取頁面上所有form元素,當然這點說得一點都沒有錯,但不夠深入。那麼如何深入呢?那麼就要從form的嵌套入手了。

html:

1. form元素個數差異

  ie5678、webkit和molliza都會排除嵌套的form元素,而ie9會保留form元素。

通過在chrome的調試工具可檢視webkit解析生成的dom樹結構,是不生産嵌套的form元素的,并且将嵌套的form節點下的子節點提取

到上一級。而在ie5678下,通過調試工具發現dom樹中依然包含嵌套的form元素節點,但其下的子節點被提取到上一級。而ie9下的嵌套form節

點在dom樹中被完整的建構,是以不僅dom中包含嵌套的form節點,而且其子節點并沒有被提取到上一級。

下面代碼級的驗證:

2. form節點下表單節點的差異

  通過 form元素.length 可擷取其下的 input節點 個數,通過 form元素[{number} 索引] 擷取指定位置的 input元素 。

寫到這裡我想有人會說哪有人會寫嵌套form的啊,确實能寫出這種html結構出來的,我也十分佩服。總結一句,真心請大夥不要嵌套form。下面我們再羅列出

 下面是判斷嵌套form和排除的方法,但不建議為排除嵌套form而重寫document.getelementsbytagname等方法,因為會将

原來為htmlcollection或nodelist類型的傳回對象,改為沒有實時同步特性的array對象,何苦呢。。。。。。

十、隐藏的武士刀二: document.links                        

  擷取文檔中所有擁有href屬性的a和area對象的引用。但在ie5678中 document.links是個類函數,而在webkit和molliza中是個htmlcollection對象。

十一、隐藏的武士刀三: document.scripts                                                                      

   擷取文檔中所有script對象的引用。但從ie5678到webkit、molliza都包含以自閉合格式聲明的script對象 <script /> ,正确的聲明格式是 <script></script> 。

但在ie5678中 document.scripts是個類函數,而在webkit和molliza中是個htmlcollection對象。在ie5678下的具體玩法如下:

十二、隐藏的武士刀四: document.stylesheets                       

擷取文檔中所有style和link的cssstylesheet類型對象的引用,與

document.getelementsbytagname('style')和

document.getelementsbytagname('link')擷取的是htmlstyleelement類型對象是不同的,在

ie5678中是一個類函數,webkit和molliza中是一個stylesheetlist類型對象(屬于nodelist類型,想了解跟多

nodelist和htmlcollection可留意另一篇《js魔法堂:那些困擾你的dom集合類型》)。由于涉及的邊幅過大,是以打算另開一篇

《js魔法堂:哈佬,css.js!》

十三、隐藏的武士刀五: document.anchors                        

   擷取文檔中所有錨對象(htmlanchorelement)的引用。該方法在ie5678下傳回的是一個類函數,在webkit、molliza下傳回一個htmlcollection對象。并且在ie5678和webkit、molliza的擷取的錨對象個數也不同。

十四、隐藏的武士刀六: document.images                      

  擷取文檔中所有img的對象引用。 該方法在ie5678下傳回的是一個類函數,在webkit、molliza下傳回一個htmlcollection對象。

十五、隐藏的武士刀七: document.embeds                      

  擷取文檔中所有embed的對象引用。該方法在ie5678下傳回的是一個類函數,在webkit、molliza下傳回一個htmlcollection對象。

十六、隐藏的武士刀八: document.applets                       

   擷取文檔中所有applet的對象引用。該方法在ie5678下傳回的是一個類函數,在webkit、molliza下傳回一個htmlcollection對象。

十七、隐藏的武士刀九: document.plugins                       

  效果和document.embeds一樣

十八、完整實作                                  

   這裡對getelementbyid,getelementsbytagname,getelementsbyname進行了封裝進而繼承function,并polyfill了getelementsbyclassname,并排除嵌套form的問題。

其中關于通過 (!+[1,]) 判斷ie5678的黑魔法我想大家早已從司徒正美的blog那聽聞過了,但底層到底是怎樣換算出來的呢?我們可以通過後面的《js魔法堂:隐式類型轉換的背後》來一起探讨一下!

十九、總結                                 

  本來沒想寫這麼多,但一邊寫一邊找資料來盡量使内容完善,自己也得益不少。當然,内容上依舊不全面,望大家一起補充,一起探讨^_^!

繼續閱讀