DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 用於節點操作的API,顛覆原生操作HTML DOM節點的API
用於節點操作的API,顛覆原生操作HTML DOM節點的API
編輯:關於JavaScript     
第一次看到敏捷開發的定義,我就被敏捷開發迷住了。通俗來說,敏捷開發可以讓我們用過的代碼可以再次重用,因為是再次重用,所以相對安全,再次調試也沒有第一次那麼費心,省時省力。不斷重用代碼的過程中把存在的bug不斷的修復,也因為不斷的去重用, 這個模板變得起越來越獨立,適用的情況越來越廣范,最後在安全方面達到銅牆鐵壁,在開發方面達到隨心所欲,在維護方面達到從容面對。

敏捷開發的確是利害,但如何練就這種深奧的武功呢?就我自身的情況靠人傳授武功是不可能了,因為公司就我一個做開發的,苦思冪想之後,決定從開源的優秀框架入手,把它一行一行代碼看懂,然後再為我所用。因為是一個人開發,前台和後台都得包辦,哪從那一面做起呢? 之前有過一二個月的開發經驗,覺得前台的JS很費時,而且老覺得在做重復的事情,比如發ajax請求,接收結果之後操作節點(有時候遇到不兼容的情況,如select和table在IE下不支持使用innerHTML,style在IE不會自動轉化為字符串,要用cssText代替,一旦這些情況遇上,真得是無比打擊程序員的積極性,因為你要為此花時間去找替代方案,去調試),還有節點輪換,彈出層,表單驗證等一系列煩瑣工作。所以我就堅決從前台的JS做起。為了練就怎麼把JS的重用性提高,我選擇向Jquery取經。花了幾個月看了一大半,略有所得。我其中之一的JS模塊“無限深度操作節點”(文采不好,名字不妥別見怪)出來了。有了它,我在操作節點方面變得容易,代碼變得精簡,而且不用寫額外的代碼去兼容浏覽器,談笑中,功能就完成了。
首先我談談是什麼讓操作節點給我們帶來煩惱:
  • 編寫ajax程序時,動態增刪改頁面元素幾乎是不可避免的,使用屬性innerHTML就是我們經常利用的途徑,但在IE中table,thead,tfoot,tbody,tr,col,colgroup,html,title,style,frameset的innerHTML屬性是只讀的,也就是我們不能使用innerHTML更新這些節點。(這裡沒有提到select,其實select也是不會成功的,估計是IE的bug)。例子如下:(下面的$id代表document.getElementById) <select id="jkit">
        <option value="1">jquery</option>
    </select> //執行代碼:
    $id('jkit').innerHTML = '<option>jquery</option>';
    //IE下並沒有報任何錯誤,但select一個option節點都沒有了。如果你對table使用innerHTML,IE會報unknown runtime error  對於這種情況比較常用的兼容方法是外加一個外層元素,例子如下: <div id="jkit">
        <select>
            <option value="1">jquery</option>
        </select>
    </div> //執行代碼:
    $id('jkit').innerHTML = '<select><option value="1">jkit</option></select>';
    //這樣IE也成功改變select,但這種做法有個缺點,如果你對select注冊過事件,這些事件會全部丟失,你要外加代碼來重新注冊事件。  
  • 在指定的節點前後新增節點,這就涉及於節點定位,節點創建以及給節點屬性設置。使用innerHTML通常只用於覆蓋DOM元素的所有節點,如果只想改變元素的某個子節點,或者只想在某個子節點前後增加節點,仍然使用innerHTML就適得其反,實現起來很吃力了,而且使用了innerHTML之後,對子節點注冊過的事件肯定全部丟失掉。不使用innerHTML,那只好使用原生的DOM方法了,但這種代替方案也不好使,看下面例子: <select>
        <option value="1">jquery</option>
        <option value="2">jkit</option>
        <option value="3">mars</option>
    </select> //現在我想在jkit之前多加一個option,用原生的DOM方法實現:
    var newNode = document.createElement('option'),//新建一個節點
    selector = document.getElementById('jkit3'),
    /* 也可以用selector.options,但getElementsByTagName更通用。
    那用childNodes怎麼?最好也不要,對於空白節點,IE和FF的處理方式不一樣,
    就這例子,在FF中,select的firstChild是空白文本節點,
    因為select和第一個option之間有換行以及空白字符,
    FF會為其創建節點,而IE會忽略 */
    options = document.getElementsByTagName('option');
    newNode.setAttribute('value','new');
    //newNode.setAttribute('text','NewNode');text不支持這樣設置
    //newNode.text = 'NewNode';ie不支持這種方式
    newNode.innerHTML = 'NewNode';
    selector.insertBefore(newNode,options[1]);//在kit之前插入
     執行了上面的代碼之後,select多了一個option:
    <select>
        <option value="1">jquery</option>
        <option value="new">newNode</option>
        <option value="2">jkit</option>
        <option value="3">mars</option>
    </select>
    新增一個節點這要用到6句代碼,現在只是新增一個節點,那對於批量操作怎麼辦啊?而且原生的DOM方法不怎麼好用,名字長而且參數順序不好記住,用的時候還得翻文檔(除非你的開發的天天在操作節點,天天用這些方法)。有人說,也不怎麼麻煩啊,我可以把這6句代碼寫成一個方法,然後通過傳參來完成批量操作。某種程度上是可以減輕工作量,但看遠一點,這也是不怎麼現實的。原因有三:其一,每次新增的節點的屬性不一樣,加的位置不一樣,你還得在方法外面確定新節點的位置以及在方法內部判斷這個節點要加那些屬些,下個節點要加哪個屬性,把代碼寫在法方裡面只能讓你稍為輕松了些;其二,上面的代碼可以看到,新增一個節點,你要排除很多不兼容的實現方法,由其在設置屬性方面,每種元素有不一樣的屬性,在兼容方案上就有差異,你針對不同元素寫這樣的方法時,你必須要記住每種元素的兼容方案,可誰敢說能記住所有兼容方案,而且程序員想在技術上有所作為,靠的是思維邏輯的提升,而不是記憶,沒必需在這方面下苦功;其三,一個項目是實現功能了,下一個項目再來,有相似的功能,但在細節上有差異,按上面的做法,可以想像的到你的代碼重用性非常得低,因為要改變某些細節,你得重新理解曾經寫下的邏輯,重新為你細節上的不同重構代碼,這樣一來,效率太低了,完全談不上敏捷開發。

    看到這樣,估計讀者都會問,如果不是這樣做,難道開發真的可以做到復制粘貼嗎?可以肯定的說,是不可能的。再全面的代碼也不可能滿足實現了不同包羅所有邏輯。但可以做得復用性很高,關鍵在於找出不同邏輯的共性並很好的獨立出來。上面的操作節點做法我之所以說復用性低,是因為把操作節點跟特定的邏輯混合一起了,就是說特定邏輯的代碼跟操作節點的代碼關系太緊密了,到下一個邏輯又遇上節點操作時,再想重用上次的代碼,就必須溫習上次寫下的邏輯,然後把操作節點的代碼分離出來後再派上用場。下面介紹的由我開發的無限層次節點操作,就很好的從邏輯上獨立出來,與邏輯無關。
  • 上面的以select為例子,option是select的孩子節點,我們操作的是兩層的樹型結構,如果再深入一些,操作元素的子孫節點呢?如何定位元素後代節點呢?下面還是以原生的dom方法來實現: <table>
        <tr>
            <td>
                <ul>
                    <li></li>
                    <li></li>
                </ul>
            </td>
            <td>
                <ul>
                    <li></li>
                </ul>
            </td>
        </tr>
    </table> //現在我想在第一個li之前多加一個option,用原生的DOM方法實現:
    newNode = document.createElement('li'),
    table = document.getElementById('jkit4'),
    //取li的父節點:
    uls = table.getElementsByTagName('ul'),
    /* getElementsByTagName雖然通用,但如果li標簽裡面嵌套了li,li父節點的兄弟節點也有li的話,那麼getElementsByTagName都會取到這些節點,如果你的html結構真有哪麼復雜,取出來結果後你也很難定位到你想找的li節點。遇到這情況,你只以通過childNodes一層層往下找,但之前提過childNodes在IE和FF中行為是不致的,所以你還要做兼容處理。*/
    lis = table.getElementsByTagName('li');
    newNode.innerHTML = 'NewNode';
    //在指定位置插入
    uls[0].insertBefore(newNode,lis[0]);
     萬變不離其中,跟上面的實現差不多,遇到復雜的html結構,可能就是定位比較麻煩,有人說定位不難,我可以在想要找的li上給定一個id屬性,這樣再怎麼復雜也可以一步定位,但我想問如果要批量操作呢? 如果table有很多行,每一行第都涉及到這樣的操作,那是不是每個li給定一人id ? 是可以,通過循環指也不怎麼難,但我是反對這樣的做法。如果有好的代替方案,我更贊成的是保持干淨的HTML,在我開發的項目中,用於定位無素屬性我是盡可能少給,因為我有足夠靈活的方案尋節點。說到干淨的HTML,這裡說點題外話。怎麼保持HTML的干淨呢?在我開發中,HTML是不會混雜任何 邏輯性的javascript代碼(只有接收後台數據的一些變量,而且這些變量統一放在HTML的最後面,變量太多的話,以組的形式組織),給元素注冊事件也不會把onclick,onblur等事件代碼嵌進html中,查看源碼你不會在HTML中看到任何onclick,onblur等事件代碼。回到這篇文章的主題吧!下面就這三點談談怎麼讓我們的工作變得簡單!
