Sizzle.filter [ 源码分析 ]

最近一直在研究Sizzle选择器,对于其中的原理确实不得不佩服!

Sizzle中的filter方法,主要负责块表达式过滤元素集合,在方法内部会调用Sizzle.selector.fitler方法执行过滤操作。

Sizzle.filter主要分5个关键步骤:

1 使用LeftMatch确定表达式类型。

2 调用Sizzle.selectors.preFilter预过虑函数,执行过滤前的修正。

3 调用Sizzle.selectors.filter[ type ] 中对应的过滤函数,执行过滤操作,如果返回false,刚将元素集合中的对应位置的元素替换为false;

4 删除表达式中已过滤的部分

5 重复1 - 4 步骤

源码如下:

Sizzle.filter = function( expr, set, inplace, not ) {
	//expr 块表达式
	//set 待过滤的元素集合
	//inplace 是否替换,如果为true,那么当set中的元素与expr不匹配时,刚将不匹配的元素替换为false,
	//否则,构造新的数组,只保留匹配的元素
	//not: 如果为true,则去掉匹配元素,保留不匹配元素,如果为false,则保留匹配元素,去掉不匹配元素
	var match, anyFound,
		type, found, item, filter, left,
		i, pass,
		old = expr,
		result = [],
		curLoop = set,
		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
	//while 循环,无限循环用expr过滤set元素,直到expr为空,因为在最后会将已经匹配过的部分删除,所以expr会越来越短
	while ( expr && set.length ) {
		for ( type in Expr.filter ) {
			//这里用来确定表达式类型,根据leftMatch正则
			//这里是match正则,LeftMatch只是要该正则加上了前缀和后缀
			/*
			match: {
				ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
				CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
				NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
				ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
				TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
				CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
				POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
				PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
			},
			*/

			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
				filter = Expr.filter[ type ];
				//确定前缀
				left = match[1];

				anyFound = false;

				match.splice(1,1);
				//这里官方说是匹配 \\ 如果是 \\ 那么表示后面的元素都是被转义,刚不需要再去过滤,
				//不过个人觉得这个地方似乎无效的  left.subster(left.length - 1)
				//这个字符的长度只会是1 怎么会和 \\ 相等呢
				if ( left.substr( left.length - 1 ) === "\\" ) {
					continue;
				}
				//重置result为空数组,用于缩小候选集,result用来存放通过过滤的元素
				if ( curLoop === result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					//这里会进行预过滤操作,主要对表达式进行修改正,如转义\\,取出空格等
					//针对不同和类型,会有不同的方式,详情可以见preFilter方法
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
					/*match:
						1 true: 继续需要执行过滤,尚不到执行过虑函数的时候,直接执行continue,不再去调用filter方法。如pos,child
						2 false: 已经执行了过滤,缩小了候选集如:CLASS
						3 字符串: 修正之后的过滤参数,后面会继续调用对应的过虑函数
					*/

					if ( !match ) {
						anyFound = found = true;

					} else if ( match === true ) {
						continue;
					}
				}
				//返回的是修正的过滤参数,刚继续执行
				if ( match ) {
					//遍历候选集中的元素
					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
						//元素存在
						if ( item ) {
							//对其进行过滤,返回结果
							found = filter( item, match, i, curLoop );
							//这里与 not 进行 异或 操作
							pass = not ^ found;
							//为替换模式,并且found 不为空
							if ( inplace && found != null ) {
								//如果通过,那么为true
								if ( pass ) {
									anyFound = true;
								} else {
									//否则将元素替换为false
									curLoop[i] = false;
								}

							} else if ( pass ) {
								//这要需要注意的是既然pass为真了, 那么就不需要判断了found,因为pass为found与not异或的结果
								//如果通过了,并且为非替换模式,那么将元素放入到新的数组result中
								result.push( item );
								anyFound = true;
							}
						}
					}
				}
				//found不为undefined
				if ( found !== undefined ) {
					if ( !inplace ) {
						//非替换模式,将结果复制给curLoop,
						curLoop = result;
					}
					//删除已经过滤过了的部分表达式
					expr = expr.replace( Expr.match[ type ], "" );
					//不匹配,返回空
					if ( !anyFound ) {
						return [];
					}
					//这里的break,说明已经进行过一次过虑,已经找到对应的type,可以选择跳出当前type的循环,
					break;
				}
			}
		}

		// 这里主要是匹配,如果最后过滤的表达式没有变化,那么认为过滤表达式有问题
		if ( expr === old ) {
			if ( anyFound == null ) {
				Sizzle.error( expr );

			} else {
				break;
			}
		}
		//备份expr,继续循环
		old = expr;
	}
	//返回过滤的结果
	return curLoop;
};
时间: 2024-10-11 11:43:07

