2.3.4 生成 DOM 元素
jQuery.fn.init() 构造函数能够构建 jQuery 对象,并把匹配的 DOM 元素存储在 jQuery 对象内部集合中。jQuery.fn.init() 构造函数可以接收单个的 DOM 元素,也可以接收 DOM 集合。如果接收的是字符串型 ID 值,则直接在文档中查找对应的 DOM 元素,并把它传递给 jQuery 对象;如果接收的是字符串型 HTML 片段,则需要把这个字符串片段生成 DOM 元素。下面我们将重点分析 jQuery 是如何把 HTML 片段生成 DOM 元素的。
在2.3.3节中,我们可以看到 jQuery.fn.init() 构造器通过 jQuery.clean([match[1], context]); 语句实现把 HTML 片段生成 DOM 元素,jQuery.clean() 是一个公共函数。源代码及其注释如下所示。
jQuery.clean() 包含三个参数,其中 elems 和 context 可以支持多种形式的值。Elems 参数可以为数组、类数组、对象结构的形式。数组元素和对象属性可以混合使用。
对于数字类型参数,则会被转换为字符串型,除了字符串型外,其他的都放入返回的数组中,当然对于集合形式只需要读取集合中每个元素即可。
对于字符串型参数,则把它转换成 DOM 元素,再存入返回的数组中。转换的方式是,把 HTML 字符串片段赋值给创建的 div 元素的 innerHTML ,这样就可以把 HTML 字符串片段挂到 DOM 文档树中,从而实现把字符串转换成 DOM 元素。
在转换过程中,应该考虑 HTML 语法约定,因为标签嵌套是有严格限制的,例如,<td> 必须存在 <tr> 中。因此在执行转换前,还应该对 HTML 字符串进行预处理,即修正 HTML 标签不规范的用法,这也是 jQuery.clean() 函数的一个重要工作。
[html]view
plaincopy
- <scripttype="text/javascript">
- (function(){
- var
- window=this,
- jQuery=window.jQuery=window.$=function(selector,context){
- returnnewjQuery.fn.init(selector,context);
- },
- quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/;
- jQuery.fn=jQuery.prototype={
- init:function(selector,context){
- selector=selector||document;
- if(typeofselector=="string"){
- varmatch=quickExpr.exec(selector);
- if(match&&(match[1]||!context)){
- //第二种情况,处理HTML字符串,类似$(html)->$(array)
- if(match[1]){
- selector=jQuery.clean([match[1]],context);
- }
- }
- }
- }
- };
- jQuery.fn.init.prototype=jQuery.fn;
- //jQuery功能扩展函数
- jQuery.extend=jQuery.fn.extend=function(obj){
- for(varpropinobj){
- this[prop]=obj[prop];
- }
- returnthis;
- };
- //公共函数扩展
- jQuery.extend({
- //参数说明:object表示jQuery对象,callback表示回调函数,args回调函数的参数数组
- each:function(object,callback,args){
- varname,i=0,length=object.length;
- if(args){//如果存在回调函数的参数数组
- if(length===undefined){//如果object不是jQuery对象
- for(nameinobject){//遍历object的属性
- if(callback.apply(object[name],args)===false)
- //在对象上调用回调函数
- break;//如果回调函数返回值为false,则跳出循环
- }
- }else{
- for(;i<length;)//遍历jQuery对象数组
- if(callback.apply(object[i++],args)===false)
- //在对象上调用回调函数
- break;//如果回调函数返回值为false,则跳出循环
- }
- }else{
- if(length===undefined){//如果object不是jQuery对象
- for(nameinobject)//遍历object的属性
- if(callback.call(object[name],name,object[name])===false)
- break;//如果回调函数返回值为false,则跳出循环
- }else{//如果object是jQuery对象
- for(varvalue=object[0];//遍历jQuery对象数组
- i<length&&callback.call(value,i,value)!==false;value=object[++i]){}
- }
- }
- },
- //把HTML字符串片段转换成DOM元素
- //参数说明:
- //elems参数表示多个HTML字符串片段的数据
- //context参数表示上下文
- //fragment参数表示框架对象
- clean:function(elems,context,fragment){
- context=context||document;//默认的上下文是document
- //在IE中!context.createElement是错误用法,因为它返回的是对象类型,而不是逻辑值,
- //故通过返回类型进行判断
- if(typeofcontext.createElement=="undefined")
- //支持context为jQuery对象,并获取第一个元素
- context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;
- //如果仅匹配一个标签,且没有指定框架参数,则直接创建DOM元素,并跳过后面的解析
- if(!fragment&&elems.length===1&&typeofelems[0]==="string"){
- varmatch=/^<(\w+)\s*\/?>$/.exec(elems[0]);
- if(match)
- return[context.createElement(match[1])];
- }
- varret=[],scripts=[],div=context.createElement("div");
- //匹配每一个HTML字符串片段,并为每一个片段执行回调函数
- jQuery.each(elems,function(i,elem){
- if(typeofelem==="number")//把数值转换为字符串的高效方法
- elem+='';
- if(!elem)//如果不存在元素,则返回,或者为''、undefined、false等时也返回
- return;
- //HTML字符串转换为DOM节点
- if(typeofelem==="string"){
- //统一转换为XHTML严谨型文档的标签形式,如<div/>的形式修改为<div></div>
- //但是对于(abbr|br|col|img|input|link|meta|param|hr|area|embed)不修改
- //front=(<(\w+)[^>]*?)--非贪婪的重复
- elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){
- //all--匹配项
- //front--第一个捕获组
- //tag--第二个捕获组
- returntag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?
- all:
- front+"></"+tag+">";
- });
- //清除空格,否则indexof可能会出现不能正常工作,elem.replace(/^\s+/,"")清除左侧空格
- vartags=elem.replace(/^\s+/,"").substring(0,10).toLowerCase();
- //有些标签必须是有一些约束的,如<option>必须在<select></select>中间
- //下面代码大部分是对<table>中的子元素进行修正。数组中第一个元素为深度
- varwrap=
- //约束<option>,<opt在开始位置(index=0)就返回&&运算符后面的数组(!0返回true)
- !tags.indexOf("<opt")&&
- [1,"<selectmultiple='multiple'>","</select>"]||
- //<leg必须在<fieldset>内部
- !tags.indexOf("<leg")&&
- [1,"<fieldset>","</fieldset>"]||
- //thead|tbody|tfoot|colg|cap必须在<table>内部
- tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&
- [1,"<table>","</table>"]||
- //tr在<tbody中间
- !tags.indexOf("<tr")&&
- [2,"<table><tbody>","</tbody></table>"]||
- //td在tr中间
- (!tags.indexOf("<td")||!tags.indexOf("<th"))&&
- [3,"<table><tbody><tr>","</tr></tbody></table>"]||
- //col在<colgroup>中间
- !tags.indexOf("<col")&&
- [2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||
- //IE中link,script不能串行化
- //IEcan'tserialize<link>and<script>tagsnormally
- !jQuery.support.htmlSerialize&&
- [1,"div<div>","</div>"]||
- //默认不修正
- [0,"",""];
- //包裹HTML之后,采用innerHTML转换成DOM
- div.innerHTML=wrap[1]+elem+wrap[2];
- //转到正确的深度,对于[1,"<table>","</table>"],div=<table>
- while(wrap[0]--)
- div=div.lastChild;
- //fragments去掉IE对<table>自动插入的<tbody>
- //if(!jQuery.support.tbody){
- //第一种情况:tags以<table>开头但没有<tbody>。在IE生成的元素中可能自动加<tbody>
- //第二种情况:thead|tbody|tfoot|colg|cap为tags,那wrap[1]=="<table>"
- //TODO
- //}
- //使用innerHTML,IE会去掉开头的空格节点,因此加上去掉的空格节点
- //TODO
- //elem从字符转换成了数组
- //TODO
- }
- //如果是DOM元素,则推入数组,否则就合并数组
- /*if(elem.nodeType)
- ret.push(elem);
- else
- ret=jQuery.merge(ret,elem);*/
- });
- //如果指定了第3个参数,即框架对象,则附加到框架对象上
- //这段是新增加的,用来处理js代码,同时也取消了form的处理
- if(fragment){
- //TODO
- }
- //返回DOM元素集合
- returnret;
- }
- });
- })();
- window.onload=function(){
- varcontext=document.getElementById("wrap");
- //测试代码
- $("<option/><div/>",context);
- };
- </script>
上面这段代码实际上最后访问的是一个名为 ret 的数组,数组中的元素变为 DOM 元素的对象,而它的 innerHTML 正好就是刚才的 HTML 字符串。