@ -3681,81 +3681,114 @@ if ( !jQuery.support.submitBubbles ) {
}
// IE change delegation and checkbox/radio fix
// 判断jQuery是否不支持change事件冒泡( 如果!jQuery.support.changeBubbles为真, 表示不支持)
if ( ! jQuery . support . changeBubbles ) {
// 为jQuery的事件系统中的'special'对象添加名为'change'的自定义事件相关配置
jQuery . event . special . change = {
// 'setup'函数会在绑定事件时被调用,用于进行一些初始化设置
setup : function ( ) {
// 测试当前节点的节点名是否匹配特定的表单元素相关的正则表达式( rformElems应该是在别处定义的用于匹配表单元素节点名的正则)
if ( rformElems . test ( this . nodeName ) ) {
// IE doesn't fire change on a check/radio until blur; trigger it on click
// after a propertychange. Eat the blur-change in special.change.handle.
// This still fires onchange a second time for check/radio after blur.
// 在IE浏览器中, 复选框( checkbox) 和单选框( radio) 在失去焦点( blur) 时才触发'change'事件, 这里在点击( click) 后且发生属性改变( propertychange) 时触发它。
// 并且在'special.change.handle'中处理掉因失去焦点导致的重复的'change'事件触发情况。
// 但这样对于复选框和单选框在失去焦点后还是会第二次触发'onchange'事件。
if ( this . type === "checkbox" || this . type === "radio" ) {
// 为当前元素添加名为'propertychange._change'的事件监听器,当属性改变事件发生时执行下面的回调函数
jQuery . event . add ( this , "propertychange._change" , function ( event ) {
// 如果属性改变事件的原始事件中属性名为'checked'(也就是复选框或单选框的选中状态改变了)
if ( event . originalEvent . propertyName === "checked" ) {
// 标记当前元素刚刚发生了改变
this . _just _changed = true ;
}
} ) ;
// 为当前元素添加名为'click._change'的事件监听器,当点击事件发生时执行下面的回调函数
jQuery . event . add ( this , "click._change" , function ( event ) {
// 如果当前元素刚刚发生了改变且不是通过代码手动触发的事件(!event.isTrigger)
if ( this . _just _changed && ! event . isTrigger ) {
// 重置刚刚改变的标记
this . _just _changed = false ;
// 模拟触发'change'事件, 传递当前元素、原始点击事件以及一些其他相关参数( 最后一个参数true可能有特定含义, 比如冒泡相关等)
jQuery . event . simulate ( "change" , this , event , true ) ;
}
} ) ;
}
// 表示不需要执行默认的事件绑定逻辑了( 可能有特定的jQuery内部机制相关含义)
return false ;
}
// Delegated event; lazy-add a change handler on descendant inputs
// 如果是委托事件(意味着事件是绑定在父元素上,等待子元素触发的情况)
// 延迟添加一个针对后代输入元素的'change'事件处理程序
jQuery . event . add ( this , "beforeactivate._change" , function ( e ) {
// 获取实际触发事件的目标元素
var elem = e . target ;
// 再次测试目标元素的节点名是否匹配特定表单元素正则,并且该元素还没有添加过'_change'相关的事件处理程序(!elem._change_attached)
if ( rformElems . test ( elem . nodeName ) && ! elem . _change _attached ) {
// 为目标元素添加名为'change._change'的事件监听器,当'change'事件发生时执行下面的回调函数
jQuery . event . add ( elem , "change._change" , function ( event ) {
// 如果目标元素有父元素,并且事件不是模拟触发的(!event.isSimulated) 也不是通过代码手动触发的( !event.isTrigger)
if ( this . parentNode && ! event . isSimulated && ! event . isTrigger ) {
// 模拟触发父元素的'change'事件, 传递父元素、当前事件以及相关参数( 同样最后一个参数true可能和冒泡等有关)
jQuery . event . simulate ( "change" , this . parentNode , event , true ) ;
}
} ) ;
// 标记该元素已经添加过'_change'相关的事件处理程序了
elem . _change _attached = true ;
}
} ) ;
} ,
// 'handle'函数用于处理实际触发的事件,决定是否执行默认的事件处理逻辑等
handle : function ( event ) {
// 获取实际触发事件的目标元素
var elem = event . target ;
// Swallow native change events from checkbox/radio, we already triggered them above
// 如果当前处理事件的对象不是目标元素本身( 可能是冒泡上来的情况等) , 或者事件是模拟触发的( event.isSimulated) , 或者是通过代码手动触发的( event.isTrigger) , 或者目标元素不是单选框和复选框类型
if ( this !== elem || event . isSimulated || event . isTrigger || ( elem . type !== "radio" && elem . type !== "checkbox" ) ) {
// 执行默认的事件处理程序, 也就是调用原本绑定的事件处理函数, 传递相关参数( arguments包含了事件相关的参数等)
return event . handleObj . handler . apply ( this , arguments ) ;
}
} ,
// 'teardown'函数会在解绑事件时被调用,用于清理相关的事件绑定等操作
teardown : function ( ) {
// 移除当前元素上所有名称包含'._change'的事件监听器
jQuery . event . remove ( this , "._change" ) ;
// 返回当前节点的节点名是否匹配特定表单元素的正则表达式的测试结果(可能用于判断是否还有相关清理工作要做等)
return rformElems . test ( this . nodeName ) ;
}
} ;
}
// Create "bubbling" focus and blur events
// 判断jQuery是否不支持焦点进入( focusin) 和焦点离开( focusout) 事件冒泡( 如果!jQuery.support.focusinBubbles为真, 表示不支持)
if ( ! jQuery . support . focusinBubbles ) {
// 遍历包含'focus'和'blur'的对象,将'focus'映射为'focusin', 'blur'映射为'focusout',进行相关操作
jQuery . each ( { focus : "focusin" , blur : "focusout" } , function ( orig , fix ) {
// Attach a single capturing handler while someone wants focusin/focusout
// 用于记录当前有多少个地方想要使用'focusin'或'focusout'事件, 初始化为0
var attaches = 0 ,
// 定义事件处理函数,用于模拟触发对应的'fix'(也就是'focusin'或'focusout')事件
handler = function ( event ) {
jQuery . event . simulate ( fix , event . target , jQuery . event . fix ( event ) , true ) ;
} ;
// 为jQuery的事件系统中的'special'对象添加名为'fix'(也就是'focusin'或'focusout')的自定义事件相关配置
jQuery . event . special [ fix ] = {
//'setup'函数会在绑定'focusin'或'focusout'事件时被调用,用于进行初始化设置
setup : function ( ) {
// 当第一次有地方绑定该事件时( attaches初始为0, 自增后为1)
if ( attaches ++ === 0 ) {
// 在文档对象上添加原生的'orig'(也就是'focus'或'blur')事件的捕获阶段监听器,绑定上面定义的'handler'函数
document . addEventListener ( orig , handler , true ) ;
}
} ,
// 'teardown'函数会在解绑'focusin'或'focusout'事件时被调用,用于清理相关操作
teardown : function ( ) {
// 当所有绑定该事件的地方都解绑了( attaches自减后为0)
if ( -- attaches === 0 ) {
// 移除文档对象上对应的原生'orig'(也就是'focus'或'blur')事件的捕获阶段监听器
document . removeEventListener ( orig , handler , true ) ;
}
}
@ -3763,68 +3796,84 @@ if ( !jQuery.support.focusinBubbles ) {
} ) ;
}
// 使用jQuery.fn.extend方法来扩展jQuery的原型对象, 添加一系列与事件处理相关的方法
jQuery . fn . extend ( {
// on方法用于绑定事件, 可以有多种参数形式来处理不同的绑定场景
on : function ( types , selector , data , fn , /*INTERNAL*/ one ) {
var origFn , type ;
// Types can be a map of types/handlers
// 第一种情况: 如果types参数是一个对象, 意味着可以传入多个事件类型及其对应的处理函数的映射形式
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
// 如果selector参数不是字符串类型( 且不为null, 这里原注释中虽然注释掉了!= null判断, 但可能实际逻辑中有此考虑) , 说明可能传入的参数形式是( types-Object, data )这种, 没有selector参数
if ( typeof selector !== "string" ) { // && selector!= null
// ( types-Object, data )
// 将data参数赋值为原本的selector( 这里selector可能传了实际的数据对象等) , 并将selector设为undefined, 符合 ( types-Object, data )的参数预期
data = data || selector ;
selector = undefined ;
}
// 遍历传入的types对象的每个属性( 也就是每个事件类型)
for ( type in types ) {
// 递归调用on方法, 逐个绑定每个事件类型及其对应的处理函数, 实现对多个事件的绑定
this . on ( type , selector , data , types [ type ] , one ) ;
}
// 返回当前的jQuery对象实例, 以便支持链式调用
return this ;
}
// 第二种情况: 如果data和fn都为null, 说明传入的参数形式可能是( types, fn )这种, 将fn赋值为原本的selector, 然后将data和selector都设为undefined来符合预期的参数格式
if ( data == null && fn == null ) {
// ( types, fn )
fn = selector ;
data = selector = undefined ;
} else if ( fn == null ) {
// 如果fn为null, 进一步判断selector的类型, 如果是字符串类型, 说明参数形式可能是( types, selector, fn ), 将fn赋值为原本的data, data设为undefined
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data ;
data = undefined ;
} else {
// ( types, data, fn )
// 如果selector不是字符串类型, 参数形式可能是 ( types, data, fn ), 将fn赋值为原本的data, 将data赋值为原本的selector, 再将selector设为undefined
fn = data ;
data = selector ;
selector = undefined ;
}
}
// 如果fn的值为false, 将fn指向一个名为returnFalse的函数( 这里returnFalse应该是在别处定义的, 可能返回false的函数, 用于特定的事件处理逻辑)
if ( fn === false ) {
fn = returnFalse ;
} else if ( ! fn ) {
// 如果fn为假值( 比如undefined、null等) , 直接返回当前的jQuery对象实例, 不进行事件绑定操作
return this ;
}
// 如果one参数的值为1, 表示只希望事件触发一次
if ( one === 1 ) {
origFn = fn ;
// 重新定义fn函数, 在事件触发时, 先移除对应的事件绑定( 通过off方法, 这里的jQuery()创建了一个空的jQuery对象集合, 但不影响off方法的调用逻辑, 因为off方法内部可以处理这种情况, 根据event中的信息来确定要移除的事件) , 然后再执行原本的事件处理函数( origFn)
fn = function ( event ) {
// Can use an empty set, since event contains the info
jQuery ( ) . off ( event ) ;
return origFn . apply ( this , arguments ) ;
} ;
// Use same guid so caller can remove using origFn
// 让新的fn函数继承原本origFn函数的唯一标识符( guid) , 如果origFn没有guid, 则生成一个新的guid, 保证调用者可以通过origFn来移除这个只触发一次的事件绑定
fn . guid = origFn . guid || ( origFn . guid = jQuery . guid ++ ) ;
}
// 遍历当前jQuery对象集合中的每个元素, 调用jQuery.event.add方法为每个元素添加指定的事件绑定, 传入事件类型、处理函数、数据以及选择器等参数
return this . each ( function ( ) {
jQuery . event . add ( this , types , fn , data , selector ) ;
} ) ;
} ,
// one方法是对on方法的一个简单封装, 用于方便地绑定只触发一次的事件, 直接调用on方法并传入参数1表示只触发一次
one : function ( types , selector , data , fn ) {
return this . on ( types , selector , data , fn , 1 ) ;
} ,
// off方法用于解绑事件, 同样有多种参数形式来应对不同的解绑场景
off : function ( types , selector , fn ) {
// 如果types参数是一个已经触发过的jQuery.Event对象( 它有preventDefault、handleObj等属性) , 说明是基于已触发事件对象来解绑对应的事件绑定
if ( types && types . preventDefault && types . handleObj ) {
// ( event ) dispatched jQuery.Event
// 获取事件对象中的handleObj属性, 它包含了事件相关的原始类型、命名空间、选择器以及处理函数等关键信息
var handleObj = types . handleObj ;
// 通过事件对象中的delegateTarget属性找到对应的元素( 可能是委托事件绑定的目标元素) , 然后调用off方法来解绑该元素上符合条件的事件, 条件包括原始事件类型、命名空间、选择器以及处理函数等信息
jQuery ( types . delegateTarget ) . off (
handleObj . namespace ? handleObj . origType + "." + handleObj . namespace : handleObj . origType ,
handleObj . selector ,
@ -3832,116 +3881,142 @@ jQuery.fn.extend({
) ;
return this ;
}
// 如果types参数是一个对象, 意味着可以传入多个事件类型及其对应的处理函数的映射形式来批量解绑事件
if ( typeof types === "object" ) {
// ( types-object [, selector] )
// 遍历传入的types对象的每个属性( 也就是每个事件类型)
for ( var type in types ) {
// 递归调用off方法, 逐个解绑每个事件类型及其对应的处理函数, 实现对多个事件的解绑
this . off ( type , selector , types [ type ] ) ;
}
return this ;
}
// 如果selector参数是false或者是一个函数类型, 说明可能传入的参数形式是( types [, fn] ), 将fn赋值为原本的selector, 然后将selector设为undefined来符合预期的参数格式
if ( selector === false || typeof selector === "function" ) {
// ( types [, fn] )
fn = selector ;
selector = undefined ;
}
// 如果fn的值为false, 将fn指向一个名为returnFalse的函数( 和on方法中类似的处理逻辑)
if ( fn === false ) {
fn = returnFalse ;
}
// 遍历当前jQuery对象集合中的每个元素, 调用jQuery.event.remove方法为每个元素移除指定的事件绑定, 传入事件类型、处理函数以及选择器等参数
return this . each ( function ( ) {
jQuery . event . remove ( this , types , fn , selector ) ;
} ) ;
} ,
// bind方法是对on方法的一种简化调用形式, 用于绑定事件, 它传入事件类型、数据以及处理函数, 将选择器参数设为null, 也就是不涉及选择器相关的复杂事件绑定场景
bind : function ( types , data , fn ) {
return this . on ( types , null , data , fn ) ;
} ,
// unbind方法是对off方法的一种简化调用形式, 用于解绑事件, 它传入事件类型以及处理函数, 将选择器参数设为null, 也就是不涉及选择器相关的复杂事件解绑场景
unbind : function ( types , fn ) {
return this . off ( types , null , fn ) ;
} ,
// live方法用于给当前元素的上下文( context) 绑定事件, 通过调用on方法, 传入事件类型、选择器( this.selector表示当前元素相关的选择器) 、数据以及处理函数等来实现, 实现一种类似事件委托的效果, 让符合选择器的后代元素可以响应事件
live : function ( types , data , fn ) {
jQuery ( this . context ) . on ( types , this . selector , data , fn ) ;
return this ;
} ,
// die方法用于移除通过live方法绑定的事件, 通过调用off方法, 传入事件类型以及选择器( 如果this.selector不存在则使用"**"作为通配符选择器,确保能移除相关的事件绑定)等来实现
die : function ( types , fn ) {
jQuery ( this . context ) . off ( types , this . selector || "**" , fn ) ;
return this ;
} ,
// delegate方法是对on方法的另一种调用形式, 用于进行事件委托绑定, 传入选择器、事件类型、数据以及处理函数等参数, 本质上就是调用on方法来完成事件委托相关的事件绑定操作
delegate : function ( selector , types , data , fn ) {
return this . on ( types , selector , data , fn ) ;
} ,
// undelegate方法用于移除通过delegate方法绑定的事件, 根据传入参数的个数来判断具体的解绑逻辑, 如果只传入一个参数, 说明可能是基于命名空间等来解绑, 调用off方法并传入相应参数; 如果传入多个参数, 就是常规的传入事件类型、选择器以及处理函数等参数来解绑对应的事件委托绑定
undelegate : function ( selector , types , fn ) {
// ( namespace ) or ( selector, types [, fn] )
return arguments . length == 1 ? this . off ( selector , "**" ) : this . off ( types , selector , fn ) ;
} ,
// trigger方法用于触发指定的事件, 遍历当前jQuery对象集合中的每个元素, 调用jQuery.event.trigger方法来触发传入的事件类型以及传递相关的数据, 触发每个元素上对应的事件
trigger : function ( type , data ) {
return this . each ( function ( ) {
jQuery . event . trigger ( type , data , this ) ;
} ) ;
} ,
// triggerHandler方法用于触发指定的事件, 但和trigger方法不同的是, 它只在第一个元素( this[0]) 上触发事件, 并且以一种特殊的方式触发( 最后一个参数true可能有特定的内部触发机制含义, 比如不进行事件冒泡等) , 如果存在第一个元素则调用jQuery.event.trigger方法触发事件并返回结果, 否则返回undefined之类的值( 因为没有元素可触发事件)
triggerHandler : function ( type , data ) {
if ( this [ 0 ] ) {
return jQuery . event . trigger ( type , data , this [ 0 ] , true ) ;
}
} ,
// toggle方法用于实现点击事件的切换效果, 也就是每次点击执行不同的函数
toggle : function ( fn ) {
// Save reference to arguments for access in closure
// 保存传入的所有参数,方便在闭包中访问
var args = arguments ,
guid = fn . guid || jQuery . guid ++ ,
i = 0 ,
toggler = function ( event ) {
// Figure out which function to execute
// 计算出当前应该执行的函数索引, 通过获取当前元素上存储的上一次执行的函数索引( 通过jQuery._data方法获取, 以"lastToggle" + fn.guid为键, 初始化为0, 如果不存在则默认为0) , 然后取余得到本次要执行的函数索引
var lastToggle = ( jQuery . _data ( this , "lastToggle" + fn . guid ) || 0 ) % i ;
// 更新当前元素上存储的上一次执行的函数索引, 自增1, 为下一次点击做准备
jQuery . _data ( this , "lastToggle" + fn . guid , lastToggle + 1 ) ;
// Make sure that clicks stop
// 阻止事件的默认行为, 比如阻止链接的跳转等( 如果是在点击链接等可触发默认行为的元素上绑定的toggle事件)
event . preventDefault ( ) ;
// and execute the function
// 执行对应的函数, 并返回执行结果, 如果结果为假值则返回false( 可能用于一些逻辑判断等)
return args [ lastToggle ] . apply ( this , arguments ) || false ;
} ;
// link all the functions, so any of them can unbind this click handler
// 让toggler函数继承传入函数的唯一标识符( guid) , 保证可以通过这个标识符来统一管理相关的事件绑定等操作
toggler . guid = guid ;
// 遍历所有传入的函数参数, 让每个函数都继承相同的唯一标识符( guid) , 使得它们可以作为一组来进行相关的事件绑定和管理
while ( i < args . length ) {
args [ i ++ ] . guid = guid ;
}
// 将toggler函数绑定到当前jQuery对象集合的点击事件( click) 上, 实现点击切换执行不同函数的效果
return this . click ( toggler ) ;
} ,
// hover方法用于方便地绑定鼠标移入( mouseenter) 和鼠标移出( mouseleave) 事件, 传入对应的处理函数, 如果只传入一个函数则同时作为鼠标移入和移出的处理函数, 通过链式调用分别调用mouseenter和mouseleave方法来绑定事件
hover : function ( fnOver , fnOut ) {
return this . mouseenter ( fnOver ) . mouseleave ( fnOut || fnOver ) ;
}
} ) ;
// 遍历一个包含众多常见事件名称的字符串,通过空格分割后得到的事件名称数组,对每个事件名称进行相关操作
jQuery . each ( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu" ) . split ( " " ) , function ( i , name ) {
// Handle event binding
// 为jQuery.fn对象( 也就是jQuery的原型对象) 添加以事件名称为属性名的方法, 用于方便地绑定对应事件
jQuery . fn [ name ] = function ( data , fn ) {
if ( fn == null ) {
fn = data ;
data = null ;
}
// 根据传入参数的个数来决定是绑定事件( 传入了data和fn参数) 还是触发事件( 只传入事件名称作为参数, 没有其他参数) , 如果传入参数个数大于0则调用on方法绑定事件, 否则调用trigger方法触发事件
return arguments . length > 0 ?
this . on ( name , null , data , fn ) :
this . trigger ( name ) ;
} ;
// 如果jQuery.attrFn对象存在( 可能是用于处理元素属性相关的一个对象, 和事件有一定关联等情况) , 在其上面标记当前事件名称对应的属性为true( 具体用途可能要看jQuery.attrFn在其他地方的使用逻辑)
if ( jQuery . attrFn ) {
jQuery . attrFn [ name ] = true ;
}
// 如果事件名称匹配键盘事件相关的正则表达式( rkeyEvent应该是在别处定义的用于判断键盘事件的正则) , 将当前事件名称对应的事件修复钩子( fixHooks, 用于在事件处理过程中进行一些兼容性等方面的修复操作) 指向jQuery.event.keyHooks( 应该是处理键盘事件的通用钩子函数等)
if ( rkeyEvent . test ( name ) ) {
jQuery . event . fixHooks [ name ] = jQuery . event . keyHooks ;
}
// 如果事件名称匹配鼠标事件相关的正则表达式( rmouseEvent应该是在别处定义的用于判断鼠标事件的正则) , 将当前事件名称对应的事件修复钩子( fixHooks) 指向jQuery.event.mouseHooks( 应该是处理鼠标事件的通用钩子函数等)
if ( rmouseEvent . test ( name ) ) {
jQuery . event . fixHooks [ name ] = jQuery . event . mouseHooks ;
}
@ -3955,57 +4030,72 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl
* Released under the MIT , BSD , and GPL Licenses .
* More information : http : //sizzlejs.com/
* /
// 立即执行函数,创建一个独立的作用域,避免变量污染全局环境
( function ( ) {
// 定义一个正则表达式用于分割选择器字符串,它能匹配各种复杂的选择器语法结构,例如括号包裹的内容、方括号包裹的内容、转义字符、普通字符等,并提取相关部分
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g ,
// 生成一个随机的扩展属性名称(用于在对象上添加自定义属性来做一些标记等操作,避免与其他属性冲突),通过在随机数基础上处理得到一个字符串
expando = "sizcache" + ( Math . random ( ) + '' ) . replace ( '.' , '' ) ,
done = 0 ,
// 获取Object原型上的toString方法的引用, 用于后续判断对象类型
toString = Object . prototype . toString ,
hasDuplicate = false ,
baseHasDuplicate = true ,
// 用于匹配转义字符的正则表达式
rBackslash = /\\/g ,
// 用于匹配回车换行符的正则表达式
rReturn = /\r\n/g ,
// 用于匹配非单词字符(比如标点符号等)的正则表达式
rNonWord = /\W/ ;
// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
// Thus far that includes Google Chrome.
// 这里检查JavaScript引擎是否使用了某种优化, 导致不总是调用我们的比较函数。如果是这种情况( 目前已知包括Google Chrome) , 则丢弃hasDuplicate的值。
// 通过对包含两个0的数组进行排序操作, 并在排序比较函数中修改baseHasDuplicate的值来检测这种情况
[ 0 , 0 ] . sort ( function ( ) {
baseHasDuplicate = false ;
return 0 ;
} ) ;
// 定义Sizzle函数, 它是主要的选择器解析和元素查找函数, 用于根据给定的选择器在指定的上下文中查找匹配的元素
var Sizzle = function ( selector , context , results , seed ) {
// 如果没有传入results参数, 则初始化为空数组, 用于存储查找结果
results = results || [ ] ;
// 如果没有传入context参数, 则默认为文档对象( document) , 表示查找的上下文范围
context = context || document ;
// 保存原始的查找上下文对象,方便后续可能的使用
var origContext = context ;
// 如果传入的上下文对象不是元素节点( nodeType为1表示元素节点, 9表示文档节点) , 则直接返回空数组, 因为无法在这样的对象中查找元素
if ( context . nodeType !== 1 && context . nodeType !== 9 ) {
return [ ] ;
}
// 如果没有传入选择器或者选择器类型不是字符串, 也直接返回results( 此时可能为空数组) , 不符合查找元素的基本要求
if ( ! selector || typeof selector !== "string" ) {
return results ;
}
// 定义一系列局部变量,用于后续在选择器解析和元素查找过程中的各种操作
var m , set , checkSet , extra , ret , cur , pop , i ,
prune = true ,
// 判断上下文是否是XML文档环境( 通过调用Sizzle.isXML方法来判断, 该方法应该在别处定义)
contextXML = Sizzle . isXML ( context ) ,
// 用于存储分割后的选择器各个部分,方便后续按顺序处理
parts = [ ] ,
soFar = selector ;
// Reset the position of the chunker regexp (start from head)
// 重置chunker正则表达式的匹配位置( 从开头开始匹配) , 然后通过循环不断用chunker正则表达式分割选择器字符串
do {
chunker . exec ( "" ) ;
m = chunker . exec ( soFar ) ;
if ( m ) {
// 更新剩余未处理的选择器部分
soFar = m [ 3 ] ;
// 将匹配到的选择器部分添加到parts数组中
parts . push ( m [ 1 ] ) ;
// 如果存在分隔符(表示后面还有更多选择器部分需要处理),提取额外的部分并跳出循环,后续会单独处理这部分
if ( m [ 2 ] ) {
extra = m [ 3 ] ;
break ;
@ -4013,30 +4103,34 @@ var Sizzle = function( selector, context, results, seed ) {
}
} while ( m ) ;
// 如果分割后的选择器部分数量大于1, 并且原始选择器匹配特定的位置相关模式( origPOS应该是在别处定义的用于判断位置相关选择器的正则或函数等)
if ( parts . length > 1 && origPOS . exec ( selector ) ) {
// 如果分割后的部分只有两个, 且第一个部分是相对位置选择器( Expr.relative应该是一个包含各种相对位置选择器相关信息的对象, 在别处定义)
if ( parts . length === 2 && Expr . relative [ parts [ 0 ] ] ) {
// 调用posProcess函数( 应该是处理位置相关选择器的函数, 在别处定义) 来处理这两个部分组成的选择器, 并获取匹配的元素集合
set = posProcess ( parts [ 0 ] + parts [ 1 ] , context , seed ) ;
} else {
// 如果第一个部分是相对位置选择器, 将初始的元素集合设为包含上下文对象的数组( 也就是从上下文开始查找) , 否则调用Sizzle函数( 递归调用自身) 查找第一个选择器部分对应的元素集合
set = Expr . relative [ parts [ 0 ] ] ?
[ context ] :
Sizzle ( parts . shift ( ) , context ) ;
// 循环处理剩余的选择器部分
while ( parts . length ) {
selector = parts . shift ( ) ;
// 如果当前选择器部分是相对位置选择器,将其与下一个部分合并(因为相对位置选择器通常要和后面的部分一起处理才有意义)
if ( Expr . relative [ selector ] ) {
selector += parts . shift ( ) ;
}
// 再次调用posProcess函数处理合并后的选择器, 并更新匹配的元素集合
set = posProcess ( selector , set , seed ) ;
}
}
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
// 如果选择器的根部分( 第一个部分) 是ID选择器( 并且满足一些其他条件, 比如不是内层选择器也是ID选择器等情况, 这样做可能是为了优化查找速度) , 则采取快捷方式先查找该ID对应的元素, 并更新上下文为找到的元素( 如果有的话)
if ( ! seed && parts . length > 1 && context . nodeType === 9 && ! contextXML &&
Expr . match . ID . test ( parts [ 0 ] ) && ! Expr . match . ID . test ( parts [ parts . length - 1 ] ) ) {
@ -4046,57 +4140,73 @@ var Sizzle = function( selector, context, results, seed ) {
ret . set [ 0 ] ;
}
// 如果存在上下文(也就是前面处理后找到了合适的查找起点)
if ( context ) {
// 如果传入了seed参数, 构建一个包含expr和set属性的对象, expr为剩余选择器部分的最后一个( 通过pop取出) , set为传入的seed( 可能是已经筛选过的元素集合等) ; 否则调用Sizzle.find函数查找最后一个选择器部分对应的元素集合( 根据不同情况传入合适的上下文等参数)
ret = seed ?
{ expr : parts . pop ( ) , set : makeArray ( seed ) } :
Sizzle . find ( parts . pop ( ) , parts . length === 1 && ( parts [ 0 ] === "~" || parts [ 0 ] === "+" ) && context . parentNode ? context . parentNode : context , contextXML ) ;
// 如果找到的结果中有expr属性( 表示可能经过了筛选等操作) , 则调用Sizzle.filter函数进一步过滤元素集合; 否则直接使用找到的元素集合
set = ret . expr ?
Sizzle . filter ( ret . expr , ret . set ) :
ret . set ;
// 如果还有剩余的选择器部分需要处理,则将当前的元素集合转为数组(方便后续循环处理)
if ( parts . length > 0 ) {
checkSet = makeArray ( set ) ;
} else {
// 如果没有剩余选择器部分了, 设置prune为false, 表示不需要进行后续的一些筛选操作( 具体要看后面的逻辑, 可能和元素集合的处理方式有关)
prune = false ;
}
// 循环处理剩余的选择器部分(从后往前处理,因为可能涉及到相对位置选择器等依赖后面元素的情况)
while ( parts . length ) {
cur = parts . pop ( ) ;
pop = cur ;
// 如果当前选择器部分不是相对位置选择器,将其设为空字符串(可能有特殊处理逻辑,比如当作普通选择器等情况)
if ( ! Expr . relative [ cur ] ) {
cur = "" ;
} else {
// 如果是相对位置选择器,取出下一个选择器部分(可能和当前相对位置选择器配合使用)
pop = parts . pop ( ) ;
}
// 如果取出的配合使用的选择器部分为null( 可能没有下一个部分了等情况) , 则将其设为上下文对象( 也就是当作相对当前上下文来处理)
if ( pop == null ) {
pop = context ;
}
// 调用相对位置选择器对应的处理函数( Expr.relative[cur]应该是一个函数, 根据不同的相对位置选择器有不同的处理逻辑) , 传入当前的元素集合、相关的参考元素以及是否是XML文档环境等参数, 来更新元素集合
Expr . relative [ cur ] ( checkSet , pop , contextXML ) ;
}
} else {
// 如果没有合适的上下文( 比如传入的上下文不符合要求等情况) , 将checkSet和parts都设为空数组, 后续可能会根据这个情况进行错误处理等操作
checkSet = parts = [ ] ;
}
}
// 如果checkSet为空( 可能前面处理过程中没有正确赋值等情况) , 则将其设为set( 也就是前面查找得到的元素集合)
if ( ! checkSet ) {
checkSet = set ;
}
// 如果checkSet仍然为空, 说明出现了错误, 调用Sizzle.error函数( 应该是用于输出错误信息等的函数, 在别处定义) , 传入当前的选择器部分或者整个选择器字符串来提示错误
if ( ! checkSet ) {
Sizzle . error ( cur || selector ) ;
}
// 判断checkSet的类型是否是数组( 通过调用toString方法并判断返回值是否是"[object Array]"来确定)
if ( toString . call ( checkSet ) === "[object Array]" ) {
// 如果不需要进行筛选操作( prune为false) , 直接将checkSet中的元素添加到results数组中( 通过apply方法将元素逐个添加, 模拟数组的pushAll操作)
if ( ! prune ) {
results . push . apply ( results , checkSet ) ;
} else if ( context && context . nodeType === 1 ) {
// 如果需要筛选, 并且上下文是元素节点, 循环遍历checkSet数组, 判断每个元素是否符合条件( 比如元素存在、是元素节点并且满足Sizzle.contains方法判断的包含关系等, Sizzle.contains应该是判断元素包含关系的函数, 在别处定义) , 符合条件的元素对应的set中的元素添加到results数组中
for ( i = 0 ; checkSet [ i ] != null ; i ++ ) {
if ( checkSet [ i ] && ( checkSet [ i ] === true || checkSet [ i ] . nodeType === 1 && Sizzle . contains ( context , checkSet [ i ] ) ) ) {
results . push ( set [ i ] ) ;
@ -4104,6 +4214,7 @@ var Sizzle = function( selector, context, results, seed ) {
}
} else {
// 如果需要筛选, 但上下文不是元素节点, 循环遍历checkSet数组, 只判断元素是否是元素节点, 是元素节点的对应的set中的元素添加到results数组中
for ( i = 0 ; checkSet [ i ] != null ; i ++ ) {
if ( checkSet [ i ] && checkSet [ i ] . nodeType === 1 ) {
results . push ( set [ i ] ) ;
@ -4112,74 +4223,105 @@ var Sizzle = function( selector, context, results, seed ) {
}
} else {
// 如果checkSet不是数组类型, 调用makeArray函数( 应该是将类数组对象等转为真正数组的函数, 在别处定义) 将其转为数组, 并将结果添加到results数组中
makeArray ( checkSet , results ) ;
}
// 如果存在额外的选择器部分(前面分割出来的,还未处理的部分)
if ( extra ) {
// 递归调用Sizzle函数, 传入额外的选择器部分、原始的上下文对象以及results数组( 用于继续添加查找结果) 和seed( 可能用于延续之前的查找逻辑等)
Sizzle ( extra , origContext , results , seed ) ;
// 调用Sizzle.uniqueSort函数( 应该是对结果数组进行去重和排序的函数, 在别处定义) 对results数组进行处理, 确保结果的唯一性和顺序性
Sizzle . uniqueSort ( results ) ;
}
// 返回最终的查找结果数组
return results ;
} ;
// 定义Sizzle对象的uniqueSort方法, 用于对结果数组进行去重和排序操作
Sizzle . uniqueSort = function ( results ) {
// 如果sortOrder存在( sortOrder应该是在别处定义的用于排序的规则相关变量, 可能是一个比较函数等, 用于确定元素的排序顺序)
if ( sortOrder ) {
// 恢复hasDuplicate的值为baseHasDuplicate的值( 前面可能在某些检测情况下对baseHasDuplicate进行了修改, 这里重新赋值给hasDuplicate, 用于后续判断是否有重复元素)
hasDuplicate = baseHasDuplicate ;
// 使用sortOrder规则对results数组进行排序, 改变数组内元素的顺序使其符合指定的排序要求
results . sort ( sortOrder ) ;
// 如果存在重复元素( hasDuplicate为真)
if ( hasDuplicate ) {
// 从索引为1开始遍历results数组( 因为比较重复是从第二个元素开始和前一个元素对比)
for ( var i = 1 ; i < results . length ; i ++ ) {
// 如果当前元素和前一个元素相等(说明是重复元素)
if ( results [ i ] === results [ i - 1 ] ) {
// 使用splice方法从数组中移除当前这个重复元素, 同时将索引i自减1, 因为移除元素后后面的元素会向前移动一位, 下次循环需要再次检查当前位置的元素是否和前一个重复
results . splice ( i -- , 1 ) ;
}
}
}
}
// 返回经过去重和排序后的results数组
return results ;
} ;
// 定义Sizzle对象的matches方法, 它通过调用Sizzle函数, 传入表达式、空的上下文、空的初始结果以及给定的元素集合( set) , 来判断给定的元素集合中的元素是否匹配指定的表达式( expr) , 本质上是利用Sizzle函数进行元素匹配的一种封装
Sizzle . matches = function ( expr , set ) {
return Sizzle ( expr , null , null , set ) ;
} ;
// 定义Sizzle对象的matchesSelector方法, 它通过调用Sizzle函数, 传入表达式、空的上下文、空的初始结果以及包含单个节点( node) 的数组, 然后判断返回的结果数组长度是否大于0, 以此来确定给定的节点是否匹配指定的表达式( expr) , 也就是判断单个节点是否符合特定的选择器要求的一种方式
Sizzle . matchesSelector = function ( node , expr ) {
return Sizzle ( expr , null , null , [ node ] ) . length > 0 ;
} ;
// 定义Sizzle对象的find方法, 用于查找与给定表达式( expr) 匹配的元素集合, 在指定的上下文( context) 中进行查找, 同时考虑是否是XML文档环境( isXML)
Sizzle . find = function ( expr , context , isXML ) {
var set , i , len , match , type , left ;
// 如果没有传入表达式( expr) , 直接返回空数组, 因为没有查找依据
if ( ! expr ) {
return [ ] ;
}
// 遍历Expr.order数组( Expr.order应该是在别处定义的, 包含了各种查找类型的顺序相关信息, 用于按顺序尝试不同的查找方式)
for ( i = 0 , len = Expr . order . length ; i < len ; i ++ ) {
type = Expr . order [ i ] ;
// 使用对应类型的左匹配正则( Expr.leftMatch[type], 不同的查找类型有不同的匹配正则表达式, 用于提取表达式中的关键部分) 来尝试匹配传入的表达式( expr) , 如果匹配成功
if ( ( match = Expr . leftMatch [ type ] . exec ( expr ) ) ) {
// 获取匹配到的左边部分(可能是选择器的关键标识部分等)
left = match [ 1 ] ;
// 移除匹配结果数组中的第二个元素(具体用途可能和不同查找类型的处理逻辑有关,这里不清楚具体原因,但从代码逻辑看是要去掉这个元素)
match . splice ( 1 , 1 ) ;
// 如果左边部分的最后一个字符不是转义字符(\),说明是正常的匹配情况
if ( left . substr ( left . length - 1 ) !== "\\" ) {
// 去除匹配结果数组中第二个元素( 经过前面的splice操作后, 这里的第二个元素对应的含义应该和具体查找类型相关, 可能是要去除一些转义相关的干扰等) 中的转义字符( 通过替换为空字符串的方式)
match [ 1 ] = ( match [ 1 ] || "" ) . replace ( rBackslash , "" ) ;
// 调用对应查找类型的查找函数( Expr.find[type], 不同类型有不同的查找逻辑实现, 在别处定义) , 传入处理后的匹配结果、上下文以及是否是XML文档环境等参数, 获取查找到的元素集合
set = Expr . find [ type ] ( match , context , isXML ) ;
// 如果查找到了元素集合( 不为null)
if ( set != null ) {
// 将表达式中已经匹配并处理过的部分替换为空字符串,也就是去掉已经查找过的部分,继续处理剩余的表达式部分
expr = expr . replace ( Expr . match [ type ] , "" ) ;
// 找到元素集合后就跳出循环,因为已经完成了本次查找任务
break ;
}
}
}
}
// 如果没有找到匹配的元素集合( set为null)
if ( ! set ) {
// 判断上下文对象是否有getElementsByTagName方法( 用于在非XML文档环境下获取所有标签名为*(也就是所有元素)的元素集合),如果有则调用该方法获取所有元素,否则返回空数组
set = typeof context . getElementsByTagName !== "undefined" ?
context . getElementsByTagName ( "*" ) :
[ ] ;
}
// 返回一个包含找到的元素集合( set) 以及剩余未处理的表达式( expr) 的对象, 方便后续可能的进一步处理( 比如继续基于剩余表达式筛选元素等)
return { set : set , expr : expr } ;
} ;