jQuery源码测试笔记之domManip函数

来源:互联网 发布:那个网络教育好 编辑:程序博客网 时间:2024/06/02 07:30

代码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 arraysargs = 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 WebKitif ( 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").lengthif ( l ) {//用参数创建一个Fragment对象,ownerDocument是当前的调用对象的ownerDocumentfragment = 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++ ) {//保存fragmentnode = fragment;                    //判断是否已经到了$("#n1").length-1就是是否到最后一个调用对象!if ( i !== iNoClone ) {//不是最后一个元素那么对该fragment克隆,也就是克隆下面的p元素,具有该元素的所有属性node = jQuery.clone( node, true, true );// Keep references to cloned scripts for later restorationif ( 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/javascriptjQuery.map( scripts, restoreScript );                     //对上面的script也就是元素的script遍历// Evaluate executable scripts on first document insertionfor ( 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 presentif ( 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 memoryfragment = 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 themif ( 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,documentvar 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会被执行!,如果把第三个参数设置为[],也是一样的结果!

0 0
原创粉丝点击