jQuery技术内幕:深入解析jQuery架构设计与实现原理. 2.3 jQuery.fn.init( selector, context, rootjQuery )

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:

2.3 jQuery.fn.init( selector, context, rootjQuery )

2.3.1 12个分支

构造函数jQuery.fn.init()负责解析参数selector和context的类型,并执行相应的逻辑,最后返回jQuery.fn.init()的实例。参数selector和context共有12个有效分支,如表2-1所示。

表2-1 参数selector和context的12个分支

         selector   context    示  例

1       可以转换为false    —     $()

2       DOM元素        —     $( document.body )

3       字符串     “body”          —     $('body')

4                单独标签         —     $('<div>')

$('<div>',{'class': 'test'} )

5                复杂 HTML 代码   —     $('<div>abc</div>')

6                “#id”         undefined         $('#id')

7                选择器表达式        undefined         $('div p')

8                选择器表达式        jQuery 对象    $('div p', $('#id') )

9                选择器表达式        DOM 元素      $('div.foo').click( function() {

   $('span', this ).addClass('bar');

} );

10     函数         —     $( function(){ ... } )

11     jQuery 对象    —     $( $('div p') )

12     其他任意类型的值         —     $( { abc: 123 } )

$( [ 1, 2, 3 ] )

 

下面分析jQuery.fn.init()的源码,看看它是如何解析和处理参数selector和context的12个分支的。

2.3.2 源码分析

1.?定义jQuery.fn.init( selector, context, rootjQuery )

相关代码如下所示:

