深入理解Javascript动态插入技术

最近有人发现,所有的大型类库已经能够使用div.innerhtml = HTML片段生成节点的元素,然后将其插入到目标元素。这实际上是insertadjacenthtml,但IE的邪恶的innerHTML将这种优势变成了劣势。首先,innerHTML将删除在某些位置的空缺,和见下面的运行框的结果。

复制代码代码如下所示:






IE innerHTML,美丽


在window.onload =函数(){
var div = document.createelement(div);
div.innerhtml =斯塔布美
警报(| + div.innerhtml +| );
var c = div.childnodes;
警报(批生成节点+ c.length);
对于(var i = 0,n = c.length;i < n;i++){
警报(C {我}。节点类型);
如果(C {我}。节点类型= 1){
警报(:::+ C {我}。子。长度);
}
}
}












另一个可恶的一点是,在伊江,以下元素的innerHTML只读:COL,COLGROUP,框架、HTML、头、风格、表、tbody tfoot,tbody,和X。为了收拾他们,Ext做了一个insertintotable.insertintotable是DOM的insertBefore和appendChild添加使用jquery,情况基本相同。但是jQuery是完全依赖于这两种方法,并采用insertadjacenthtml ext。为了提高效率,所有的类库已经以同样的方式使用文档片段。基本过程是提取节点通过div.innerhtml,然后移动到文档的片段,然后插入节点和appendChild在,Firefox,Ext也用createcontextualfra段解析文本直接插入目标位置。很明显,Ext比jQuery快得多。然而,jQuery的插入不仅是一个HTML片段,也是各种节点和jQuery对象。下面是jQuery流程综述。

复制代码代码如下所示:
附加:函数(){
传入的参数对象,对于表单的特殊处理,回调函数来说是正确的。
返回this.dommanip(争吵,真的,功能(元){)
如果(this.nodetype = 1)
this.appendchild(元);
});
},
DomManip:功能(参数、表格、回调){
如果(此{ 0 })是否存在元素节点
var =片段(这{ 0 }。ownerdocument | |这{ 0 })(。createdocumentfragment),
下面是传入的三个参数
脚本= jquery.clean(args(这{ 0 }。ownerdocument | |这{ 0 }),片段),
首先fragment.firstchild;

如果(第一)
对于(var i = 0,L = this.length;i < L;i++)
callback.call(根(这{我},第一),this.length我1 | | > > 0
Fragment.cloneNode(真):片段);

如果(脚本)
jquery.each(脚本、evalscript);
}

返回此;