接下來要談的是,怎麼編寫代碼讓我們的事件變得簡單。先不要理會技術層面的事件,假如現在有這麼一個JS類,裡面有若干方法,這些方法足以讓我們完成上面以及比上面更加復雜的事件。這個假設存在的類,因為只有方法名,沒有具體實現,我們把它稱為接口。那些下面要講的是定義接口,當我們覺得接口中的方法能滿足需求,我們再去實現它。(這是比較好的開發流程,先規劃後行動)
  • 實例化一個對象

    開發項目中,因為不止無限層次節點操作這個插件,還有很多,囊括了b2c和b2b常用的插件,比如輪換,分頁,表單驗證,表單元素取設值,批量上傳圖片,事件的重新包裝,事件的批量處理,搜索的自動補全,購物車的ajax操作,以及公共方法的類。所以整個js會有很多類,每個類對應一個js變量,這樣一來就有很多公共的js變量,如果開發的時候不小心在別的地方聲明了一個同名的變量,那麼這個類被消失。為了避免這種情況,我把所有類封裝到一個名為$jkit類中,這樣公共的變量就只有一個了。插件的類變成了局部,我怎麼樣訪問呢?我另外定義一個$CL的類,裡面定義了一些方法,這些方法用於訪問$jkit的類,這樣一來,就算有再多插件,都只會有兩個公共變量$CL和$jkit。$CL負責調用$jkit。比如$CL中的newObj就用於實例化插件對象的方法。newObj有兩個參數,第一個指定要實例化哪個插件,第二個參數用於實例化這個插件時的初始化參數,以數組的形式傳參。 <table id="jkit">
        <tbody>
            <tr>
                <th class="align">Option</th>
                <th>
                    <a href="javascript:void(0);">Status</a>
                </th>
                <th>
                    <a href="javascript:void(0);">Attribute</a>
                </th>
            </tr>
            <tr>
                <td></td>
                <td>
                    <select size="3" name="status">
                        <option value="all">所有</option>
                        <option value="0">下架</option>
                    </select>
                </td>
                <td>
                    <ul>
                        <li id="lior">L</li>
                        <li>XL</li>
                    </ul>
                </td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table> /* 下面代碼實例化了一個無限層次節點操作插件的對象,相當於重新構造了一棵新的對象樹,新的對象樹帶有新的成員方法。就下面的例子,新對象樹引用為table,table也是新樹的根。它的後代對象由第二個參數childs決定 。新樹的根引用是操作其後代對象的入口。重點講解一下第二個參數。childs是一個數組結構,數組的第一個元素為tr,表示為dom根節點root的孩子節點tr重新構造新對象。如果原dom根節點root的中沒有tr,將不會構造對象;如果tr內嵌有tr,不會為內嵌的tr構造節點,也就是說只會為孩子節點構造對象,孩子以下不會理會。第二個元素是td th,為什麼有兩個呢?因為tr下的孩子節點可以是th也可以是td,如果想同時為th td構造新的對象,就要同時寫進去,用空格分開,檢簽的順序不限制。第三個元素為select ul,為什麼這兩個可以寫在一起?因為他們位於同一層次的,相對於根節點,它們都在第三層。只要同一層次的都可以寫在一起。後面的以此類推,數量不限,就是無限層次了。新對象樹的層次結構和原dom樹的層次結構是一一對應的。 */
    root = document.getElementById('category'),

    childs = ['tr', 'td th', 'select ul', 'li option'],

    table = $CL.newObj('maNode', [root, childs]);  
  • 新樹的成員方法,閱讀下面的API時,請心裡緊記兩點,其一,根對象以及後代對象的所有方法都是針對原DOM對象的,比如對新對象調用del,其實質是刪除了對應的原DOM對象;其二,每次調用對象的增刪改時,都會重新構造相應的分枝

    根對象獨有方法
    function map(index1,index2,,,indexN){}
    該方法用於尋找後代節點,table.map(1,1,0)會找到第二行的第二個單元格的第一個對象,也就是select對應的對象。當map只有一個參數,並且該參數為DOM原生對象,那麼該方法返回對應的新對象。
    function index(DOMElement){}
    該方法返回原生DOMElement對象對應的素引,table.index(document.getElementById('lior')),則返回[1,2,0,0],結果是數組形式
    後代對象獨有的方法
    function add(index, html){}
    該方法用於增加兄弟節點,index是相對於調用該方法的對象所處位置的位移,html就是要插要的節點,html可以是符合W3c標淮的任意html字任串
    table.map(2).add(-1,'<tr><td></td><td></td><td></td></tr>'),在第三行前面新增一行(可以同時插入多行)
    table.map(2).add(-2,'<tr><td></td><td></td><td></td></tr>'),在第三行前一行前面新增一行
    table.map(0).add(2,'<tr><td></td><td></td><td></td></tr>'),在第一行後一行前面新增一行
    table.map(1).add(0,'<tr><td></td><td></td><td></td></tr>'),index為0表示在當前行前面新增一行,並把當前行刪除
    table.map(1).add('<tr><td></td><td></td><td></td></tr>'),省略第一個參數,這是比較特別的用法,不用理會由哪個對象用,會在最後一行新增一行
    table.map(1,1).add(1,'<td class="one">新增單元格</td>'),在第二行的第二個單元格之後新增一個單元格,再深入的節點如些類推,只需由map方法來確定節點就可以了
    function del(index){}
    該方法用於刪除兄弟節點,index是相對於調用該方法的對象所處位置的位移
    table.map(1).del(),index省略表示刪除自身,這裡刪除第二行等同於table.map(1).del(0)
    table.map(0).del(2),這裡刪除相對於當前調用對象後面第二行,這裡就是刪除第三行
    table.map(2).del(-2),這裡刪除相對於當前調用對象前面第二行,這裡就是刪除第一行
    table.map(0,1).del([0,-1,1]),如果index是一個數組,那麼就是刪除指定索引的兄弟節點,這時不用理會由哪個對象調用,索引為負數表示從最後計起,-1表示最後一個,這裡刪除第一個,第二個以及最後一個th
    table.map(0,1).del(0,-1),如果有兩個參數,表示刪除指定區間的兄弟節點,這時不用理會由哪個對象調用,索引為負數表示從最後計起,-1表示最後一個,這裡刪除第一個至後一個元素,參數大的可以作為第一個參數,大小順序沒有限制
    function getParent(){}
    獲取調用對象父對象對應的原生DOM對象節點,table.map(0,1).getParent().tagName為tr
    function getHigher(){}
    獲取調用對象的父對象,table.map(0,1).getHigher.getNode().tagName為tr
    根對象以及後代對象都擁有的方法
    function getNode(){}
    獲取調用對象對應的原生DOM對象節點,table.getNode().tagName為table,table.map(0,1).getNode()為th
    function sizeOf(){}
    獲取調用對象子對象的個數,table.sizeOf()為3,表示有三行
    function pos(){}
    獲取調用對象在其所有兄弟節點的所處的位置,table.map(1).pos()為1
    function html(html){}
    獲取調用對象對應原生dom對象的innerHTML,如果有傳參,為其innerHTML屬性賦值(請不要對innerHTML為只讀的對象賦值)
    function attr(html){}
    獲取調用對象對應原生dom對象的innerHTML,如果有傳參,為其對應的屬性賦值(還未實現)
    function before(index,html){}
    往調用對象的指定子對象前面添加節點,index是相對位移
    table.before(1,'<tr><td></td><td></td><td></td></tr>'),第二行前面添加一個行
    table.map(1,2,0).before(-1,'<li style="color:red;">新增li節點</li>'),在最後一個li前面新增一個li(索引為負數表示從最後計起,-1表示最後一個)
    table.before('<tr><td></td><td></td><td></td></tr>'),省略第一個參數表示在第一個子對象前面新增節點
    function append(index,html){}
    往調用對象的指定子對象後面添加節點,index是相對位移
    table.append(1,'<tr><td></td><td></td><td></td></tr>'),第二行後面添加一個行
    table.map(1,2,0).append(-1,'<li style="color:red;">新增li節點</li>'),在最後一個li後面新增一個li(索引為負數表示從最後計起,-1表示最後一個)
    table.append('<tr><td></td><td></td><td></td></tr>'),省略第一個參數表示在第一個子對象後面新增節點
    function replace(index,html){}
    用html生成的節點替代調用對象的指定子對象對應的原生DOM節點,index是相對位移
    table.replace(2,'<tr><td>新增行</td><td></td><td></td></tr>'),新增一行,並用其替換掉第二行
    table.replace(-1,'<tr><td>新增行</td><td></td><td></td></tr>'),新增一行,並用其替換掉最後一行(索引為負數表示從最後計起,-1表示最後一個)
    function clean(index){}
    該方法用於刪除兄弟節點,index是相對於調用該方法的對象所處位置的位移
    table.clean(),index省略表示刪除第一個子對象,這裡刪除第一行等同於table.map(1).del(0)
    table.clean(2),這裡表示刪除第三行
    table.clean(-2),這裡表示刪除最後一行
    table.map(0).clean([0,-1,1]),如果index是一個數組,那麼就是刪除指定索引的子對象,索引為負數表示從最後計起,-1表示最後一個,這裡刪除第一個,第二個以及最後一個th
    table.map(0).clean(0,-1),如果有兩個參數,表示刪除指定區間的子對象,索引為負數表示從最後計起,-1表示最後一個,這裡刪除第一個至後一個元素,參數大的可以作為第一個參數,大小順序沒有限制
    如果新樹的根節點是table,而其子節點是tbody/thead/tfoot,由於我們更多時候不會操作這些節點,而是直接操作tr,所以我做了一個允許略過這些節點的處理。當然你如果想操作tbody也是可以的,你大可以這樣傳參['tbody thead tfoot','tr','td'];只想取其中一個的話可以['tbody','tr','td'];直接取tr的話,可以['tr','td'],這種情況會對tbody/thead/tfoot中的所有tr生成新對象
結束語:回過頭想想,有了這麼一個插件之後,節點操作不就是小菜一碟,之前提到的操作節點的三大煩惱就迎刃而解了吧。而且這個插件與任何邏輯無關,不需要二次加工,拿起來就可以用了,遇到無法滿足需求的時候還可以對其進行擴展。想象有一天開發流程好像是拼圖游戲,開發好的插件經過組合,一個項目就出來,那是多麼美妙的事啊。可能結果並沒有預期的美好,但是可以預料得到,向著這個方向走,事件變得越來越簡單是必然的。因為篇幅太長了,源碼部分會在下次發表文章的時候進行詳細的解講。
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved