jQuery自定义选择器

jQuery自定义选择器

本文介绍了jQuery自定义选择器,“未定义"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使jQuery ui日历在单击日期时进行ajax调用,但是几天前我遇到了一个问题.我发现了应该执行此操作的代码片段,但是正如我发现的那样,它使用了jQuery自定义选择器.代码给了我一个错误,所以我开始深入研究自定义选择器以了解有关它们的更多信息.到目前为止,我还无法找出为什么会出现这种奇怪的行为.

i am trying to make a jQuery ui calendar make ajax calls when a date is clicked on,but i ran into a problem a few days ago.I found a snippet of code that supposedly does this , but as i found out it uses jQuery custom selectors. The code gave me an error so i started digging into the custom selectors to find out more about them. So far i haven't been able to find out why i get this strange behavior.

这是一张希望可以解决问题的图片,我将在之后解释更多

Here is a picture to hopefully clear things up , i will explain more after it

我已经在控制台中输入

$('.ui-datepicker-calendar td a:test(3)')

正如您所见,我meta2和stack2是未定义的,还有另外一件奇怪的事,为什么index2返回一个#document,它应该包含元素数组的索引.

And as you see i meta2 and stack2 are undefined and one more strange thing , why does index2 return a #document , its supposed to contain the index of the array of elements.

此外,元素(el2)甚至都不是正确的元素.看一看,我打电话

Moreover the element (el2) is not even the right element.Take a look , i call

$('.ui-datepicker-calendar td a:test(3)')

这应该从日历中选择所有日期,并且在第一个循环中,console.log应该将其打印出来

this is supposed to select all the dates from the calendar, and in the first loop , console.log should print out this

<td class=" ui-datepicker-week-end " data-handler="selectDay" data-event="click" data-month="8" data-year="2012"><a class="ui-state-default" href="#">1</a></td>

但是我得到了整个文档中的第一个"a"标签,在这种情况下,它是上个月的标签(如图所示).

but instead i get the the first "a" tag in the whole document, in this case its the one for the previous month( as seen in the picture ).

如果有人可以稍微了解一下这种情况,请这样做.哦,还有我要避免的一件事

If anyone can shed a little light on this situation , please do.Oh and one more thing i forgout about

meta2,它应该包含这个

meta2 , its supposed to contain this

[
    ':test(argument)', // full selector
    'test',            // only selector
    '',                // quotes used
    'argument'         // parameters
]

再次是我的未定义...

and again in my case its undefined...

我将分享我的JavaScript代码,希望对您有帮助

I will share my javascript code i hope it helps

<script>
    $(function()
    {
        $.expr[":"].test = function(el2,index2,meta2,stack2)
        {
            debugger;
            console.log(el2);
            console.log(index2);
            console.log(meta2);
            console.log(stack2);
        }
    })

    $(function()
    {
        function getJsonDate(year, month)
        {
            $.getJSON('dates.php?year='+year+'&month='+month, function(data)
            {
                var i = 0;
                for (i = 0; i < data.data.length; i++)
                {
                    debugger;
                    var myDay = data.data[i]['d'];
                    $('.ui-datepicker-calendar td a:exactly('+data.data[i]['d']+')')
                    .css({color: '#f00'})
                    .attr('href',data.data[i]['link'])
                    .parent().attr('onclick','');
                }
            });
        }
        $.expr[":"].exactly = function(el, index, meta, stack)
        {
            debugger;
            console.log(el);
            console.log(index);
            console.log(meta);
            console.log(stack);
            var s = meta[3];
            if (!s) return false;
            return eval("/^" + s + "$/i").test($(el).text());
        };
        $('#datepicker').datepicker(
        {
            inline: true,
            onSelect: function(dateText, inst)
            {
                Date.prototype.toString = function () {return isNaN (this) ? 'NaN' : [this.getDate(), this.getMonth(), this.getFullYear()].join('/')}
                d = new Date(dateText);
                getJsonDate(d.getFullYear(), d.getMonth()+1);
            },
            onChangeMonthYear: function(year, month, inst)
            {
                //alert(year);
                //alert(month);
                getJsonDate(year, month);
            }
        });
    });
</script>

推荐答案

最简短的解释是"jQuery 1.8.0中存在错误,请升级至1.8.1以进行修复",但这并不能完全解决所有问题.

The shortest explanation is "jQuery 1.8.0 has a bug in it, upgrade to 1.8.1 for the fix", but that doesn't quite answer everything.

jQuery 1.8.x具有一个显着升级的"Sizzle"引擎,它是用于选择器的.自定义选择器的调用方式已作为此更改的一部分进行了更改,但此外,许多选择器的处理方式也已更改.有关规则处理顺序的各种假设不再成立,等等.在各种用例中,处理速度也显着提高.

jQuery 1.8.x has a significantly upgraded "Sizzle" engine, which is what it uses for selectors. The way custom selectors are called has been altered as part of this change, but moreover the way a lot of selectors are processed have been altered. Various assumptions about the order in which rules are processed no longer hold true, etc. It's also significantly faster in various use-cases.

