jQuery源码——.html()方法原理解析
2015-05-12 16:42
399 查看
在将字符串转化为html碎片时,一般会将字符串作为容器的innerHTML属性赋值。但innerHTML有很多局限性,比如我们想转化的字符串中有<script>标签并且包含一个立即执行的函数,如果将此字符串通过innerHTML转化为html碎片,<script>标签中的函数并不会被执行。
jQuery中的.html()函数可以弥补innerHTML的缺陷,我们看下这个方法是如何实现的。
其实原理很简单:正则匹配<script>标签,获取js函数,然后用eval()函数解析。jQuery在处理此工程中有几个细节值得学习。
首先看一下html()函数的主入口:
1. html()函数返回一个单例闭包access()函数,避免作用域污染;
2. 第14行,首先确定value是string类型,并且用 rnoInnerhtml.test( value ) 正则匹配value中是否包含<script>标签;
3. 第26行,首先清理容器的内容,然后将value作为容器的innerHTML属性赋值,然后将代表容器的局部变量elem赋值为0,跳过37行逻辑。有些同学会疑惑,将elem赋值为0为什么不会影响dom元素?这里面涉及到JavaScript中值类型和引用类型的区别,请自行查阅相关资料;
4. 第38行,如果value中包括<script>标签,则用append()方法进行后续操作。
那么append()函数是怎么处理的呢?
append()函数调用domManip()函数,回调函数中的参数elem是经domManip()函数处理后的documentFragment,domManip()内部代码如下:
1. 第28行-55行,生成docmentFragment,并将<script>节点克隆以便后续的解析执行;
2. 第57行-79行,执行<script>内部的代码,注意75行的 node.text || node.textContent || node.innerHTML || "" ,这是兼容写法,即获取<script>标签内部的文本。
3. 第69行,如果<script>标签是引用外部资源,则请求资源url;
3. 第75行,如果<script>标签是行内代码,则调用globaleEval()函数执行<script>内部的逻辑,代码如下:
jQuery中的.html()函数可以弥补innerHTML的缺陷,我们看下这个方法是如何实现的。
其实原理很简单:正则匹配<script>标签,获取js函数,然后用eval()函数解析。jQuery在处理此工程中有几个细节值得学习。
首先看一下html()函数的主入口:
html: function( value ) { return access( this, function( value ) { var elem = this[ 0 ] || {}, i = 0, l = this.length; if ( value === undefined ) { return elem.nodeType === 1 ? elem.innerHTML.replace( rinlinejQuery, "" ) : undefined; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && ( support.htmlSerialize || !rnoshimcache.test( value ) ) && ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { value = value.replace( rxhtmlTag, "<$1></$2>" ); try { for (; i < l; i++ ) { // Remove element nodes and prevent memory leaks elem = this[i] || {}; if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch(e) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); },
1. html()函数返回一个单例闭包access()函数,避免作用域污染;
2. 第14行,首先确定value是string类型,并且用 rnoInnerhtml.test( value ) 正则匹配value中是否包含<script>标签;
3. 第26行,首先清理容器的内容,然后将value作为容器的innerHTML属性赋值,然后将代表容器的局部变量elem赋值为0,跳过37行逻辑。有些同学会疑惑,将elem赋值为0为什么不会影响dom元素?这里面涉及到JavaScript中值类型和引用类型的区别,请自行查阅相关资料;
4. 第38行,如果value中包括<script>标签,则用append()方法进行后续操作。
那么append()函数是怎么处理的呢?
append: function() { return this.domManip( arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.appendChild( elem ); } }); },
append()函数调用domManip()函数,回调函数中的参数elem是经domManip()函数处理后的documentFragment,domManip()内部代码如下:
domManip: function( args, callback ) { // Flatten any nested arrays args = concat.apply( [], args ); var first, node, hasScripts, scripts, doc, fragment, i = 0, l = this.length, set = this, iNoClone = l - 1, 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 ) ) ) { return this.each(function( index ) { var self = set.eq( index ); if ( isFunction ) { args[0] = value.call( this, index, self.html() ); } self.domManip( args, callback ); }); } if ( l ) { fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; } if ( first ) { scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); 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++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // Keep references to cloned scripts for later restoration if ( hasScripts ) { jQuery.merge( scripts, getAll( node, "script" ) ); } } callback.call( this[i], node, i ); } if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src ) { // Optional AJAX dependency, but won't run scripts if not present if ( jQuery._evalUrl ) { jQuery._evalUrl( node.src ); } } else { jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); } } } } // Fix #11809: Avoid leaking memory fragment = first = null; } } return this; }
1. 第28行-55行,生成docmentFragment,并将<script>节点克隆以便后续的解析执行;
2. 第57行-79行,执行<script>内部的代码,注意75行的 node.text || node.textContent || node.innerHTML || "" ,这是兼容写法,即获取<script>标签内部的文本。
3. 第69行,如果<script>标签是引用外部资源,则请求资源url;
3. 第75行,如果<script>标签是行内代码,则调用globaleEval()函数执行<script>内部的逻辑,代码如下:
globalEval: function( data ) { if ( data && jQuery.trim( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } },
相关文章推荐
- jquery源码解析:jQuery队列操作queue方法实现的原理
- jQuery源码分析以及从jQuery对象创建的角度理解extend方法的原理
- jQuery源码分析之parseHTML方法
- jQuery源码阅读(六)---jQuery实例方法解析
- jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究
- jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理
- jquery源码解析:jQuery原型方法init的详解
- JQuery源码解析-JQuery.extend()方法
- jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理
- jquery获得当前html页面源码的方法
- 解析xml的几种方法,他们的原理,比较 以及JAVA源码
- Spring IOC原理源码解析(@Autowired原理详解 :标识属性与方法)(二 )
- jQuery.isPlainObject方法源码解析
- JQuery源码解析-添加JQuery的一些方法和属性
- jQuery源码解析之构造函数的方法
- jq源码解析之绑在$,jQuery上面的方法(实例讲解)
- JQuery源码解析-JQuery的工具方法(5)
- JQuery源码解析-JQuery的工具方法(3)
- JQuery源码解析-JQuery的工具方法(4)
- JQuery源码解析-JQuery的工具方法