jQuery从入门到精通源码分析系列(七) : 筛选器 Sizzle引擎 - 位置伪类
沉沙 2018-07-10 来源 : 阅读 1075 评论 0

摘要:本篇jQuery教程通过代码讲解了 筛选器 Sizzle引擎的位置伪类,希望阅读本篇文章以后大家有所收获,帮助大家对jQuery的理解更加深入。

本章开始分析过滤器,根据API的顺序来

主要涉及的知识点

jQuery的组成

pushStack方法的作用

sizzle伪类选择器

首页我们知道jQuery对象是一个数组对象

内部结构

jQuery的选择最终还是依靠的DOM提供的接口,jQuery只是最了最佳的方式最快的匹配到合适的位置

构建一个基础的jQuery对象有:

元素合集

元素数量

上下文

通过pushStack()方法构建的prevObject的引用储存,这个在DOM操作的时候特别有用

选择器

具体文章前面有分析,pushStack有什么用处,sizzler如何最佳匹配,这里就不详说了,接下来看具体的操作

.eq( index )

如果一个jQuery对象表示一个DOM元素的集合,.eq()方法从集合的一个元素中构造新的jQuery对象。所提供的索引标识这个集合中的元素的位置。

根据jQuery的结构,这个很好处理了,返回选择器中的元素,数组索引从0开始的

eq: function( i ) {

    var len = this.length,

        j = +i + ( i < 0 ? len : 0 );

    return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );

},

这里有个细节,我们取DOM元素是索引,取出来的只是一个节点了,那么还要实现链式操作,我们都知道返回this就能链式,

但是jQuery的每一次操作都是可以回溯的,包括节点的遍历查找,所以这时候返回的当前的this是不行的,这样就需要构建一个新的上下文对象

li.eq(2).css('background-color', 'red');

所以取出来的DOM节点还要通过pushStack方法给包装一次

.first()

first: function() {

            return this.eq( 0 );

        },

.last()

last: function() {

    return this.eq( -1 );

},

可见first,last方法都是一路货,通过eq取索引罢了

.slice()

如果提供的jQuery代表了一组DOM元素,.slice()方法从匹配元素的子集中构造一个新的jQuery对象。

所提供的start索引标识的设置一个集合中的元素的位置;如果end被省略,这个元素之后的所有元素将包含在结果中

slice: function() {

    return this.pushStack( core_slice.apply( this, arguments ) );

},

其实就是

[].slice.apply(this,arguments )

这里的this是jq对象,根据参数转成数组子集

.map( callback(index, domElement) )

描述: 通过一个函数匹配当前集合中的每个元素,产生一个包含新的jQuery对象。

map有点类似each的赶脚,本质上还是有区别的

首先:

在设计上each是不可改变的迭代器,each方法就相当于js中的for循环,返回 'false' 将停止循环 (就像在普通的循环中使用 'break')

map的方法可以作为一个迭代器,但实际是为了操纵提供的数组并返回一个新数组。

map将一组元素转换成其他数组(不论是否是元素数组),你可以用这个函数来通过新规则(如过滤掉数字)来建立一个新列表,

不论是值、属性还是CSS样式,或者其他特别形式。这都可以用'$.map()'来方便的建立。

可见map除了迭代的功能还要返回一新的对象,所以潜在的有内存消耗

map: function( callback ) {
    return this.pushStack( jQuery.map(this, function( elem, i ) {
        return callback.call( elem, i, elem );
    }));
},
jQuery.map: function( elems, callback, arg ) {
            var value,
                i = 0,
                length = elems.length,
                isArray = isArraylike( elems ),
                ret = [];
            // Go through the array, translating each of the items to their
            if ( isArray ) {
                for ( ; i < length; i++ ) {
                    value = callback( elems[ i ], i, arg );
                    if ( value != null ) {
                        ret[ ret.length ] = value;
                    }
                }
                // Go through every key on the object,
            } else {
                for ( i in elems ) {
                    value = callback( elems[ i ], i, arg );
                    if ( value != null ) {
                        ret[ ret.length ] = value;
                    }
                }
            }
            // Flatten any nested arrays
            return core_concat.apply( [], ret );
        },