Sizzle.filter [ 源码分析 ]的相关文章

jQuery Sizzle 入口 [ 源码分析 ]

var Sizzle = function( selector, context, results, seed ) { //context 默认为document,可以人为指定 results = results || []; context = context || document; var origContext = context; //判断文档节点 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return [

jQuery Sizzle.find [ 源码分析 ]

Sizzle.find = function( expr, context, isXML ) { var set, i, len, match, type, left; //expr是否为空 if ( !expr ) { return []; } for ( i = 0, len = Expr.order.length; i < len; i++ ) { type = Expr.order[i]; //根据Expr.order,即Sizzle.selectors.order 的四种类型:ID,C

Struts2 源码分析——过滤器(Filter)

章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析——核心机制)里的机制图片来分析源码.如果还不明白核心机制的朋友,请转到对应的章节进行阅读.笔者为了方便读者阅读,也把图片在次贴到了本章中.如下 根据图片笔者就明白我们首要分析便是橙黄色(Servlet Filters).也就是传说的过滤器(Filter).相信看过笔者前面几个章节的读者都明白strut

CloudStack核心类ApiServlet、ApiServer、ApiDispatcher、GenericDaoBase源码分析

ApiServlet 首先从整体上看下ApiServlet,Outline视图如下, 一.注意@Inject依赖的是javax.inject.jar,它和spring的@Autowired的区别在于使用它时变量不用生成相应的set方法. 二.CloudStack所有的请求都会被ApiSerlet拦截处理,进入到doGet()或者doPost()方法,然后统一交由processRequest()处理. 三.processRequestInContext()方法: 1.更多的是日志记录和异常信息处理

jQuery 2.1.4版本的源码分析

jquery中获取元素的源码分析 jQuery.each({// 获取当前元素的父级元素 parent: function(elem) { var parent = elem.parentNode;//nodeType为11的节点类型是DocumentFragment return parent && parent.nodeType !== 11 ? parent : null; },//获取所有的 父节点 这涉及到 dir 方法 parents: function(elem) { ret

keystone源码分析(一)——Paste Deploy的应用

本keystone源码分析系列基于Juno版Keystone,于2014年10月16日随Juno版OpenStack发布. Keystone作为OpenStack中的身份管理与授权模块,主要实现系统用户的身份认证.基于角色的授权管理.其他OpenStack服务的地址发现和安全策略管理等功能.Keystone作为开源云系统OpenStack中至关重要的组成部分,与OpenStack中几乎所有的其他服务(如Nova, Glance, Neutron等)都有着密切的联系.同时,Keystone作为开源

storm操作zookeeper源码分析-cluster.clj

storm操作zookeeper的主要函数都定义在命名空间backtype.storm.cluster中(即cluster.clj文件中).backtype.storm.cluster定义了两个重要protocol:ClusterState和StormClusterState.clojure中的protocol可以看成java中的接口,封装了一组方法.ClusterState协议中封装了一组与zookeeper进行交互的基础函数,如获取子节点函数,获取子节点数据函数等,ClusterState协

Spark SQL Catalyst源码分析之Physical Plan 到 RDD的具体实现

接上一篇文章Spark SQL Catalyst源码分析之Physical Plan,本文将介绍Physical Plan的toRDD的具体实现细节: 我们都知道一段sql,真正的执行是当你调用它的collect()方法才会执行Spark Job,最后计算得到RDD. lazy val toRdd: RDD[Row] = executedPlan.execute() Spark Plan基本包含4种操作类型,即BasicOperator基本类型,还有就是Join.Aggregate和Sort这种

Spark SQL Catalyst源码分析之TreeNode Library

前几篇文章介绍了Spark SQL的Catalyst的SqlParser,和Analyzer,本来打算直接写Optimizer的,但是发现忘记介绍TreeNode这个Catalyst的核心概念,介绍这个可以更好的理解Optimizer是如何对Analyzed Logical Plan进行优化的生成Optimized Logical Plan,本文就将TreeNode基本架构进行解释. 一.TreeNode类型 TreeNode Library是Catalyst的核心类库,语法树的构建都是由一个个