即使升级到1.8.1,在处理提供的示例时,您仍然会看到与1.7.2(1.8.x之前的系列中的最新版本)的方式有很大不同.这解释了您在选择页面上的第一个< a>元素"时看到的内容.就是说:您对自定义选择器如何工作的期望不是它们实际如何工作,并且如果您允许循环继续进行(而不是在第一次迭代中调用"debugger;"),您会发现它实际上正在经历 all < a>元素).简而言之:Sizzle不能保证各种规则将被调用的顺序,只是结果会匹配所有规则.

Even when upgrading to 1.8.1, you'll still see things look quite a bit different from the way they did in 1.7.2 (the latest in the pre-1.8.x series) when processing the example you provided. This explains what you're seeing in the selection of "the first <a> element on the page". That is to say: your expectation of how custom selectors work is not how they actually work, and if you allowed the loop to continue (rather than calling 'debugger;' at the first iteration), you'd see that it's actually going through all <a> elements). In short: Sizzle doesn't guarantee what order the various rules are going to be called, only that the result will match all of them.

如果您确定自定义规则的效率将比其他规则低(也许是因为您确定其他规则会严重减少匹配元素的数量),则可以先选择它们,然后再强制它们运行仅在该元素子集上调用.find(),例如:

If you are certain that your custom rule will be less-efficient than other rules (perhaps because you are certain that other rules can severely reduce the number of matched elements), you can force these to run first by selecting them, then calling .find() on just that subset of elements, eg:

$(".ui-datepicker-calendar").find("td a:test(3)");

关于未定义的堆栈",正如Kevin B指出的那样,尽管1.8.1更新恢复了向后兼容性,但API发生了变化,并且看起来堆栈"不再被传递给伪指令了. .实际上,这是有道理的,因为可以更改测试的顺序.也就是说:堆栈在到达时是空的,因为处理任何< a>元素是否与该伪选择器匹配"是处理的第一条规则.测试应该始终是独立的,因此无论如何堆栈都不会真正有用(可能只会导致混乱).

As for the "stack" being undefined, as Kevin B points out, though the 1.8.1 update restores backwards-compatibility, the API has changed, and it looks like "stack" is simply no-longer passed into the pseudo. This makes sense, really, due to the altered order in which tests may be called. That is: the stack is empty at the time you reach it, because "see if any of the <a> elements match this pseudo-selector" is the first rule that gets processed. Tests should always be self-contained, so the stack wouldn't really be very useful anyway (may only lead to confusion).

因此,如果1.8.1恢复了向后兼容,那么创建伪选择器的向前兼容方法是什么?正如您在 Sizzle的伪选择器的文档中看到的,从jQuery 1.8开始,创建伪选择器的首选方法是"createPseudo"方法($ .expr.createPseudo),它更喜欢使用闭包而不是"meta"参数.因此,对于您的特定示例,执行这些操作的新"方法将是:

So if 1.8.1 restores backwards-compatibility, then what is the forwards-compatible method for creating pseudo-selectors? As you can see in the documentation for Sizzle's pseudo-selectors, the preferred method for creating pseudo-selectors as of jQuery 1.8 is the "createPseudo" method ($.expr.createPseudo), which prefers to use a closure instead of the "meta" argument. So for your particular examples, the "new" ways of doing them would be:

$.expr[":"].test = $.expr.createPseudo(function( tomatch )
{
        return function( el2 )
        {
            debugger;
            console.log(el2); // not much else to see here
        };
})

其中"tomatch"是:test(...)选择器的参数.如您所见,在这种新语法中,不再需要您要查找的额外参数.至于更有用的东西:

where "tomatch" is the argument to the :test(...) selector. As you can see, the extra arguments you were looking for are simply no longer necessary in this new syntax. As for something a bit more useful:

$.expr[":"].exactly = $.expr.createPseudo(function( s )
{
    return function(el)
    {
        if(!s) return false;
        return eval("/^" + s + "$/i").test($(el).text());
    };
});

此版本的完全"应该与1.8+兼容,并且是首选的做事方法.

This version of "exactly" should be compatible with 1.8+, and is the preferred* method of doing things.

我认为,即使在jQuery版本/api中遇到了麻烦,您提供的代码仍然无法完全实现您想要的功能,因为datepicker可能会根据插件的想法而重新构建.仍然有一小段时间可以告诉您所需的元素确实按预期突出显示,因此选择器本身确实可以正常工作.

I think that even with the bump in jQuery version / api, the code you provided still won't do exactly what you want, as the datepicker is liable to be rebuilt on the whim of the plugin. There is still a brief moment when you can tell that the desired elements are indeed highlighted as intended, so the selector itself does appear to be working.

下面是一个完整的示例,该示例基于您提供的示例.通过更改1.7.2、1.8.0和1.8.1之间使用的jQuery版本,可以看到行为上的差异.为了实现跨版本的兼容性,已在伪选择器函数分配中添加了$ .expr.createPseudo测试.注意所有调试器";语句已被注释掉,因为在datepicker中所有日期链接的每次迭代中都有一个断点变得很乏味,并且对getJSON调用进行了模拟以允许测试是独立的.