可见除了执行回调,还要收集返回值

value = callback( elems[ i ], i, arg );
if ( value != null ) {
    ret[ ret.length ] = value;
}

通过jQuery.map会返回一个新的迭代对象,然后又让pushStack方法包装一个新的堆上的元素集合(jQuery对象)

.filter()

选择器是个比较复杂的东东了,这里又要涉及到sizzle的处理了,顺便一起回顾以往的知识点,加以巩固

讲过滤器,就里不得不提一下.find(),虽然2者都是一个效果,但是处理有本质的区别

filter与find

jQuery官方的API这样说明filter和find函数:

filter(selector):  

     Description: Reduce the set of matched elements to those that match the selector or pass the function’s test.  

find(selector):  

    Description: Get the descendants of each element in the current set of matched elements, filtered by a selector.

find()会在当前指定元素中查找符合条件的子元素,是对它的子集操作,

filter()则是在当前指定的元素集合中查找符合条件的元素,是对自身集合元素进行筛选。

显而易见find()是对它的子集操作,filter()对自身集合元素筛选

find以后再说,先看看filter的实现

filter实例

官方给出的直接搬过来

<ul>

    <li>list item 1</li>

    <li>list item 2</li>

    <li>list item 3</li>

</ul>

li.filter(':even').css('background-color', 'red');

filter源码

第一步

可见winnow一定是返回一个数据对象,在通过pushStack包装

filter: function( selector ) {
    return this.pushStack( winnow(this, selector || [], false) );
},

第二步

jQuery.extend({
    filter: function( expr, elems, not ) {
        var elem = elems[ 0 ];
        if ( not ) {
            expr = ":not(" + expr + ")";
        }
        return elems.length === 1 && elem.nodeType === 1 ?
            jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
            jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
                return elem.nodeType === 1;
            }));
    },

filter是针对元素节点本身操作的,所以还要顾虑一下保证合集中都是elem.nodeType === 1的节点类型才行

第三步

方法最终还是靠sizzle去解析,传递一个符合解析的实参

传递了选择器与种子合集了,这里具体可以参考之前的sizzle系列文章,走的流程依然一样

通过词法分析器,解析出词法关系

Token:{  

   value:'匹配到的字符串',

   type:'对应的Token类型',

   matches:'正则匹配到的一个结构'

}

几种Token : TAG, ID, CLASS, ATTR, CHILD, PSEUDO, NAME,但是这里的type对应是一种伪类PSEUDO,这是在之前sizzle里面没有提到的

sizzle伪类

其实之前的文章分析了tokenize方法,把选择语句分解成分词

当然看这里需要结合之前的sizzle系列的篇幅了,比较复杂

Sizzle巧妙的就是它没有直接将拿到的“分词”结果与Expr中的方法逐个匹配逐个执行,而是先根据规则组合出一个大的匹配方法,最后一步执行

编译函数机制中最核心的一段

Sizzle.compile:

i = group.length;while ( i-- ) {
    cached = matcherFromTokens( group[i] );
    if ( cached[ expando ] ) {
        setMatchers.push( cached );
    } else {
        elementMatchers.push( cached );
    }
}// Cache the compiled function
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );

1 :matcherFromTokens方法,通过tokens生成匹配程序,它充当了selector“分词”与Expr中定义的匹配方法的串联与纽带的作用,可以说选择符的各种排列组合都是能适应的了

2: matcherFromGroupMatchers方法,通过返回curry的superMatcher方法执行

伪类如何生成最终的匹配器

matcherFromTokens源码核心部分

