zoukankan      html  css  js  c++  java
  • jQuery源码——.html()方法原理解析

    在将字符串转化为html碎片时,一般会将字符串作为容器的innerHTML属性赋值。但innerHTML有很多局限性,比如我们想转化的字符串中有<script>标签并且包含一个立即执行的函数,如果将此字符串通过innerHTML转化为html碎片,<script>标签中的函数并不会被执行。

    jQuery中的.html()函数可以弥补innerHTML的缺陷,我们看下这个方法是如何实现的。

    其实原理很简单:正则匹配<script>标签,获取js函数,然后用eval()函数解析。jQuery在处理此工程中有几个细节值得学习。

    首先看一下html()函数的主入口:

     1 html: function( value ) {
     2         return access( this, function( value ) {
     3             var elem = this[ 0 ] || {},
     4                 i = 0,
     5                 l = this.length;
     6 
     7             if ( value === undefined ) {
     8                 return elem.nodeType === 1 ?
     9                     elem.innerHTML.replace( rinlinejQuery, "" ) :
    10                     undefined;
    11             }
    12 
    13             // See if we can take a shortcut and just use innerHTML
    14             if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
    15                 ( support.htmlSerialize || !rnoshimcache.test( value )  ) &&
    16                 ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
    17                 !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) {
    18 
    19                 value = value.replace( rxhtmlTag, "<$1></$2>" );
    20 
    21                 try {
    22                     for (; i < l; i++ ) {
    23                         // Remove element nodes and prevent memory leaks
    24                         elem = this[i] || {};
    25                         if ( elem.nodeType === 1 ) {
    26                             jQuery.cleanData( getAll( elem, false ) );
    27                             elem.innerHTML = value;
    28                         }
    29                     }
    30 
    31                     elem = 0;
    32 
    33                 // If using innerHTML throws an exception, use the fallback method
    34                 } catch(e) {}
    35             }
    36 
    37             if ( elem ) {
    38                 this.empty().append( value );
    39             }
    40         }, null, value, arguments.length );
    41     },

    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()函数是怎么处理的呢?

    1 append: function() {
    2         return this.domManip( arguments, function( elem ) {
    3             if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
    4                 var target = manipulationTarget( this, elem );
    5                 target.appendChild( elem );
    6             }
    7         });
    8     },

    append()函数调用domManip()函数,回调函数中的参数elem是经domManip()函数处理后的documentFragment,domManip()内部代码如下:

     1 domManip: function( args, callback ) {
     2 
     3         // Flatten any nested arrays
     4         args = concat.apply( [], args );
     5 
     6         var first, node, hasScripts,
     7             scripts, doc, fragment,
     8             i = 0,
     9             l = this.length,
    10             set = this,
    11             iNoClone = l - 1,
    12             value = args[0],
    13             isFunction = jQuery.isFunction( value );
    14 
    15         // We can't cloneNode fragments that contain checked, in WebKit
    16         if ( isFunction ||
    17                 ( l > 1 && typeof value === "string" &&
    18                     !support.checkClone && rchecked.test( value ) ) ) {
    19             return this.each(function( index ) {
    20                 var self = set.eq( index );
    21                 if ( isFunction ) {
    22                     args[0] = value.call( this, index, self.html() );
    23                 }
    24                 self.domManip( args, callback );
    25             });
    26         }
    27 
    28         if ( l ) {
    29             fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
    30             first = fragment.firstChild;
    31 
    32             if ( fragment.childNodes.length === 1 ) {
    33                 fragment = first;
    34             }
    35 
    36             if ( first ) {
    37                 scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
    38                 hasScripts = scripts.length;
    39 
    40                 // Use the original fragment for the last item instead of the first because it can end up
    41                 // being emptied incorrectly in certain situations (#8070).
    42                 for ( ; i < l; i++ ) {
    43                     node = fragment;
    44 
    45                     if ( i !== iNoClone ) {
    46                         node = jQuery.clone( node, true, true );
    47 
    48                         // Keep references to cloned scripts for later restoration
    49                         if ( hasScripts ) {
    50                             jQuery.merge( scripts, getAll( node, "script" ) );
    51                         }
    52                     }
    53 
    54                     callback.call( this[i], node, i );
    55                 }
    56 
    57                 if ( hasScripts ) {
    58                     doc = scripts[ scripts.length - 1 ].ownerDocument;
    59 
    60                     // Reenable scripts
    61                     jQuery.map( scripts, restoreScript );
    62 
    63                     // Evaluate executable scripts on first document insertion
    64                     for ( i = 0; i < hasScripts; i++ ) {
    65                         node = scripts[ i ];
    66                         if ( rscriptType.test( node.type || "" ) &&
    67                             !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
    68 
    69                             if ( node.src ) {
    70                                 // Optional AJAX dependency, but won't run scripts if not present
    71                                 if ( jQuery._evalUrl ) {
    72                                     jQuery._evalUrl( node.src );
    73                                 }
    74                             } else {
    75                                 jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
    76                             }
    77                         }
    78                     }
    79                 }
    80 
    81                 // Fix #11809: Avoid leaking memory
    82                 fragment = first = null;
    83             }
    84         }
    85 
    86         return this;
    87     }

    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>内部的逻辑,代码如下:

     1 globalEval: function( data ) {
     2         if ( data && jQuery.trim( data ) ) {
     3             // We use execScript on Internet Explorer
     4             // We use an anonymous function so that context is window
     5             // rather than jQuery in Firefox
     6             ( window.execScript || function( data ) {
     7                 window[ "eval" ].call( window, data );
     8             } )( data );
     9         }
    10     },
  • 相关阅读:
    SetWindowsHookEx详解
    C#使用全局钩子(hook),SetWindowsHookEx返回0、不回调的解决
    C#使用全局钩子(hook),SetWindowsHookEx返回0、不回调的解决
    how to get geometry type of layer using IMapServer3 and IMapLayerInfo? (C#)
    how to get geometry type of layer using IMapServer3 and IMapLayerInfo? (C#)
    windows cmd命令显示UTF8设置
    windows cmd命令显示UTF8设置
    C#写的NoSQL开源项目/系统(系列)
    TCP协议详解(2)
    红黑树
  • 原文地址:https://www.cnblogs.com/ihardcoder/p/4497808.html
Copyright © 2011-2022 走看看