第二章 jQuery技术解密 (四)

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() 函数的一个重要工作。

<script type="text/javascript">
(function(){
var 
	window = this,
	jQuery = window.jQuery = window.$ = function(selector, context){
		return new jQuery.fn.init(selector, context);
	},
	quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/;

jQuery.fn = jQuery.prototype = {
	init: function(selector, context){
		selector = selector || document;

		if(typeof selector == "string"){
			var match = 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(var prop in obj){
		this[prop] = obj[prop];
	}
	return this;
};
// 公共函数扩展
jQuery.extend({
	// 参数说明:object 表示 jQuery 对象,callback 表示回调函数, args 回调函数的参数数组
	each: function(object, callback, args){
		var name, i=0, length = object.length;
		if(args){     // 如果存在回调函数的参数数组
			if(length === undefined){ // 如果 object 不是 jQuery 对象
				for(name in object){  // 遍历 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(name in object) 	// 遍历 object 的属性
					if(callback.call(object[name], name, object[name]) === false)
						break;			// 如果回调函数返回值为 false ,则跳出循环	
			} else {			// 如果 object 是 jQuery 对象
				for(var value = 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(typeof context.createElement == "undefined")
			// 支持 context 为 jQuery 对象,并获取第一个元素
			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
		// 如果仅匹配一个标签,且没有指定框架参数,则直接创建 DOM 元素,并跳过后面的解析
		if(!fragment && elems.length === 1 && typeof elems[0] === "string") {
			var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
			if ( match )
				return [ context.createElement( match[1] ) ];
		}
		var ret = [], scripts = [], div = context.createElement("div");
		// 匹配每一个 HTML 字符串片段,并为每一个片段执行回调函数			
		jQuery.each(elems, function(i, elem){
			if(typeof elem === "number") // 把数值转换为字符串的高效方法
				elem += '';
			if(!elem) // 如果不存在元素,则返回,或者为 ''、undefined、false 等时也返回
				return;
			// HTML 字符串转换为 DOM 节点
			if(typeof elem === "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 -- 第二个捕获组
					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
						all :
						front + "></" + tag + ">";
				});
				// 清除空格,否则 indexof 可能会出现不能正常工作 , elem.replace(/^\s+/, "") 清除左侧空格
				var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
				// 有些标签必须是有一些约束的,如 <option> 必须在 <select></select>中间
				// 下面代码大部分是对 <table> 中的子元素进行修正。数组中第一个元素为深度
				var wrap = 
					// 约束 <option> ,<opt在开始位置(index=0)就返回&&运算符后面的数组 (!0 返回true)
					!tags.indexOf("<opt") &&
					[ 1, "<select multiple='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 不能串行化
					// IE can't serialize <link> and <script> tags normally
					!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 元素集合
		return ret;
	}		
});

})();

window.onload = function(){
	
var context = document.getElementById("wrap");
// 测试代码
$("<option /><div />", context);

};
</script>
上面这段代码实际上最后访问的是一个名为 ret 的数组,数组中的元素变为 DOM 元素的对象,而它的 innerHTML 正好就是刚才的 HTML 字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值