javascript 动态插入技术(2)

作者:司徒正美 来源:Ruby's Louvre 时间:2009-12-14 20:50:00 

下面是Ext利用insertAdjactentHTML等方法实现的DomHelper方法,官网给出的数据:

Insertion MethodIE7 beta 2IE6FF 1.5Opera 9
DOM.7301.35 .420.280
HTML Fragments.360.380.400.260
Template.320.335
.385
.220
Compiled Template.295.300.350.210

数据来源:《Tutorial:使用DomHelper 创建元素的DOM、HTML片断和模版》

这数据有点老了,而且最新3.03早就解决了在IE table插入内容的诟病(table,tbody,tr等的innerHTML都是只读,insertAdjactentHTML,pasteHTML等方法都无法修改其内容,要用又慢又标准的DOM方法才行,Ext的早期版本就在这里遭遇滑铁卢了)。可以看出,结合insertAdjactentHTML与文档碎片后,IE6插入节点的速度也得到难以置信的提升,直逼火狐。基于它,Ext开发了四个分支方法insertBeforeinsertAfterinsertFirstappend,分别对应jQuery的beforeafterprependappend。不过,jQuery还把这几个方法巧妙地调换了调用者与传入参数,衍生出insertBeforeinsertAfterprependToappendTo这几个方法。但不管怎么说,jQuery这样一刀切的做法实现令人不敢苛同。下面是在火狐中实现insertAdjactentXXX家族的一个版本:





