遍歷 DOM 注意點
版權宣告:本文為博主原創文章,未經博主允許不得轉載。https://blog.csdn.net/zhangxin09/article/details/86693618
在實現一個清理 HTML 冗餘標籤(word 貼上過來的)功能,最簡單的,莫過於:
// MSWordHtmlCleaners.js https://gist.github.com/ronanguilloux/2915995 cleanPaste : function(html) { // Remove additional MS Word content html = html.replace(/<(\/)*(\\?xml:|meta|link|span|font|del|ins|st1:|[ovwxp]:)((.|\s)*?)>/gi, ''); // Unwanted // tags html = html.replace(/(class|style|type|start)=("(.*?)"|(\w*))/gi, ''); // Unwanted // sttributes html = html.replace(/<style(.*?)style>/gi, '');// Style tags html = html.replace(/<script(.*?)script>/gi, ''); // Script tags html = html.replace(/<!--(.*?)-->/gi, '');// HTML comments return html; },
因為某些正則功能的缺失,網頁的 JS 正則是不能完全匹配對應標籤的。故所以這種通過正則過濾的辦法是有潛在問題的。於是考慮另外一種方法,如老外寫的這個http://booden.net/ContentCleaner.aspx,它是通過 DOM 方法刪除標籤的,——我覺得可行,就拿來改造。遇到的問題不少,記錄如下。
首先,他依賴 jQuery,我希望是原生的,開始我以為修改下問題不大,但後來發現沒那麼容易,首當其衝的是 DOM 遍歷。寫一個遍歷並遞迴 DOM 的程式碼不困難,麻煩在於,當你使用 for 遍歷,集合的總數是固定的(length),而當你刪除了一個元素,等於破壞了這個陣列,陣列下標也亂了。於是改為 while(firstChild) 迴圈的方法,有點連結串列那樣的寫法,總是會出現死迴圈的現象,可能是指標有誤。
苦於沒有辦法快放棄的情況下,忽然想起老外那個程式碼有段函式 removeComments(),想想那正是原生的遍歷方法,它是刪除註釋節點後而繼續迭代的。於是拿來改造用,並得到成功!
後來想想,可能這就是迭代器與 for 的區別,因為集合裡面的元素允許動態變化,用 for 遍歷肯定有問題,這時就適宜用迭代器,諸如 hasNext()、getNextItem() 的 API,儘管會囉嗦點。
在刪除 DOM 元素屬性時,也遇到這種問題。例如下面程式碼,每次只能刪除一個屬性,而不是全部!
var attrs = el.attributes; for (var i = 0; i < attrs.length; i++) { console.log(attrs[i]); el.removeAttribute(attrs[i].name); }
解決方法是如MDN 所說的,
for(var i = attrs.length - 1; i >= 0; i--) { var name = attrs[i].name; if (attributesAllowed[tag] == null || attributesAllowed[tag].indexOf("|" + name.toLowerCase() + "|") == -1){ node.removeAttribute(name); } }
最後,完整的清理 HTML 程式碼如下
// 清理冗餘 HTML cleanHTML(){ // 類似於 白名單 var tagsAllowed = "|h1|h2|h3|p|div|a|b|strong|br|ol|ul|li|pre|img|br|hr|font|"; var attributesAllowed = {}; attributesAllowed["div"] = "|id|class|"; attributesAllowed["a"] = "|id|class|href|name|"; attributesAllowed["img"] = "|src|"; this.everyNode(this.iframeBody, node => { var isDelete = false; if (node.nodeType === 1) { var tag = node.tagName.toLowerCase(); if (tagsAllowed.indexOf("|" + tag + "|") === -1) isDelete = true; if (!isDelete) { // 刪除屬性 var attrs = node.attributes; for(var i = attrs.length - 1; i >= 0; i--) { var name = attrs[i].name; if (attributesAllowed[tag] == null || attributesAllowed[tag].indexOf("|" + name.toLowerCase() + "|") == -1){ node.removeAttribute(name); } } } } else if (node.nodeType === 8) {// 刪除註釋 isDelete = true; } return isDelete; }); }, everyNode (el, fn) { var objChildNode = el.firstChild; while (objChildNode) { if (fn(objChildNode)) { // 返回 true 則刪除 var next = objChildNode.nextSibling; el.removeChild(objChildNode); objChildNode = next; } else { if (objChildNode.nodeType === 1) this.everyNode(objChildNode, fn); objChildNode = objChildNode.nextSibling; } } }