sizzle分析记录:分解流程

<form>
  <label>Name:</label>
  <input name="name" />
  <fieldset>
      <label>Newsletter:</label>
      <div name="newsletter" /><p>1<p</div>
      <div name="letter" /><p name=‘aaron‘>2<p></div>
      <div name="tter" /><p>3<p</div>
 </fieldset>
</form>

js

$("form div > p[name=aaron]")

解析的流程:

编译器:分5个步骤

涉及: TAG元素 关系选择器 属性选择器

1:通过tokenize词法分析器分组



2:遍历tokens,从右边往左边开始筛选,最快定位到目标元素合集

//先看看有没有搜索器find,搜索器就是浏览器一些原生的取DOM接口,简单的表述就是以下对象了
            // Expr.find = {
            // ‘ID‘    : context.getElementById,
            // ‘CLASS‘ : context.getElementsByClassName,
            // ‘TAG‘   : context.getElementsByTagName
            //        }

操作如下

Expr.find["TAG"] = support.getElementsByTagName ?
    function( tag, context ) {
        if ( typeof context.getElementsByTagName !== strundefined ) {
            return context.getElementsByTagName( tag );
        }
    } :

那么第一筛选找到的定位元素,就形成了一个 seed种子合集,那么余下的所有的操作都是围绕这个种子合集处理

因为节点总是存在各种关系的,所以不管是通过这个最靠近的目标的元素,往上还是往下 都是可以处理的



3:重组选择器,开始执行继续分解"form div > [name=aaron]"

因为种子合已经抽出了,所以选择器就需要重新排列

"form div > [name=aaron]"

踢掉了P元素,已经被抽离了



4 : 生成编译处理器

这里为什么要这么复杂,因为生成了编译闭包可以缓存起来,通过这种机制,增加了重复选择器的效率

在matcherFromTokens方法中通过分解tokens生成对应的处理器

例如:form div [name=aaron]

在分解过程中分2大块

A:关系选择器的处理  > + ~ 空

B: ATTR CHILD CLASS ID PSEUDO TAG的处理

用matchers保留组合关系

1:分解第一个TAG:form 保存处理器到matchers.push( Expr.filter[“TAG”]) ;

2:分解第二个“空”的关系选择器,此时

A:用elementMatcher把之前的matchers压入到这个匹配里面,生成一个遍历方法的处理

function elementMatcher( matchers ) {
    return matchers.length > 1 ?
        function( elem, context, xml ) {
            var i = matchers.length;
            while ( i-- ) {
                if ( !matchers[i]( elem, context, xml ) ) {
                    return false;
                }
            }
            return true;
        } :
        matchers[0];
}

B:用addCombinator再次包装,生成一个位置关系的查找关系

function addCombinator( matcher, combinator, base ) {
    var dir = combinator.dir,
        checkNonElements = base && dir === "parentNode",
        doneName = done++;

    return
        // Check against all ancestor/preceding elements
        // 检查所有祖先/元素
        function( elem, context, xml ) {
            var oldCache, outerCache,
                newCache = [ dirruns, doneName ];
                while ( (elem = elem[ dir ]) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        outerCache = elem[ expando ] || (elem[ expando ] = {});
                        if ( (oldCache = outerCache[ dir ]) &&
                            oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

                            // Assign to newCache so results back-propagate to previous elements
                            return (newCache[ 2 ] = oldCache[ 2 ]);
                        } else {
                            // Reuse newcache so results back-propagate to previous elements
                            outerCache[ dir ] = newCache;

                            // A match means we‘re done; a fail means we have to keep checking
                            if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
                                return true;
                            }
                        }
                    }
                }
        };
}

所以此时的matchers的关系是一个层级的包含结构,然后依次这样递归

这个地方相当绕!!!!

生成的最后

cached = matcherFromTokens( match[i] );

变成了一个超大的嵌套闭包



5: 通过matcherFromGroupMatchers这个函数来生成最终的匹配器

var bySet = setMatchers.length > 0,
        byElement = elementMatchers.length > 0,

        superMatcher = function(seed, context, xml, results, outermost) {
            //分解这个匹配处理器
        }

    return superMatcher

通过matcherFromGroupMatchers的处理最直接的就是能看出,elementMatchers, setMatchers 2个结果不需要再返回出去,直接形成curry的方法,在内部就合并参数

外面就直接调用了,这样

var compileFunc = compiled || compile( selector, match );

compileFunc(
    seed,
    context,
    !documentIsHTML,
    results,
    outermost
);

compileFunc 一直是持有elementMatchers, setMatchers 的引用的,这个设计的手法还是值得借鉴的



执行期:

至此之前的5个步骤都是编译成函数处理器的过程,然后就是开始执行了

粗的原理就是把直接分解出来的seed种子合集丢到这个处理器中,然后处理器就会根据各种关系进行分解匹配

从而得到结果集

superMatcher:

while ( (matcher = elementMatchers[j++]) ) {
    if ( matcher( elem, context, xml ) ) {
        results.push( elem );
        break;
    }
}

抽出第一个seed元素,p

然后把p丢到atrr是过滤筛选器中去匹配下,看看是否能找到对应的这个属性

当然还是继续从右往左边匹配过滤了

一次是【name=aaron】 => div => from

matchers[i] => Expr.filter.ATTR =>

p.getAttribute(‘name=aaron’) => 得到结果

function elementMatcher( matchers ) {
    return matchers.length > 1 ?
        function( elem, context, xml ) {
            var i = matchers.length;
            while ( i-- ) {
                if ( !matchers[i]( elem, context, xml ) ) {
                    return false;
                }
            }
            return true;
        } :
        matchers[0];
}

如果匹配失败,自然就退出了  return false ,就不需要在往前找了 ,然后再次递归seed

如果成功,就需要再深入的匹配了

因为是从右到左逐个匹配,所以往前走就会遇到关系选择器的问题,

那么jQuery把四种关系 > + ~ 空的处理给抽出一个具体的方法就是addCombinator

1 "form div > p[name=aaron]"
2 seed => p
3 筛选[name=aaron]
4 > => addCombinator方法 找到对应关系映射的父节点elem
5 elem中去匹配div 递归elementMatcher方法
6 “空” =>  addCombinator方法找到祖先父节点elem
7 elem中去找form为止
 
可见这个查找是及其复杂繁琐的

总结:

sizzle对选择器的大概是思路:

分解所有的选择器为最小单元,从右往左边开始挑出一个浏览器的API能快速定位的元素TAG,ID,CLASS节点,这样就能确定最终的元素跟这个元素是有关系的

然后把剩余的选择器单元开始生成一个匹配器,主要是用来做筛选,最后根据关系分组

如果就依次匹配往上查找,通过关系处理器定位上一个节点的元素,通过普通匹配器去确定是否为可选的内容

sizzle分析记录:分解流程

时间: 2024-10-31 22:53:01

sizzle分析记录:分解流程的相关文章

sizzle分析记录:关于querySelectorAll兼容问题

querySelector和querySelectorAll是W3C提供的新的查询接口 目前几乎主流浏览器均支持了他们.包括 IE8(含) 以上版本. Firefox. Chrome.Safari.Opera. 万能的sizzle在高版本的浏览器中复杂的选择器尽量走querySelectorAll,前提是这个匹配的节点没有兼容问题 从IE8开始虽然支持querySelectorAll的API,但是会有各式各样的BUG,所以sizzle拿rbuggyQSA用来记录这个BUG问题 if ( supp

sizzle分析记录:getAttribute和getAttributeNode

部分IE游览器下无法通过getAttribute取值? <form name="aaron"> <input type="text" name="aaron"/> </form> alert(form.getAttribute('name')); IE6.7中错误 alert(form.getAttributeNode('name').nodeValue); 看看jQ的解决方案 能力判断 support.att

sizzle分析记录: 自定义伪类选择器

可见性 :hidden :visible 隐藏对象没有宽高,前提是用display:none处理的 jQuery.expr.filters.hidden = function( elem ) { // Support: Opera <= 12.12 // Opera reports offsetWidths and offsetHeights less than zero on some elements return elem.offsetWidth <= 0 && elem

sizzle分析记录:属性选择器

源码部分 通过Sizzle.attr匹配出值 然后通过表达式刷选计算 "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } resu

leveldb源码分析--插入删除流程

由于网络上对leveldb的分析文章都比较丰富,一些基础概念和模型都介绍得比较多,所以本人就不再对这些概念以专门的篇幅进行介绍,本文主要以代码流程注释的方式. 首先我们从db的插入和删除开始以对整个体系有一个感性的认识,首先看插入: Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) { WriteBatch batch; //leveldb中不管单个插入还是多个插入都是以Wri

Okhttp源码分析--基本使用流程分析

Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Request.Builder() .get() .url("www.baidu.com") .build(); Call call =okHttpClient.newCall(request).execute(); 异步请求 OkHttpClient okHttpClient=new OkH

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二)

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二) 1.通过mutate(put)操作,将单个put操作添加到缓冲操作中,这些缓冲操作其实就是Put的父类的一个List的集合.如下: private List<Row> writeAsyncBuffer = new LinkedList<>(); writeAsyncBuffer.add(m); 当writeAsyncBuffer满了之后或者是人为的调用backgroundFlushCommits操作促使缓冲池中的

日志分析记录

set() - set()  去掉公共部分 set存放的元素不能重复 list存放的元素可以重复 logging 模块 定义日志格式 import logging logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message') 屏幕上打印: WARNING:root:This is warning message 级别最高

openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的flex_array结构体成员变量的含义结论: struct { int element_size; // 这是flex_array_part结构体存放的哈希头指针的大小 int total_nr_elements; // 这是全部flex_array_part结构体中的哈希头指针的总个数 int e