A full example is below, based on the samples which you provided. You can see the differences in behaviour by changing the jQuery version used between 1.7.2, 1.8.0, and 1.8.1. For compatibility across versions, a test for $.expr.createPseudo has been added to the pseudo-selector function assignments. Note that all "debugger;" statements have been commented-out, as having a breakpoint at each iteration across all date links in the datepicker gets rather tedious, and the getJSON call has been mocked to allow the test to be self-contained.

<html>
<head>
    <title>jQuery custom selector, "undefined"</title>
    <!-- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script> -->
    <!-- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.js"></script> -->
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.js"></script>
    <link rel="stylesheet"
        href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/jquery-ui.css" />
    <script>
    $(function()
    {
        $.expr[":"].test = $.expr.createPseudo ?
            $.expr.createPseudo(function( tomatch )
            {
                return function( el2 )
                {
//                  debugger;
                    console.log(el2);
                };
            }) :
            function(el2,index2,meta2,stack2)
            {
    //          debugger;
                console.log(el2);
                console.log(index2);
                console.log(meta2);
                console.log(stack2);
            };
    })

    $(function()
    {
        function getJsonDate(year, month)
        {
            //$.getJSON('dates.php?year='+year+'&month='+month, function(data)
            //{
                var data = {data:[
                    {d:1,link:"a"},
                    {d:15,link:"b"},
                    {d:25,link:"c"}
                ]};
                var i = 0;
                for (i = 0; i < data.data.length; i++)
                {
//                  debugger;
                    var myDay = data.data[i]['d'];
                    $('.ui-datepicker-calendar td a:exactly('+data.data[i]['d']+')').
                        css({color: '#f00'}).
                        attr('href',data.data[i]['link']).
                        parent().attr('onclick','');
                }
            //});
        }

        $.expr[":"].exactly = $.expr.createPseudo ?
            $.expr.createPseudo(function( s )
            {
                return function(el)
                {
                    if(!s) return false;
                    return eval("/^" + s + "$/i").test($(el).text());
                };
            }) :
            function(el, index, meta, stack)
            {
//              debugger;
                console.log(el);
                console.log(index);
                console.log(meta);
                console.log(stack);
                var s = meta[3];
                if (!s) return false;
                return eval("/^" + s + "$/i").test($(el).text());
            };

        $('#datepicker').datepicker(
        {
            inline: true,
            onSelect: function(dateText, inst)
            {
                Date.prototype.toString = function () {
                    return isNaN (this) ?
                        'NaN' :
                        [this.getDate(), this.getMonth(), this.getFullYear()].join('/')
                }
                d = new Date(dateText);
                getJsonDate(d.getFullYear(), d.getMonth()+1);
            },
            onChangeMonthYear: function(year, month, inst)
            {
                //alert(year);
                //alert(month);
                getJsonDate(year, month);
                return false;
            }
        });
    });
    </script>
    <script>
    (function($){$(function(){
        $("<input />").
            attr({type:"button", value: "run test selector"}).
            click(function(){
                $(".ui-datepicker-calendar td:test(3) a");

                // Or, if you are certain that your test will be less-efficient than an exclusion based
                // on parents, you could do:
                //  $(".ui-datepicker-calendar").find("td a:test(3)");
            }).
            appendTo("body");
    })}(window.jQuery));
    </script>
</head>
<body>
    <a href="#ignoreThisLink">.</a>
    <a href="#ignoreThisToo">.</a>
    <p>
        (first, click the input box to cause the datepicker to initialise)
    </p>
    <input type="text" id="datepicker" />
</body>
</html>

我希望这有助于阐明一些事情.

I hope that helps to shed some light on things.

*我说这是首选"方法,但是您仍在使用"eval".不建议这样做,因为它很慢并且可能导致意外/令人惊讶/危险的结果.一种基于字符串构建正则表达式的更好方法是

*I say this is the "preferred" method, but you are still using "eval". This is highly discouraged, as it's slow and can lead to unexpected/surprising/dangerous results. A better way to build regexes based on a string would be

return (new RegExp("^" + s + "$", "i")).test($(el).text());

尽管这也有问题,因为"s"可能包含RegExp特殊字符.但是,在这种特殊情况下,甚至不需要使用正则表达式,并且可以通过以下方式更有效地测试事物:

Though even this has problems, as "s" may contain RegExp special-characters. In this particular case, though, a regular expression is not even needed, and things can be tested much more efficiently via:

return String(s).toUpperCase() === $(el).text().toUpperCase();

通过将转换滚动到闭包中,您甚至可以节省更多,从而提供完整的功能:

You can even save a bit more by rolling the conversion into the closure, giving you the full function of:

$.expr[":"].exactly = $.expr.createPseudo(function( s )
{
        if(!s) return function(){ return false; };
        s = String(s).toUpperCase();

        return function(el)
        {
            return (s === $(el).text().toUpperCase());
        };
});

这篇关于jQuery自定义选择器,“未定义"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 17:24