42

有很多关于将未来的操作绑定到不存在的元素的问题,最终都用live /delegate来回答。我想知道如何对与选择器匹配的所有现有元素以及与尚未创建的同一选择器匹配的所有未来元素运行任意回调(例如添加类或触发插件) 。

似乎 livequery 插件的主要功能使其成为核心,但另一部分,附加任意回调不知何故丢失了。

另一个常见的答案是事件委托,但是如果一个人无法访问创建元素以触发事件的所有供应商代码怎么办?


这是一些真实世界的代码:

// with livequery
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
    .livequery(function(){
        $(this)
            .focus(function(){
                $(this).addClass('active');
            })
            .blur(function(){
                $(this).removeClass('active');
            })
            .addClass('text');
    });

// with live
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
    .live('focus', function(){
            $(this).addClass('active');
        })
    .live('blur', function(){
            $(this).removeClass('active');
        });
    // now how to add the class to future elements?
    // (or apply another plugin or whatever arbitrary non-event thing)

一种方法是监控何时添加/删除新节点并重新触发我们的选择器。感谢@arnorhs,我们知道了DOMNodeInserted事件,我会忽略跨浏览器问题,希望那些小的 IE 补丁有朝一日可以到达 jQuery 的上游,或者知道可以包装 jQuery DOM 函数。

但是,即使我们可以确保 DOMNodeInserted 触发跨浏览器,使用多个选择器绑定到它也是荒谬的。可以随时创建数百个元素,并且对这些元素中的每一个进行潜在的数十个选择器调用将会爬行。

到目前为止我最好的主意

监视 DOMNodeInserted/Deleted 和/或挂钩到 jQuery 的 DOM 操作例程以仅设置一个“重新初始化”应该发生的标志可能会更好吗?然后可能只有一个计时器每 x 秒检查一次该标志,仅在 DOM 实际更改时运行所有这些选择器/回调。

如果您快速添加/删除大量元素(如动画或____),那仍然可能非常糟糕。如果 x 较低,则必须每 x 秒为每个保存的选择器重新解析一次 DOM 可能会过于紧张,而如果 x 很高,则界面会显得迟钝。

还有其他新颖的解决方案吗?

当它允许我时,我会添加一个赏金。我为最新颖的解决方案添加了赏金!

基本上我得到的是一种更面向方面的操作 DOM 的方法。一种可以允许将来创建新元素的方法,并且应该使用初始 document.ready 修改来创建它们

JS 最近能够做很多魔法,我希望它会很明显。

4

5 回答 5

10

在我看来,DOM Level 3 事件DOMNodeInserted帮助(仅针对节点触发)和DOMSubtreeModified帮助(几乎针对任何修改触发,例如属性更改)是完成该任务的最佳选择。

当然,这些事件的最大缺点是,这个世界的 Internet Explorer 不支持它们
(……嗯,IE9支持)。

这个问题的另一个合理的解决方案是挂钩任何可以修改 DOM 的方法。但是我们不得不问,我们的范围是什么?

仅处理来自特定库(如 jQuery)的 DOM 修改方法就足够了吗?如果由于某种原因另一个库正在修改 DOM 甚至是本机方法怎么办?

如果只是为了jQuery,我们根本不需要.sub()。我们可以编写如下形式的钩子:

HTML

<div id="test">Test DIV</div>

JS

(function(_append, _appendTo, _after, _insertAfter, _before, _insertBefore) {
    $.fn.append = function() {
        this.trigger({
            type: 'DOMChanged',
            newnode: arguments[0]
        });
        return _append.apply(this, arguments);
    };
    $.fn.appendTo = function() {
        this.trigger({
            type: 'DOMChanged',
            newnode: this
        });
        return _appendTo.apply(this, arguments);
    };
    $.fn.after = function() {
        this.trigger({
             type: 'DOMChanged',
             newnode: arguments[0]
         });
        return _after.apply(this, arguments);
    };

    // and so forth

}($.fn.append, $.fn.appendTo, $.fn.after, $.fn.insertAfter, $.fn.before, $.fn.insertBefore));

$('#test').bind('DOMChanged', function(e) {
    console.log('New node: ', e.newnode);
});

$('#test').after('<span>new span</span>');
$('#test').append('<p>new paragraph</p>');
$('<div>new div</div>').appendTo($('#test'));

可以在此处找到上述代码的实时示例:http: //www.jsfiddle.net/RRfTZ/1/

这当然需要完整的 DOMmanip 方法列表。我不确定您是否可以使用这种方法覆盖本机方法.appendChild().appendChild位于Element.prototype.appendChild例如,可能值得一试。

更新

Element.prototype.appendChild我在 Chrome、Safari 和 Firefox(官方最新版本)中测试了覆盖等。适用于 Chrome 和 Safari,但不适用于 Firefox!


可能还有其他方法来解决这个要求。但我想不出一种真正令人满意的方法,比如计数/观察节点的所有后代(这需要intervalor timeouts,eeek)。

结论

DOM 级别 3 事件的混合支持和挂钩的 DOMmanip 方法可能是您可以在这里做的最好的事情。

于 2011-02-11T23:56:43.333 回答
6

我正在阅读 jQuery 的新版本 1.5 版,我立即想到了这个问题。

使用 jQuery 1.5,您实际上可以使用称为 jQuery.sub(); 的东西创建自己的 jQuery 版本;

这样,您实际上可以覆盖 jQuery 中的默认 .append()、insert()、.html()、.. 函数,并创建自己的自定义事件,称为“mydomchange” - 而不会影响所有其他脚本。

所以你可以做这样的事情(从 .sub() 带有小修改的文档复制。):

var sub$ = jQuery.sub();
sub$.fn.insert = function() {
    // New functionality: Trigger a domchange event
    this.trigger("domchange");
    // Be sure to call the original jQuery remove method
    return jQuery.fn.insert.apply( this, arguments );
};

您必须对所有 dom 操作方法执行此操作...

jQuery 文档中的 jQuery.sub():http: //api.jquery.com/jQuery.sub/

于 2011-02-09T00:33:23.880 回答
2

好问题

似乎有一个可以绑定的自定义事件:http: //javascript.gakaa.com/domnodeinserted-description.aspx

所以我想你可以这样做:

$(document).bind('DOMNodeInserted',function(){ /* do stuff */ });

但是我没有尝试过所以我不知道..

顺便说一句:相关问题: javascript 可以在每个 Dom 元素上侦听“onDomChange”吗?

于 2011-02-04T02:43:46.093 回答
0

没有简单明显的方法可以做到这一点。唯一可靠的方法是主动轮询,这会导致在创建新元素和轮询注意到它之间出现渲染打嗝。这也可能使您的页面占用大量资源,具体取决于您轮询页面的频率。正如您所观察到的,您还可以将此与绑定几个特定于浏览器的事件结合起来,以至少使这些浏览器中的事情变得更好。

您可以覆盖 jQuery 的 DOM 修改函数来触发自定义更改事件(并使用 $.live 捕获这些事件以进行操作),但是当我过去尝试过这个时,它巧妙地破坏了各种 jQuery 插件(我的猜测是这些插件做类似的事情)。最后我放弃了可靠地这样做,因为我不想放弃性能并让主动轮询出现打嗝,并且没有其他全面的方法可以做到这一点。相反,我有一个初始化事件,我确保为我所做的每个 DOM 更改触发,并将我的操作事件绑定到这些事件。

请注意,如果您不考虑周全,很容易陷入无限事件循环,而且这也可能很微妙且难以追踪;更糟糕的是,您的单元测试不允许的极端情况可能会发生(因此您的用户体验它而不仅仅是您)。从这个意义上说,自定义手动触发的初始化事件更容易诊断,因为您始终知道触发它的确切时间。

于 2011-02-12T16:23:55.953 回答
-1

好吧,首先,如果你担心性能,你甚至不应该使用这样的选择器。

$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')

任何没有 xpath(不仅仅是 IE iirc)或 getElementsByClassName(IE 7 及更低版本)本机实现的浏览器都可以轻松地在大型站点上花费几秒钟的时间来研究它,因此轮询当然是完全不可能的如果你想要它那么广泛。

于 2011-02-12T00:20:37.927 回答