功能根(元,Cur){
收益表jquery.nodename(元素,表)jquery.nodename(狗,TR)
(elem.getelementsbytagname(把){ 0 } | |
elem.appendchild(elem.ownerdocument.createelement(把))):
元素;
}
}
/ /单元是一个参数对象,语境是一个文档对象,和片段是空的文档片段
清洁:功能(单元、语境、片段){
背景背景文件| |;

context.createelement失败在伊江 / /!一个错误而返回typeof'object
如果(typeof context.createelement =未定义)
确保上下文文档对象
背景背景背景context.ownerdocument { 0 } { 0 }。ownerdocument文件| | | |;

如果一个字符串传递了,那么它就是一个标记。
刚做的createElement并跳过/休息
如果只有一个文档对象标签,例如
也许我们可能在外面。
然后 / /直接到里面的名取元素,document.createelement(div)创建后,把返回的数组
如果(片段elems.length typeof elems { 0 } 1!)
VaR的比赛= / / ^美元。exec(elems { 0 });
如果(匹配)
返回{ context.createelement(匹配{ 1 })};
}
使用DIV / innerHTML创建节点
VaR ret = { },{ } =脚本,div = context.createelement(div);
如果我们将其添加到$(this)中,则追加()表1
/ / jquery.each横贯aguments对象,callback.call(价值,我值)根据其第四种支链(没有参数,长度)。
的jquery.each(单元、功能(我、元){ / /我是指数,和元素是在议论对象元素
如果(typeof elem =数)
元素+ =;

如果(!元)
返回;

将HTML字符串转换成DOM节点
如果(typeof elem =字符串){
在所有浏览器中修复XHTML样式标签
elem = elem.replace( / / / G(} *),功能(所有的,前面,标签){
返回(tag.match / ^(简称| BR | Col | IMG |输入|链接|元|参数| HR |地区|嵌入)$ /我)
所有:
前+ ;
});

否则返回 / /装饰的空白,不会像预期的那样工作
var标签= elem.replace( / ^ + /,),Substring(0, 10)ToLowerCase();

VaR包=
OPTGROUP / /期权或
Tags.indexOf(<选择)
{ 1,

Tags.indexOf(<腿)
{ 1,

Tags.match( / ^ <(thead | tbody | tFoot | COLG |帽) /)
{ 1,

Tags.indexOf(< TR )
{ 2,

匹配的/以上
(!Tags.indexOf(< TD)| |!Tags.indexOf(<日))
{ 3,

Tags.indexOf(<西)
{ 2,

无法序列化和标签通常 / / IE
jquery.support.htmlserialize / /链接元素用于创建!
{ 1,

{ 0,

到HTML并返回,然后剥离额外的包装器
div.innerhtml =包{ 1 } +元素+包{ 2 }; / / +如表1 +

到正确的深度
同时(包装{ 0 })
div.lastchild div =;

IE / /自动插入tbody,如果我们使用$()来创建一个HTML片段,它应该返回
伊江将返回。
如果(!jquery支持。TBODY){

是一个字符串,可能有错误。
无功hasbody = / / < tbody i.test(元),
tbody =!Tags.indexOf(<表)!hasbody
Div.firstChild div.firstChild.childNodes:

是空的或的/字符串
包{ 1 } = =hasbody !
Div.childNodes:
{ };

对于(var j = tbody.length - 1;J > = 0;——J)
如果自动插入内部肯定不是内容
如果(jquery.nodename(TBODY { J },把)!TBODY { J }。子长度)。
TBODY { }(J。parentnode.removechild TBODY { j));

}

IE浏览器完全杀死前导空格时/使用innerHTML
如果(!jquery.support.leadingwhitespace / / ^测试(单元))。
div.insertbefore(context.createtextnode(elem.match( / ^ * /){ 0 }),div.firstchild);
所有节点都是纯数组
jquery.makearray elem =(div.childnodes);
}

如果(元素。节点类型)
Ret.push(元);
其他的
全和 / /两个数组合并方法将在IE消失对象元素参数元素
ret = jquery.merge(RET,元素);

});

如果(片段){
对于(var i = 0;RET i };i + +){
/ /如果子第一层有一个脚本元素节点,我们使用脚本来收集起来,为后面globaleval动态执行
如果(jquery.nodename(RET {我},脚本)(RET {我},{我}类型。类型。toLowerCase)= | |!(文本){
scripts.push(RET {我},{我} parentNode RET。parentnode.removechild(RET {我}):RET {我});
{人}
在每个层中遍历节点,收集脚本元素节点
如果(RET {我}。节点类型= 1)
ret.splice.apply(RET,{我+ 1, 0 } .concat(jquery.makearray(RET {我}。getElementsByTagName(脚本))))))
fragment.appendchild(RET {我});
}
}

返回脚本;由于动态插入引入了三个参数,所以我们返回这里
}

返回页首;
},





眼泪太复杂了!然而,jQuery的实现并不十分聪明。它将所有的对象插入到干净的节点,然后把它们放到一个文档片段,并将其与appendChild和insertBefore。除了火狐,其他的浏览器都支持insertAdjactentXXX家族的今天,应该充分利用这些本地API。以下是由外部使用insertadjactenthtml等方法实现了domhelper方法,和由官方给出的数据网络:



这个数据有点旧了,但3.03已经在伊江表解决批评插入内容(表,tbody,TR和innerHTML是只读的,insertadjactenthtml,pastehtml和其他方法无法修改其内容,然后,在早期版本的Ext滑铁卢使用DOM方法速度慢、标准受到了这里)。可以看出,insertadjactenthtml和IE6文件片段的组合,插入节点的速度也有了惊人的推广Firefox。在此基础上,Ext形成了四分法:在,都insertfirst和附加,,对应于jQuery的前,后,前置和后置的分别。然而,jQuery也巧妙地交换这些方法呼叫者和传入的参数ERS,导出都在,,prependto和appendto。但在任何情况下,jQuery的实现是不一样的。下面是一个Firefox版本的insertadjactentxxx家庭:

复制代码代码如下所示:
(函数(){())
如果('htmlelement'in这){
如果('insertadjacenthtml'in HtmlElement。原型){
返回
}
{人}
返回
}

函数插入(w,n){
开关(w.touppercase()){
case'beforeend:
this.appendchild(N)
打破
case'beforebegin:
this.parentnode.insertbefore(n,这个)
打破
case'afterbegin:
this.insertbefore(N,这样子{ 0 })
打破
case'afterend:
this.parentnode.insertbefore(n,这个兄弟)
打破
}
}

功能insertadjacenttext(W,T){
Insert.call (this, W, document.createTextNode (t || '))
}

功能insertadjacenthtml(W,H){
VAR r = document.createrange()
R.selectNode(本)
(这insert.call,W,r.createcontextualfragment(H))
}

功能insertadjacentelement(W,N){
(这insert.call,W,N)
返回N
}

htmlelement.prototype.insertadjacenttext = insertadjacenttext
htmlelement.prototype.insertadjacenthtml = insertadjacenthtml
htmlelement.prototype.insertadjacentelement = insertadjacentelement
})



我们可以用它来设计更快更合理的动态插入方法:

复制代码代码如下所示:
四/四插入的方法,插入对应的位置insertadjactenthtml,使用jQuery的名字
可以是一个字符串,多种节点,或者一个DOM对象(一个类数组对象,很容易进行链式操作!)
jQuery代码比实现简单美观!
附加:函数({){
返回dom.batch(这个功能(EL){)
Dom.insert(EL的东西,beforeend );
});
},
开头:功能(东西){
返回dom.batch(这个功能(EL){)
Dom.insert(EL的东西,afterbegin );
});
},
以前:函数({){
返回dom.batch(这个功能(EL){)
Dom.insert(EL的东西,beforebegin );
});
},
后:函数({){
返回dom.batch(这个功能(EL){)
Dom.insert(EL的东西,afterend );
});
}



他们都叫两个静态方法,批量插入,因为DOM对象是对象的数组,我已经实现了几个重要的迭代器,如foreach,地图,和过滤,如jQuery DOM对象包含多个DOM元素,我们可以用foreach遍历执行回调方法。

复制代码代码如下所示:
批处理:函数(通道,回调){
els.foreach(回调);
返回总线/链操作
},


插入方法执行相应的dommanip jQuery功能(道场是地方法),但插入方法处理的每一个元素节点的时间,不像jQuery处理一组元素节点的集群处理已经从上面的批处理方法分离。

复制代码代码如下所示:
插入:函数(EL,东东,在哪里){
定义两个全局事件,提供内部方法调用
var doc = el.ownerdocument dom.doc | |,
片段= doc.createdocumentfragment();
如果(版本号){如果是DOM对象,则将其放在元素节点中以记录片段。
stuff.foreach(功能(EL){)
fragment.appendchild(EL);
})
碎片=碎片;
}
对于Firefox和IE /元素调用
Dom。_insertadjacentelement =功能(EL,节点,哪里){
开关({){
case'beforebegin:
el.parentnode.insertbefore(节点,EL)
打破;
case'afterbegin:
el.insertbefore(节点,埃尔。第一个孩子);
打破;
case'beforeend:
el.appendchild(节点);
打破;
case'afterend:
如果(EL。nextSibling)el.parentnode.insertbefore(节点,埃尔。兄弟);
其他el.parentnode.appendchild(节点);
打破;
}
};
Firefox调用
Dom。_insertadjacenthtml =功能(EL,htmlstr,哪里){
变量范围= doc.createrange();
开关({){
案例beforebegin : / /前
Range.setStartBefore(EL);
打破;
案例afterbegin : / /后
range.selectnodecontents(EL);
Range.collapse(真的);
打破;
案例beforeend : / /附加
range.selectnodecontents(EL);
Range.collapse(假);
打破;
案例afterend : / /前置
Range.setStartAfter(EL);
打破;
}
无功parsedhtml = range.createcontextualfragment(htmlstr);
Dom。_insertadjacentelement(EL,parsedhtml,哪里);
};
以下内容 / / innerHTML是只读的在伊江,叫insertadjacentelement插入错误将
COLGROUP / /胶原、框架、HTML、头、风格、标题、表格、tbody tfoot,THEAD和TR;
Dom。_insertadjacentiefix =功能(EL,htmlstr,哪里){
无功parsedhtml = dom.parsehtml(htmlstr,片段);
Dom。_insertadjacentelement(EL,parsedhtml,哪里)
};
如果节点是复制
= stuff.nodetype stuff.clonenode(真):东西;
如果(EL。insertadjacenthtml){ / / IE、Chrome、Opera和Safari都实施了,insertAdjactentXXX家族
尝试适合于Opera、Safari、Chrome和IE
埃尔{ 'insertadjacent +(东西nodetype'element:'html ')}(在那里,东西);
} catch(e){
/ / IE的一些元素可以通过调用insertadjacentxxx犯了一个错误,所以使用这个补丁
Dom。_insertadjacentiefix(EL的东西,在那里);
}
其他{ }
Firefox
DOM { '_insertadjacent +(东西nodetype'element:'html ')}(EL的东西,在那里);
}
}



在Firefox插件的实现,插入方法使用W3C DOM对象的一些罕见的方法,它可以在Firefox的官方网络审查。以下是将字符串转换为一个节点,一个伟大的方式,以innerhtml.prototype.js叫做_getcontentfromanonymouselement,但存在许多问题。道场是_todom,element.properties.html jQuery的MooTools,干净。Ext没有这个东西,它只支持通过HTML片段的insertadjacenthtml方法,不支持的输入元素节点的insertadjacentelement。但有时候,我们需要将文本节点(不包在元素节点),然后我们需要使用文档片段作为容器,和插入方法。

复制代码代码如下所示:
ParseHTML:功能(htmlstr,片段){
var div = dom.doc.createelement(div),
resingletag = / / / ^美元; /,一个单一的标签,如
htmlstr + =;
如果(resingletag.test(htmlstr){ / /)如果str是一个单一的标签
返回{ dom.doc.createelement(regexp。1美元)}
}
无功tagwrap = { {
选项:{选择},
OPTGROUP:{选择},
TBODY:{表},
螺纹:{表},
Tfoot:{表},

Td:{表
{表
传说:{中},
说明:{表},
默认:{表},
Col:{表
李:{UL},
链接:{div}
};
对于(var参数在tagwrap){
VaR TW = tagwrap {参数};
开关(参数){
案例选项:tw.pre =;打破;
案例链接:tw.pre = 'fixbug打破的;
默认值:tw.pre = ;
}
tw.post = ;
}
无功remultitag = /李
比赛= htmlstr.match(remultitag),
标签=比赛(比赛{ 1 }。toLowerCase):<李; / /分析,李
如果(比赛tagwrap {标签}){
VaR套= tagwrap {标签};
div.innerhtml = wrap.pre + htmlstr + wrap.post;
n = wrap.length;
当(= = 0)返回时,我们添加了内容
div.lastchild div =;
其他{ }
div.innerhtml = htmlstr;
}
IE / /自动插入tbody,如果我们使用dom.parsehtml('')的HTML片段,它应该返回
伊江将返回。
这是标准的浏览器,返回div.children.length返回1,IE会返回2
如果(dom.feature.autoinserttbody!tagwrap { }){标签!
无功owninsert = tagwrap {标签}。加入()。IndexOf(把)!= 1,我们插入
tbody = div.getelementsbytagname(把),
autoinsert = tbody.length > 0; / / IE插件
如果(!OwnInsert autoInsert){
对于(var i = 0,n = tbody.length;i < n;i++){
如果(我把{ }。子。长!)如果自动插入,则必须没有内容。
我把{ }。parentnode.removechild(TBODY {我});
}
}
}
如果(dom.feature.autoremoveblank / / ^。试验(htmlstr))
div.insertbefore(dom.doc.createtextnode(htmlstr.match( / ^ * /){ 0 }),div.firstchild);
如果(片段){
VaR firstChild;
而((firstChild = div.firstchild){ / / DIV)将被转移到共同文件的碎片!
fragment.appendchild(第一个孩子);
}
返回的片段;
}
返回div.children;
}



好了,基本上是,运行速度比jQuery代码是美丽的,至少不作为乱作为,jQuery也有四的反转。以下是jQuery的实现:

复制代码代码如下所示:
jquery.each({
appendto:添加
prependto:在
insertBefore:在
都是:后
简单的:用
}函数(名称,原始){
函数名选择器(HTML,元素节点,jQuery对象)
var = { },插入= jQuery(选择器);将插入到jQuery对象中
对于(var i = 0,L = insert.length;i < L;i++){
VaR elems =(我> 0 this.clone(真正的):这个)得到();
jquery FN {原}。申请(jQuery(插入{我}),elems); / /电话四已插入的方法
ret = ret.concat(单元);
}
返回this.pushstack(RET,名称,选择); / /熄灭因为有分离操作无链码,需要实现
};
});



我的实现:

复制代码代码如下所示:
dom.each({
appendto:'append,
prependto:'prepend,
以前在,
InsertAfter:我
}函数(方法,名称){
Dom:原型{函数=(函数){
返回DOM(物料){方法}(此);
};
});



给出一般代码,我们可以每个人拿他需要的东西。