jQuery源码测试笔记之domManip函数

代码1:domManip调用的其它函数和正则表达式:

disableScript(让script标签的type失效)

//disableScript函数
//对[object HTMLParagrahElement]里面的元素调用这个方法!但是因为getAll( fragment, "script" )返回的长度是0所以没有元素要调用这里的方法
//如果这个里面有script标签才会调用这个disableScript方法。
function disableScript( elem ) {
	//为元素设定type属性,格式为"false/"
	//如果传入参数为script设定了type="text/javascript"结果就是"true/text/javascript"
	elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;		
	return elem;
}
restoreScript让script标签重新恢复原来的,而不是true/text/javascript

function restoreScript( elem ) {
	//传入每一个script标签
	var match = rscriptTypeMasked.exec( elem.type );
	//重新启用script标签,match[0]="true/text/javascript"
	if ( match ) {
		elem.type = match[1];
	} else {
		elem.removeAttribute("type");
	}
	return elem;
}
getAll(获取元素下面所有的子元素)
//getAll函数
//domManip调用方式:getAll( fragment, "script" )
function getAll( context, tag ) {
	var elems, elem,
		i = 0,
		//调用getElementsByTagName或者querySelectorAll根据context和tag选择对象,如这里是fragment和"script"
		found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) :
			typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) :
			undefined;
   //如果没有得到选择结果
		if ( !found ) {
		for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
			if ( !tag || jQuery.nodeName( elem, tag ) ) {
				found.push( elem );
			} else {
				jQuery.merge( found, getAll( elem, tag ) );
			}
		}
	}
   //如果没有传入标签名称;或者传入了标签的同时该context的nodeName值和tag一样就把context和found的结果合并!
	//否则只是返回found的结果!这里的调用就是:append1("<p style='color:red'>我在测试</p>")这时候只有一个
	//标签[object HTMLParagrahElement]也就是这里的context,但是传入的tag是script所以这里的就是仅仅返回found的结果
	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
		jQuery.merge( [ context ], found ) :
		found;
}
manipulationTarget源码 (如果第一个参数是table,第二个参数是tr(如果第二个参数是documentFragment那么获取他的第一个子元素),那么获取table下的tbody,如果没有tbody那么创建一个tbody然后返回)