for ( ; i < len; i++ ) {
    if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
        matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
    } else {
        matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

可见根据分词器类型type的不同,会适配2组不同的处理方案

第一种是位置词素,之前就讲过,主要看下面一种

用Expr.filter的工厂方法来生成匹配器

每条选择器规则最小的几个单元可以划分为:ATTR | CHILD | CLASS | ID | PSEUDO | TAG

位置伪元素

每个子规则都有对应的匹配器,同样道理,位置伪类也有特殊的匹配器,它是由setMatcher工厂生成。

为了区分其他规则跟位置伪类,需要对位置伪类的过滤器匹配器等打个标记。

Sizzle源码里边用到的打标记方法

function markFunction( fn ) {

  fn[ expando ] = true;

  return fn;

}

位置伪元素共同的特点被createPositionalPseudo给curry一次了

function createPositionalPseudo(fn) {
    return markFunction(function(argument) {
        argument = +argument;
        return markFunction(function(seed, matches) {
            var j,
                matchIndexes = fn([], seed.length, argument),
                i = matchIndexes.length;
            // Match elements found at the specified indexes
            while (i--) {
                if (seed[(j = matchIndexes[i])]) {
                    seed[j] = !(matches[j] = seed[j]);
                }
            }
        });
    });
}

curry化 :它是一种通过把多个参数填充到函数体中,实现将函数转换成一个新的经过简化的(使之接受的参数更少)函数技术

当然也会形成闭包了,保存私有值在作用

所以这里很好理解,返回的函数其实最终是

function(argument) {
        argument = +argument;
        return markFunction(function(seed, matches) {
            var j,
                matchIndexes = fn([], seed.length, argument),
                i = matchIndexes.length;
            // Match elements found at the specified indexes
            while (i--) {
                if (seed[(j = matchIndexes[i])]) {
                    seed[j] = !(matches[j] = seed[j]);
                }
            }
        });
    });

所以总结,针对(位置型的)伪筛选:first/:last/:eq/:even/:odd/:It/:gt,都是做了单独的处理,

然后过滤器还有别的比如:not,:has,:contains 等等,其实基础的流程处理都差不多,只是针对不通的情况分别hack

在初始化的时候就:

1 返回新的curry函数

2 打上标记markFunction

回到主线方法:

Expr.filter.PSEUDO

"PSEUDO": function( pseudo, argument ) {

    // pseudo-class names are case-insensitive

    // //www.w3.org/TR/selectors/#pseudo-classes

    // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters

    // Remember that setFilters inherits from pseudos

    var args,

        fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||

            Sizzle.error( "unsupported pseudo: " + pseudo );

    // The user may use createPseudo to indicate that

    // arguments are needed to create the filter function

    // just as Sizzle does

    if ( fn[ expando ] ) {

        return fn( argument );

    }

The user may use createPseudo to indicate that

其实显而易见了,fn执行的正是上面

"even": createPositionalPseudo(function( matchIndexes, length ) {
    var i = 0;
    for ( ; i < length; i += 2 ) {
        matchIndexes.push( i );
    }
    return matchIndexes;
}),

执行继续嵌套形成的curry 函数了,在返回一个curry方法,给新方法继续打上一个标记

function( seed, matches ) {
    var j,
        matchIndexes = fn( [], seed.length, argument ),
        i = matchIndexes.length;
    // Match elements found at the specified indexes
    while ( i-- ) {
        if ( seed[ (j = matchIndexes[i]) ] ) {
            seed[j] = !(matches[j] = seed[j]);
        }
    }
}

所以最终的匹配器方法贼恶心的,嵌套了4,5层作用域

matcher :

为什么要写这么复杂?因为可以合并不同的参数传递

其实sizzle就是核心处理机制是不变的,只是针对不同的分支做了不同的处理方式,当然写东西整合在一起就显得尤为的复杂了

下一章在深入位置伪类特有的 setMatcher 匹配器工厂


本文由职坐标整理并发布,了解更多内容,请关注职坐标WEB前端jQuery频道!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程