99     init: function( selector, context, rootjQuery ) {

100         var match, elem, ret, doc;

101

第99行:定义构造函数jQuery.fn.init( selector, context, rootjQuery ),它接受3个参数:

参数 selector:可以是任意类型的值,但只有undefined、DOM 元素、字符串、函数、jQuery对象、普通 JavaScript对象这几种类型是有效的,其他类型的值也可以接受但没有意义。

参数 context:可以不传入,或者传入DOM元素、jQuery对象、普通 JavaScript 对象之一。

参数rootjQuery:包含了document对象的jQuery对象,用于 document.getElement

ById()查找失败、selector是选择器表达式且未指定context、selector是函数的情况。rootjQuery 的定义和应用场景的代码如下所示:

// document.getElementById() 查找失败

172                       return rootjQuery.find( selector );

// selector 是选择器表达式且未指定 context

187             return ( context || rootjQuery ).find( selector );

// selector 是函数

198          return rootjQuery.ready( selector );

 

// 定义 rootjQuery

916 // All jQuery objects should point back to these

917 rootjQuery = jQuery(document);

918

第100行:变量match、elem、ret、doc的功能会在接下来的分析过程中介绍。

2.?参数selector可以转换为false

参数selector可以转换为false,例如是undefined、空字符串、null等,则直接返回this,此时this是空jQuery对象,其属性length等于0。相关代码如下所示:

102         // Handle $(""), $(null), or $( undefined )

103         if ( !selector ) {

104             return this;

105         }

106

3.?参数selector是DOM元素

如果参数selector有属性nodeType,则认为selector是DOM元素,手动设置第一个元素和属性context指向该DOM元素、属性length为1,然后返回包含了该DOM元素引用的jQuery对象。相关代码如下所示:

107         // Handle $(DOMElement)

108         if ( selector.nodeType ) {

109             this.context = this[0] = selector;

110             this.length = 1;

111             return this;

112         }

113

第108行:属性nodeType声明了文档树中节点的类型,例如,Element节点的该属性值是1,Text节点是3,Comment节点是9,Document对象是9,DocumentFragment节点是11。

4.?参数selector是字符串“body”

如果参数selector是字符串“body”,手动设置属性context指向document对象、第一个元素指向body元素、属性length为1,最后返回包含了body元素引用的jQuery对象。这里是对查找字符串“body”的优化,因为文档树中只会存在一个body元素。相关代码如下所示:

114         // The body element only exists once, optimize finding it

115         if ( selector === "body" && !context && document.body ) {

116             this.context = document;

117             this[0] = document.body;

118             this.selector = selector;

119             this.length = 1;

120             return this;

121         }

122

5.?参数selector是其他字符串

如果参数selector是其他字符串,则先检测selector是HTML代码还是#id。相关代码如下所示:

123         // Handle HTML strings

124         if ( typeof selector === "string" ) {

125             // Are we dealing with HTML string or an ID?

126             if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {

127                 // Assume that strings that start and end with <> are HTML and skip the regex check

128                 match = [ null, selector, null ];

129

130             } else {

131                 match = quickExpr.exec( selector );

132             }

133

第126~128行:如果参数selector以“<”开头、以“>”结尾,且长度大于等于3,则假设这个字符串是HTML片段,跳过正则quickExpr的检查。注意这里仅仅是假设,并不一定表示它是真正合法的HTML代码,如“<div></p>”。

第131行:否则,用正则quickExpr检测参数selector是否是稍微复杂一些的HTML代码(如“abc<div>”)或#id,匹配结果存放在数组match中。正则quickExpr的定义如下:

39     // A simple way to check for HTML strings or ID strings

40     // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)

41     quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

正则quickExpr包含两个分组,依次匹配HTML代码和id。如果匹配成功,则数组match的第一个元素为参数selector,第二个元素为匹配的HTML代码或undefined,第三个元素为匹配的id或undefined。下面的例子测试了正则quickExpr的功能:

quickExpr.exec( '#target' );               // ["#target", undefined, "target"]

quickExpr.exec( '<div>' );          // ["<div>", "<div>", undefined]

quickExpr.exec( 'abc<div>' );             // ["abc<div>", "<div>", undefined]

quickExpr.exec( 'abc<div>abc#id' ); // ["abc<div>abc#id", "<div>", undefined]

quickExpr.exec( 'div' );                       // null

quickExpr.exec( '<div><img></div>' );      // ["<div><img></div>", "<div><img>

</div>", undefined]

第41行黑底白字的,在jQuery 1.6.3和之后的版本中,为了避免基于location.hash的XSS攻击,于是在quickExpr中增加了。在jQuery 1.6.3之前的版本中quickExpr的定义如下:

quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

在jQuery 1.6.3和之后的版本中,quickExpr匹配selector时如果遇到“#”,则认为不是HTML代码,而是#id,然后尝试调用document.getElementById()查找与之匹配的元素。而在jQuery 1.6.3之前的版本中,则只检查左尖括号和右尖括号,如果匹配则认为是HTML代码,并尝试创建DOM元素,这可能会导致恶意的XSS攻击。

假设有下面的场景:

在应用代码中出现$( location.hash ),即根据location.hash的值来执行不同的逻辑,而用户可以自行在浏览器地址栏中修改hash值为“#<img src=/ onerror=alert(1)>”,并重新打开这个页面;此时$( location.hash )在执行时变为$('#<img src=/ onerror=alert(1)>')。在jQuery 1.6.3之前,“#<img src=/ onerror=alert(1)>”被认为是HTML代码并创建img元素,因为属性src指向的图片地址并不存在,事件句柄onerror被执行并弹出1。这样一来,攻击者就可以在事件句柄onerror中编写恶意的JavaScript代码,例如,读取用户cookie、发起Ajax请求等。

读者可以访问以下地址,查看更多相关信息:

http://bugs.jquery.com/ticket/9521

http://ma.la/jquery_xss/

(1)参数selector是单独标签

如果参数selector是单独标签,则调用document.createElement()创建标签对应的DOM元素。相关代码如下所示:

134             // Verify a match, and that no context was specified for #id

135             if ( match && (match[1] || !context) ) {

136

137                 // HANDLE: $(html) -> $(array)

138                 if ( match[1] ) {

139                     context = context instanceof jQuery ? context[0] : context;

140                     doc = ( context ? context.ownerDocument || context : document );

141

142                     // If a single string is passed in and it's a single tag

143                     // just do a createElement and skip the rest

144                     ret = rsingleTag.exec( selector );

145

146                     if ( ret ) {

147                         if ( jQuery.isPlainObject( context ) ) {

148                             selector = [ document.createElement( ret[1] ) ];

149                             jQuery.fn.attr.call( selector, context, true );

150

151                         } else {

152                             selector = [ doc.createElement( ret[1] ) ];

153                         }

154

第135行:检测正则quickExpr匹配参数selector的结果,如果match[1]不是undefined,即参数selector是HTML代码,或者match[2]不是undefined,即参数selector是#id,并且未传入参数context。这行代码利用布尔表达式的计算顺序,省略了对match[2]的判断,完整的表达式如下:

if ( match && (match[1] || match[2] && !context) ) {

如果match不是null且match[1]是undefined,那么此时match[2]必然不是undefined,所以对match[2]的判断可以省略。

第138~140行:开始处理参数selector是HTML代码的情况,先修正context、doc,然后用正则rsingleTag检测HTML代码是否是单独标签,匹配结果存放在数组ret中。正则rsingleTag的定义如下:

50     // Match a standalone tag

51     rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,

正则rsingleTag包含一个分组“(\w+)”,该分组中不包含左右尖括号、不能包含属性、可以自关闭或不关闭;“\1”指向匹配的第一个分组“(\w+)”。

第146~153行:如果数组ret不是null,则认为参数selector是单独标签,调用document.createElement()创建标签对应的DOM元素;如果参数context是普通对象,则调用jQuery方法.attr()并传入参数context,同时把参数context中的属性、事件设置到新创建的DOM元素上。

之所以把创建的DOM元素放入数组中,是为了在后面第160行方便地调用jQuery.merge()方法。方法jQuery.merge()用于合并两个数组的元素到第一个数组,相关内容在2.8.8节介绍和分析。

参数context的细节请参考2.1.1节;方法.attr()遇到特殊属性和事件类型属性时会执行同名的jQuery方法,相关内容将在8.2节介绍和分析;方法jQuery.isPlainObject()用于检测对象是否是“纯粹”的对象,即用对象直接量{}或new Object()创建的对象,这会在2.8.2节介绍和分析。

(2)参数selector是复杂HTML代码

如果参数selector是复杂HTML代码,则利用浏览器的innerHTML机制创建DOM元素。相关代码如下所示:

155                     } else {

156                        ret = jQuery.buildFragment( [ match[1] ], [ doc ] );

157                        selector = ( ret.cacheable ? jQuery.clone(ret.fragment):ret.fragment ).childNodes;

158                     }

159

160                     return jQuery.merge( this, selector );

161

第156行:创建过程由方法jQuery.buildFragment()和jQuery.clean()实现,方法jQuery.buildFragment()返回值的格式为:

{

   fragment: 含有转换后的 DOM 元素的文档片段

   cacheable: HTML 代码是否满足缓存条件

}

第157行:如果HTML代码满足缓存条件,则在使用转换后的DOM元素时,必须先复制一份再使用,否则可以直接使用。

方法jQuery.buildFragment()和jQuery.clean()将分别在2.4节和第2.5节中介绍和分析。

第160行:将新创建的DOM元素数组合并到当前jQuery对象中并返回。

(3)参数selector是“#id”,且未指定参数context

如果参数selector是“#id”,且未指定参数context,则调用document.getElementById()查找含有指定id属性的DOM元素。相关代码如下所示:

162                  // HANDLE: $("#id")

163                  } else {

164                     elem = document.getElementById( match[2] );

165

166                     // Check parentNode to catch when Blackberry 4.6 returns

167                     // nodes that are no longer in the document #6963

168                     if ( elem && elem.parentNode ) {

169                         // Handle the case where IE and Opera return items

170                         // by name instead of ID

171                         if ( elem.id !== match[2] ) {

172                             return rootjQuery.find( selector );

173                         }

174

175                         // Otherwise, we inject the element directly into the jQuery object

176                         this.length = 1;

177                         this[0] = elem;

178                     }

179

180                     this.context = document;

181                     this.selector = selector;

182                     return this;

183                  }

184

第162~164行:如果参数selector是“#id”且未指定参数context,则调用document.getElementById()查找含有指定id属性的DOM元素。

第166~168行:检查parentNode属性,因为Blackberry 4.6会返回已经不在文档中的DOM节点。

第169~173行:如果所找到元素的属性id值与传入的值不相等,则调用Sizzle查找并返回一个含有选中元素的新jQuery对象。即使是document.getElementById()这样核心的方法也需要考虑浏览器兼容问题,在IE 6、IE 7、某些版本的Opera中,可能会按属性name查找而不是id。例如,下面的HTML代码,通过document.getElementById()并不能找到正确的DOM元素:

<!DOCTYPE html>

<html>

<head>

   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

   <meta name="description" content="head meta description">

</head>

<body>

   <div id="description">

       body div description

   </div>

   <form name="divId">

       <div id="divId"></div>

   </form>

   <script>

       alert( document.getElementById( 'description' ).outerHTML );

       alert( document.getElementById( 'divId' ).outerHTML );

   </script>

</body>

</html>t

在IE7中的运行结果如图2-2和图2-3所示。

在这种情况下,Sizzle先通过document.getElementsByTagName("*")取出所有的DOM元素,然后检查每个元素的属性id是否与指定值相等,如果相等,则放入返回结果中。具体可查阅第3章关于“Sizzle”的介绍和分析。

 

第175~182行:如果所找到元素的属性id值与传入的值相等,则设置第一个元素、属性length、context、selector,并返回当前jQuery对象。

(4)参数selector是选择器表达式

相关代码如下所示:

185             // HANDLE: $(expr, $(...))

186             } else if ( !context || context.jquery ) {

187                return ( context || rootjQuery ).find( selector );

188

189             // HANDLE: $(expr, context)

190             // (which is just equivalent to: $(context).find(expr)

191             } else {

192                 return this.constructor( context ).find( selector );

193             }

194

此时依然在字符串分支中,参数selector不是单独标签、复杂HTML代码、#id,而是选择器表达式。如果没有指定上下文,则执行rootjQuery.find( selector );如果指定了上下文,且上下文是jQuery对象,则执行context.find( selector );如果指定了上下文,但上下文不是jQuery对象,则执行this.constructor( context ).find( selector ),即先创建一个包含了context的jQuery对象,然后在该jQuery对象上调用方法.find()。

6.参数selector是函数

相关代码如下所示:

195         // HANDLE: $(function)

196         // Shortcut for document ready

197         } else if ( jQuery.isFunction( selector ) ) {

198            return rootjQuery.ready( selector );

199         }

200

第197~199行:如果参数selector是函数,则认为是绑定ready事件。从第198行代码可以看出$( function )是$( document ).ready( function )的简写。

方法jQuery.isFunction()将在2.8.2节介绍和分析。

7.?参数selector是jQuery对象

相关代码如下所示:

201         if ( selector.selector !== undefined ) {

202             this.selector = selector.selector;

203             this.context = selector.context;

204         }

205

第201~204行:如果参数selector含有属性selector,则认为它是jQuery对象,将会复制它的属性selector和context。而且在紧随其后的第206行会把参数selector中包含的选中元素引用,全部复制到当前jQuery对象中。

8.?参数selector是任意其他值

相关代码如下所示:

206         return jQuery.makeArray( selector, this );

207     },

第206行:如果selector是数组或伪数组(如jQuery对象),则都添加到当前jQuery对象中;如果selector是JavaScript对象,则作为第一个元素放入当前jQuery对象中;如果是其他类型的值,则作为第一个元素放入当前jQuery对象中。最后返回当前jQuery对象。

2.3.3 小结

至此,方法jQuery.fn.init( selector, context, rootjQuery )的12分支就介绍完了,相关的判断和执行过程可以整理为图2-4。

相关文章
|
30天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
81 13
|
1月前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
1月前
|
Kubernetes Cloud Native 微服务
探索云原生技术:容器化与微服务架构的融合之旅
本文将带领读者深入了解云原生技术的核心概念,特别是容器化和微服务架构如何相辅相成,共同构建现代软件系统。我们将通过实际代码示例,探讨如何在云平台上部署和管理微服务,以及如何使用容器编排工具来自动化这一过程。文章旨在为开发者和技术决策者提供实用的指导,帮助他们在云原生时代中更好地设计、部署和维护应用。
|
1月前
|
人工智能 前端开发 编译器
【AI系统】LLVM 架构设计和原理
本文介绍了LLVM的诞生背景及其与GCC的区别,重点阐述了LLVM的架构特点,包括其组件独立性、中间表示(IR)的优势及整体架构。通过Clang+LLVM的实际编译案例,展示了从C代码到可执行文件的全过程,突显了LLVM在编译器领域的创新与优势。
83 3
|
4天前
|
Java Linux C语言
《docker基础篇:2.Docker安装》包括前提说明、Docker的基本组成、Docker平台架构图解(架构版)、安装步骤、阿里云镜像加速、永远的HelloWorld、底层原理
《docker基础篇:2.Docker安装》包括前提说明、Docker的基本组成、Docker平台架构图解(架构版)、安装步骤、阿里云镜像加速、永远的HelloWorld、底层原理
191 88
|
1月前
|
监控 安全 API
使用PaliGemma2构建多模态目标检测系统:从架构设计到性能优化的技术实践指南
本文详细介绍了PaliGemma2模型的微调流程及其在目标检测任务中的应用。PaliGemma2通过整合SigLIP-So400m视觉编码器与Gemma 2系列语言模型,实现了多模态数据的高效处理。文章涵盖了开发环境构建、数据集预处理、模型初始化与配置、数据加载系统实现、模型微调、推理与评估系统以及性能分析与优化策略等内容。特别强调了计算资源优化、训练过程监控和自动化优化流程的重要性,为机器学习工程师和研究人员提供了系统化的技术方案。
165 77
使用PaliGemma2构建多模态目标检测系统:从架构设计到性能优化的技术实践指南
|
5天前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
13天前
|
机器学习/深度学习 算法 PyTorch
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
软演员-评论家算法(Soft Actor-Critic, SAC)是深度强化学习领域的重要进展,基于最大熵框架优化策略,在探索与利用之间实现动态平衡。SAC通过双Q网络设计和自适应温度参数,提升了训练稳定性和样本效率。本文详细解析了SAC的数学原理、网络架构及PyTorch实现,涵盖演员网络的动作采样与对数概率计算、评论家网络的Q值估计及其损失函数,并介绍了完整的SAC智能体实现流程。SAC在连续动作空间中表现出色,具有高样本效率和稳定的训练过程,适合实际应用场景。
58 7
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
|
1月前
|
运维 监控 持续交付
微服务架构解析:跨越传统架构的技术革命
微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
268 36
微服务架构解析:跨越传统架构的技术革命
|
9天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。

热门文章

最新文章

推荐镜像

更多