代码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会被执行!,如果把第三个参数设置为[],也是一样的结果!