(function() { 
if ('HTMLElement' in this) { 
if('insertAdjacentHTML' in HTMLElement.prototype) { 
return

} else { 
return

function insert(w, n) { 
switch(w.toUpperCase()) { 
case 'BEFOREEND' : 
this.appendChild(n) 
break
case 'BEFOREBEGIN' : 
this.parentNode.insertBefore(n, this) 
break
case 'AFTERBEGIN' : 
this.insertBefore(n, this.childNodes[0]) 
break
case 'AFTEREND' : 
this.parentNode.insertBefore(n, this.nextSibling) 
break


function insertAdjacentText(w, t) { 
insert.call(this, w, document.createTextNode(t || '')) 

 
function insertAdjacentHTML(w, h) { 
var r = document.createRange() 
r.selectNode(this) 
insert.call(this, w, r.createContextualFragment(h)) 

 
function insertAdjacentElement(w, n) { 
insert.call(this, w, n) 
return n 

 
HTMLElement.prototype.insertAdjacentText = insertAdjacentText 
HTMLElement.prototype.insertAdjacentHTML = insertAdjacentHTML 
HTMLElement.prototype.insertAdjacentElement = insertAdjacentElement 
})()

我们可以利用它设计出更快更合理的动态插入方法。下面是我的一些实现:

//四个插入方法,对应insertAdjactentHTML的四个插入位置,名字就套用jQuery的 
//stuff可以为字符串,各种节点或dom对象(一个类数组对象,便于链式操作!) 
//代码比jQuery的实现简洁漂亮吧! 
append:function(stuff){ 
return  dom.batch(this,function(el){ 
dom.insert(el,stuff,"beforeEnd"); 
}); 
}, 
prepend:function(stuff){ 
return  dom.batch(this,function(el){ 
dom.insert(el,stuff,"afterBegin"); 
}); 
}, 
before:function(stuff){ 
return  dom.batch(this,function(el){ 
dom.insert(el,stuff,"beforeBegin"); 
}); 
}, 
after:function(stuff){ 
return  dom.batch(this,function(el){ 
dom.insert(el,stuff,"afterEnd"); 
}); 
}

它们里面都是调用了两个静态方法,batch与insert。由于dom对象是类数组对象,我仿效jQuery那样为它实现了几个重要迭代器,forEach、map与filter等。一个dom对象包含复数个DOM元素,我们就可以用forEach遍历它们,执行其中的回调方法。

batch:function(els,callback){ 
els.forEach(callback); 
return els;//链式操作 
},

insert方法执行jQuery的domManip方法相应的机能(dojo则为place方法),但insert方法每次处理一个元素节点,不像jQuery那样处理一组元素节点。群集处理已经由上面batch方法分离出去了。

insert : function(el,stuff,where){ 
//定义两个全局的东西,提供内部方法调用 
var doc = el.ownerDocument || dom.doc, 
fragment = doc.createDocumentFragment(); 
if(stuff.version){//如果是dom对象,则把它里面的元素节点移到文档碎片中 
stuff.forEach(function(el){ 
fragment.appendChild(el); 
}) 
stuff = fragment; 

//供火狐与IE部分元素调用 
dom._insertAdjacentElement = function(el,node,where){ 
switch (where){ 
case 'beforeBegin': 
el.parentNode.insertBefore(node,el) 
break; 
case 'afterBegin': 
el.insertBefore(node,el.firstChild); 
break; 
case 'beforeEnd': 
el.appendChild(node); 
break; 
case 'afterEnd': 
if (el.nextSibling) el.parentNode.insertBefore(node,el.nextSibling); 
else el.parentNode.appendChild(node); 
break; 

}; 
//供火狐调用 
dom._insertAdjacentHTML = function(el,htmlStr,where){ 
var range = doc.createRange(); 
switch (where) { 
case "beforeBegin"://before 
range.setStartBefore(el); 
break; 
case "afterBegin"://after 
range.selectNodeContents(el); 
range.collapse(true); 
break; 
case "beforeEnd"://append 
range.selectNodeContents(el); 
range.collapse(false); 
break; 
case "afterEnd"://prepend 
range.setStartAfter(el); 
break; 

var parsedHTML = range.createContextualFragment(htmlStr); 
dom._insertAdjacentElement(el,parsedHTML,where); 
}; 
//以下元素的innerHTML在IE中是只读的,调用insertAdjacentElement进行插入就会出错 
// col, colgroup, frameset, html, head, style, title,table, tbody, tfoot, thead, 与tr; 
dom._insertAdjacentIEFix = function(el,htmlStr,where){ 
var parsedHTML = dom.parseHTML(htmlStr,fragment); 
dom._insertAdjacentElement(el,parsedHTML,where) 
}; 
//如果是节点则复制一份 
stuff = stuff.nodeType ?  stuff.cloneNode(true) : stuff; 
if (el.insertAdjacentHTML) {//ie,chrome,opera,safari都已实现insertAdjactentXXX家族 
try{//适合用于opera,safari,chrome与IE 
el['insertAdjacent'+ (stuff.nodeType ? 'Element':'HTML')](where,stuff); 
}catch(e){ 
//IE的某些元素调用insertAdjacentXXX可能出错,因此使用此补丁 
dom._insertAdjacentIEFix(el,stuff,where); 
}      
}else{ 
//火狐专用 
dom['_insertAdjacent'+ (stuff.nodeType ? 'Element':'HTML')](el,stuff,where); 

}

insert方法在实现火狐插入操作中,使用了W3C DOM Range对象的一些罕见方法,具体可到火狐官网查看。下面实现把字符串转换为节点,利用innerHTML这个伟大的方法。Prototype.js称之为_getContentFromAnonymousElement,但有许多问题,dojo称之为_toDom,mootools的Element.Properties.html,jQuery的clean。Ext没有这东西,它只支持传入HTML片断的insertAdjacentHTML方法,不支持传入元素节点的insertAdjacentElement。但有时,我们需要插入文本节点(并不包裹于元素节点之中),这时我们就需要用文档碎片做容器了,insert方法出场了。

parseHTML : function(htmlStr, fragment){ 
var div = dom.doc.createElement("div"), 
reSingleTag =  /^<(\w+)\s*\/?>$/;//匹配单个标签,如<li> 
htmlStr += ''; 
if(reSingleTag.test(htmlStr)){//如果str为单个标签 
return  [dom.doc.createElement(RegExp.$1)] 

var tagWrap = { 
option: ["select"], 
optgroup: ["select"], 
tbody: ["table"], 
thead: ["table"], 
tfoot: ["table"], 
tr: ["table", "tbody"], 
td: ["table", "tbody", "tr"], 
th: ["table", "thead", "tr"], 
legend: ["fieldset"], 
caption: ["table"], 
colgroup: ["table"], 
col: ["table", "colgroup"], 
li: ["ul"], 
link:["div"] 
}; 
for(var param in tagWrap){ 
var tw = tagWrap[param]; 
switch (param) { 
case "option":tw.pre  = '<select multiple="multiple">'; break; 
case "link": tw.pre  = 'fixbug<div>';  break; 
default : tw.pre  =   "<" + tw.join("><") + ">"; 

tw.post = "</" + tw.reverse().join("></") + ">"; 

var reMultiTag = /<\s*([\w\:]+)/,//匹配一对标签或多个标签,如<li></li>,li 
match = htmlStr.match(reMultiTag), 
tag = match ? match[1].toLowerCase() : "";//解析为<li,li 
if(match && tagWrap[tag]){ 
var wrap = tagWrap[tag]; 
div.innerHTML = wrap.pre + htmlStr + wrap.post; 
n = wrap.length; 
while(--n >= 0)//返回我们已经添加的内容 
div = div.lastChild; 
}else{ 
div.innerHTML = htmlStr; 

//处理IE自动插入tbody,如我们使用dom.parseHTML('<thead></thead>')转换HTML片断,它应该返回 
//'<thead></thead>',而IE会返回'<thead></thead><tbody></tbody>' 
//亦即,在标准浏览器中return div.children.length会返回1,IE会返回2 
if(dom.feature.autoInsertTbody && !!tagWrap[tag]){ 
var ownInsert = tagWrap[tag].join('').indexOf("tbody") !== -1,//我们插入的 
tbody = div.getElementsByTagName("tbody"), 
autoInsert = tbody.length > 0;//IE插入的 
if(!ownInsert && autoInsert){ 
for(var i=0,n=tbody.length;i<n;i++){ 
if(!tbody[i].childNodes.length )//如果是自动插入的里面肯定没有内容 
tbody[i].parentNode.removeChild( tbody[i] ); 



if (dom.feature.autoRemoveBlank && /^\s/.test(htmlStr) ) 
div.insertBefore( dom.doc.createTextNode(htmlStr.match(/^\s*/)[0] ), div.firstChild ); 
if (fragment) { 
var firstChild; 
while((firstChild = div.firstChild)){ // 将div上的节点转移到文档碎片上! 
fragment.appendChild(firstChild); 

return fragment; 

return div.children; 
}

嘛,基本上就是这样,运行起来比jQuery快许多,代码实现也算优美,至少没有像jQuery那样乱成一团。jQuery还有四个反转方法。下面是jQuery的实现: 

jQuery.each({ 
appendTo: "append", 
prependTo: "prepend", 
insertBefore: "before", 
insertAfter: "after", 
replaceAll: "replaceWith"
}, function(name, original){ 
jQuery.fn[ name ] = function( selector ) {//插入物(html,元素节点,jQuery对象) 
var ret = [], insert = jQuery( selector );//将插入转变为jQuery对象 
for ( var i = 0, l = insert.length; i < l; i++ ) { 
var elems = (i > 0 ? this.clone(true) : this).get(); 
jQuery.fn[ original ].apply( jQuery(insert[i]), elems );//调用四个已实现的插入方法 
ret = ret.concat( elems ); 

return this.pushStack( ret, name, selector );//由于没有把链式操作的代码分离出去,需要自行实现 
}; 
});

我的实现:

dom.each({ 
appendTo: 'append', 
prependTo: 'prepend', 
insertBefore: 'before', 
insertAfter: 'after'
},function(method,name){ 
dom.prototype[name] = function(stuff){ 
return dom(stuff)[method](this); 
}; 
});

大致的代码都给出,大家可以各取所需。

标签:动态,innerHTML,技术,JavaScript
0
投稿

猜你喜欢

  • 聊聊Python中关于a=[[]]*3的反思

    2021-09-08 05:12:46
  • PHP设计模式中的命令模式

    2023-05-27 21:13:43
  • Go 实现HTTP中间人代理的操作

    2024-04-23 09:40:15
  • Dreamweaver MX 2004 试用心得

    2010-03-25 12:21:00
  • python的数据与matlab互通问题:SciPy

    2022-05-16 04:53:48
  • vue3.0如何使用computed来获取vuex里数据

    2024-04-28 09:24:20
  • PHP遍历目录函数opendir()、readdir()、closedir()、rewinddir()总结

    2024-05-03 15:53:21
  • 详解python中读取和查看图片的6种方法

    2023-10-10 16:40:48
  • pytest自动化测试fixture的作用域实例化顺序及可用性

    2022-06-22 14:46:30
  • MySQL特定表全量、增量数据同步到消息队列-解决方案

    2024-01-24 04:36:47
  • python使用hdfs3模块对hdfs进行操作详解

    2023-10-30 07:12:03
  • python对批量WAV音频进行等长分割的方法实现

    2023-01-29 18:21:21
  • 各种语言常用的一句话判断代码

    2022-10-10 17:38:07
  • Python中的Numpy 面向数组编程常见操作

    2021-08-21 20:31:32
  • PyQt5 QTableView设置某一列不可编辑的方法

    2023-12-04 06:34:45
  • Effective Python bytes 与 str 的区别

    2021-07-11 16:52:13
  • 跨浏览器使用剪贴板

    2008-09-27 13:26:00
  • 前端面试之输入npm run后执行原理

    2024-05-05 09:21:55
  • Golang 实现Redis 协议解析器的解决方案

    2024-02-19 20:09:36
  • Python数学建模学习模拟退火算法多变量函数优化示例解析

    2021-05-07 09:36:37
  • asp之家 网络编程 m.aspxhome.com