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     },

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

深入解析 Go 中 Slice 底层实现

切片是 Go 中的一种基本的数据结构,使用这种结构可以用来管理数据集合。切片的设计想法是由动态数组概念而来,为了开发者可以更加方便的使一个数据结构可以自动增加和...

11820
来自专栏柠檬先生

Angularjs基础(十)

ng-blur  描述:规定blur 事件的行为       实例:当输入框失去焦点的(onblur)时执行表达式:         <input ng...

20750
来自专栏Golang语言社区

go 切片使用小结

最新项目使用go语言开发,因此有机会结识了go语言。在写代码时,无意间发现了同事代码的一个bug。今天拿来一起学习一下。 首先go语言有个强大的基本数据结构,那...

34580
来自专栏前端知识分享

第68天:原型prototype方法

构造函数有一个prototype属性,指向实例对象的原型对象。通过同一个构造函数实例化的多个对象具有相同的原型对象。经常使用原型对象来实现继承

13320
来自专栏技术博文

js去掉字符串前后空格的五种方法

第一种:循环检查替换 [javascript] //供使用者调用   function trim(s){   return trimRight(trimLeft...

43750
来自专栏偏前端工程师的驿站

JS魔法堂:追忆那些原始的选择器

一、前言                                                                            ...

24870
来自专栏电光石火

mybatis在xml文件中处理大于号小于号的方法

用了转义字符把>和<替换掉,然后就没有问题了。

271100
来自专栏vue学习

前端面试题总结(持续更新。。)

16920
来自专栏与神兽党一起成长

unslider源码分析

根据Bootstrap中文网的介绍,Unslider一个超小的 jQuery轮播(slider)插件,参照这个汉化版的介绍页面,这个插件有你需要的优点,但是本...

22120
来自专栏Golang语言社区

Go-指针、传值与传引用、垃圾回收

要点 Go使用的*、&、new()这些运算符,和C++的用法完全一样。 有传值和传引用/传地址的概念,和C++一样。 Go没有new对应的delete操作,而是...

33450

扫码关注云+社区

领取腾讯云代金券