var concat=[].concat;
var strundefined=undefined;
var rscriptTypeMasked = /^true\/(.*)/;
var rscriptType = /^$|\/(?:java|ecma)script/i;
var rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
//jQuery.nodeName的方法代码就是下面的nodeName方法
function nodeName( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	}
function manipulationTarget( elem, content ) {
	//如果是table元素
	return jQuery.nodeName( elem, "table" ) &&
		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
		elem.getElementsByTagName("tbody")[0] ||
			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
	//如果不是table元素,那么返回自身!
		elem;
}
domManip源码分析:
$.fn.extend({	
	domManip1: function( args, callback ) {
		// Flatten any nested arrays
		args = concat.apply( [], args );
       //调用append传入的参数,这里是"<p style='color:red'>我在测试</p>"
		var first, node, hasScripts,
			scripts, doc, fragment,
			i = 0,
			//$("#n1")的长度
			l = this.length,
			//保存引用$("#n1")
			set = this,
			iNoClone = l - 1,
		//获取第一个参数,这里是"<p style='color:red'>我在测试</p>"
			value = args[0],
			//如果传入的是函数
			isFunction = jQuery.isFunction( value );
		// We can't cloneNode fragments that contain checked, in WebKit
		if ( isFunction ||( l > 1 && typeof value === "string" &&!$.support.checkClone && rchecked.test( value ) ) ) {
			//如果传入的是函数,那么对每一个对象调用该函数。也就是每一个对象$("#n1")[i]
			return this.each(function( index ) {
				//获取外面的每一个jQuery对象,这里用的是eq所以返回的是jQuery对象
				var self = set.eq( index );
				if ( isFunction ) {
		//给调用函数传入参数是每一个DOM对象,DOM对象下标,以及该DOM对象的html(封装为jQuery对象调用html方法)
					args[0] = value.call( this, index, self.html() );
				}
		//用jQuery对象调用domManip方法,传入的参数不是外面传入的args函数,而是调用原来的函数的结果值!
				self.domManip( args, callback );
			});
		}
        //存在调用者$("#n1").length
		if ( l ) {
		//用参数创建一个Fragment对象,ownerDocument是当前的调用对象的ownerDocument
			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
			//获取frament的第一个孩子对象
			first = fragment.firstChild;
           //如果这个frament只有一个孩子节点,那么frament赋值为第一个孩子节点,这里调用方式满足只有一个孩子节点
			if ( fragment.childNodes.length === 1 ) {
				fragment = first;
			}
           //至少有一个孩子节点
			if ( first ) {
//getAll返回的是一个NodeList集合,对该集合的里面的元素都调用方法disableScript,当前这个NodeList只有一个[object HTMLParagrahElement]
//也就是传入的参数"<p style='color:red'>我在测试</p>"。然后把这个参数传入disableScript方法.
				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );				
				//表示script数组长度
				hasScripts = scripts.length;           
				// Use the original fragment for the last item instead of the first because it can end up
				// being emptied incorrectly in certain situations (#8070).
				for ( ; i < l; i++ ) {
					//保存fragment
					node = fragment;
                    //判断是否已经到了$("#n1").length-1就是是否到最后一个调用对象!
					if ( i !== iNoClone ) {
			//不是最后一个元素那么对该fragment克隆,也就是克隆下面的p元素,具有该元素的所有属性
						node = jQuery.clone( node, true, true );
			// Keep references to cloned scripts for later restoration
						if ( hasScripts ) {
			//获取克隆对象的所有的script,然后和原来的scripts集合进行合并
							jQuery.merge( scripts, getAll( node, "script" ) );
						}
					}
  //把每一个对象调用该callback方法,传入参数为$("#n1")[i],以及传入fragment也就是根据append参数创建的fragment对象,以及当前DOM对象的下标!
					callback.call( this[i], node, i );
				}//End of for loop
  /*这时候script已经合并了,上面克隆的node也就是fragment以后把script等也一起克隆了,总共有两个div元素,那么会被克隆一次,也就是外层if会被执行一次,最后得到了四个script而且type属性都是"true/text/javascript"。假如有n个元素,那么会克隆n-1次,最后得到个数是($("xx").length-1)×fragment的script个数+原始fragment的script个数!*/
				if ( hasScripts ) {
					doc = scripts[ scripts.length - 1 ].ownerDocument;
					// Reenable scripts
			//重新启用script元素,让元素元素type变成text/javascript
					jQuery.map( scripts, restoreScript );
                     //对上面的script也就是元素的script遍历
					// Evaluate executable scripts on first document insertion
					for ( i = 0; i < hasScripts; i++ ) {
						node = scripts[ i ];
						//获取scripts[i],如果doc也就是当前document包含当前的script标签
						if ( rscriptType.test( node.type || "" ) &&
							!jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
							//如果没有src,下面我传入的参数就是没有src属性的!
							if ( node.src ) {
								// Optional AJAX dependency, but won't run scripts if not present
								if ( jQuery._evalUrl ) {
									jQuery._evalUrl( node.src );
								}
							} else {
//获取其中script标签中的内容,调用jQuery.globalEval在全局作用域中执行,但是要去除其中的注释成分,也就是<!---->中的内容!
   jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
							}
						}
					}//End of for loop
				}
				// Fix #11809: Avoid leaking memory
				fragment = first = null;
				//把刚才用参数创建的fragment的引用设为空防止内存泄漏!
			}//End of second if
		}//End of outer if loop
      //返回this对象,也就是可以链式调用!
		return this;
	}//End of domManip1

})

jQuery._evalUrl函数源码

jQuery._evalUrl = function( url ) {
	return jQuery.ajax({
		url: url,
		type: "GET",
		dataType: "script",
		async: false,
		global: false,
		"throws": true
	});
};
note:如果script标签有src那么调用ajax方法获取到该script文件,注意这里的dataType是script,不执行全局事件,不是异步执行!throws在ajax方法中的用法是:因为这里是script,所以经过ajaxHandleResponse会变成["text script"],所以s["throws"]存在那么直接执行if语句!不管他是否抛出异常了! 因为这时候我不需要知道他是否抛出异常了!

                      // Apply converter (if not an equivalence)
				if ( conv !== true ) {
					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s[ "throws" ] ) {
						response = conv( response );
					} else {//如果没有throws那么我要把抛出的异常返回!
						try {
							response = conv( response );
						} catch ( e ) {
							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
						}
					}

下面用append方法测试domManp函数执行逻辑:

$.fn.extend({
append1: function() {
//这里举一个例子:$n1.append( document.getElementsByTagName("label"), $("i") );,那么在domManip中给第二个函数传入的context是传入的第i个DOM//元素,第一次是document.getElementsByTagName("label"),第二次是$("i")[0],第二个参数是node也就是由这两个参数组成的documentFragment对象!
		return this.domManip1( arguments, function( elem ) {
			//传入参数$("#n1")[i],i上下文是$("#n1")[i]
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				//如果是Element,documentFragment,document
				var target = manipulationTarget( this, elem );
                             //往DOM对象里面放的elem需要判断,如果this是table同时elem是tr,那么往this中添加一个tbody标签!返回tbody标签的引用!
			    //然后往tbody里面添加elem元素。这里的target就是调用对象table下面的tbody元素!所以这里是对tbody的特殊处理!
				target.appendChild( elem );
			}
		});
	}
})

调用方式1:

//这种调用把参数字符串解析成为fragment对象,然后逐个添加到调用对象后面!
$("div").append1("<p style='color:red'>我在测试<script type='text/javascript'><\/script><script type='text/javascript'><\/script></p>").attr("id");
调用方式2:

//如果调用方式为:
var $p = $("p");
$p.append( function(index, html){
	return '<span>追加元素' + (index + 1) + '</span>';	
} );
//args[0] = value.call( this, index, self.html() );
//self.domManip( args, callback );
//所以:这种调用方式是对每一个DOM对象都调用append的参数对应的回调函数,传入的第一个参数index是DOM下标,第二个参数是调用对象的内部html!然后获取到该函数的
//返回值,然后用这个返回值继续调用domManip,所以每一个DOM对象调用的时候参数可能是不一样的,这里self.domManip的callback是固定回调!
调用方式3:

var $n1 = $("#n1");
// 将所有的label元素和i元素追加到n1内部的末尾位置
// 原来位置的label元素和i元素会消失(相当于是移动到n1内部的末尾位置)
$n1.append( document.getElementsByTagName("label"), $("i") );
note:这种方式一定要注意,这时候创建的documentFragment会包含参数中的两个对象,但是因为这两个参数已经存在于DOM树中,所以当append的时候只是发生了位置移动,而没有创建这两个元素的副本!更加极端的调用见调用方式4:

调用方式4:

var $n1 = $("#n1");
// 将所有的label元素和i元素追加到n1内部的末尾位置
// 原来位置的label元素和i元素会消失(相当于是移动到n1内部的末尾位置)
$n1.append( document.getElementsByTagName("n1") );
note:这种方式很极端,根本原因在于我们没有在底层使用克隆,我们的调用对象$n1和参数是同一个对象,所以在buildFragment返回的fragment是空的,那么添加到$("#n1")是空元素,也就是底层什么也不做!

测试总结:

(1)jQuery.clone(elem,true,true)第一个true表示同时复制数据和绑定事件,第二个true表示同时赋值子元素的附加数据和绑定事件。所以append方法调用时候会保存所有的scripts,也就是可以添加script元素!

(2)jQuery.merge( scripts, getAll( node, "script" ) )执行的次数是调用对象的n-1,也就是调用对象如果是n那么他执行的次数就是n-1,最后使得scripts集合中存放的scripts的个数=原来自己传入的scripts个数+(调用对象DOM个数-1)×自己传入的scripts个数。但是我们自己传入的scripts在循环之前已经disableScript了,所以后面添加的scripts不可能和他的type一致,所以后面调用了restoreScript就只可能获取到前面我们自己传入的scripts了!(所以我在想,这里完全可以不克隆scripts)

(3)获取script标签的内容使用了node.text/node.textContent!

(4)上面buildFragment时候的一句代码为:fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );,通过buldFragment的源码分析我们知道如果args在this中,那么什么也不会做,也就是不会创建相应的DOM,而发生的是DOM的移动,也就是上面的第三中调用方式!也就是不会发生tmp = getAll( safe.appendChild( elem ), "script" ); 这一句,所以最后不会在创建好的documentFragment上!

(5)buildFragment第三个参数是false,那么会把所有的scripts解析出来

var parsed = jQuery.buildFragment( ["<div>我是内容1</div><script>alert('我是解析出来的js');<\/script><div>我是内容2</div>"], document, false );  
$("body").append(parsed);//这时候的scripts会被执行!,如果把第三个参数设置为[],也是一样的结果!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值