jQuery .live() 和 .on() 方法用于在加载动态 HTML 后添加单击事件的区别

222

我正在使用jQuery v.1.7.1,其中.live()方法明显已经被弃用。

我遇到的问题是,在使用以下方式动态加载HTML内容到元素时:

$('#parent').load("http://..."); 

如果我尝试在之后添加点击事件,使用以下任一方法都无法注册事件:

$('#parent').click(function() ...); 
或者
// according to documentation this should be used instead of .live()
$('#child').on('click', function() ...); 

如何正确实现这个功能?对我来说,似乎只有使用.live()才能起作用,但我不应该使用那种方法。请注意,#child是一个动态加载的元素。

谢谢。


25
为什么你说“据说已废弃”?你不相信文档吗? - user1106925
6
它不是所谓的被弃用:它已经被弃用了。如果您查看jQuery的.live()文档,它会告诉您如何重写现有的.live()使用方式以使用.delegate().on()(取决于您是否使用1.7+版本)。请注意,如果您像您提到的那样在动态加载元素后使用.click()添加处理程序,它应该可以工作 - 唯一的问题是尝试在动态加载元素之前使用.click()进行分配。 - nnnnnn
我把措辞改成了“显然”,因为那基本上是我的意思。无论如何,我现在明白了,显然由于.load()事件是异步的,所以#child元素只能在成功处理程序中可靠地被识别,这是有道理的。 - Sean Thoman
7个回答

617
如果你想要点击事件处理程序可以在动态加载的元素中起作用,那么你需要在一个不会被动态加载的父对象上设置事件处理程序,并给它一个选择器来匹配你的动态对象,像这样:
$('#parent').on("click", "#child", function() {});
事件处理程序将附加到#parent对象,每当一个点击事件从#child发起并冒泡到它时,它将触发您的点击处理程序。这被称为委托事件处理(事件处理委托给父对象)。
采用此方式是因为即使#child对象尚不存在,您也可以将事件附加到#parent对象,但是当它稍后存在并被点击时,点击事件将冒泡到#parent对象,它将看到它起源于#child,并且有一个关于#child点击事件的事件处理程序,然后触发您的事件。

24
很棒的解释!我之前一直没有理解live()on()的区别,但今晚我又决定试试,你的解释立刻揭示了我一直以来缺失的部分。谢谢! - MikeSchinkel
6
“一百万次升级”。在意识到jQuery可以为动态创建的子元素实现这一功能之前,我开始使用knockout。非常感谢你。 - Brock Hensley
9
你应该考虑写一本书或其他东西:你的简短文字对我比jQuery文档中关于“on()”的整页内容更有帮助。非常感谢! - Cholesterol
@jfriend00 你知道我们怎么将同样的过程应用到鼠标悬停和非悬停情况吗?有没有相关的资源可以参考? - klewis
@blackhawk - 请参考这个答案。您可以注册mouseentermouseleave事件,并在处理程序中测试哪一个被触发。使用委托无法使用jQuery的伪“hover”事件。 - jfriend00

34

试试这个:

$('#parent').on('click', '#child', function() {
    // Code
});
$.on() 文档中得知:
事件处理程序只绑定到当前选定的元素上;在调用 .on() 方法时,这些元素必须已经存在于页面上。
当你在你的代码中调用 $.on() 方法时,你的 #child 元素并不存在,所以事件不会被绑定(不像 $.live())。然而,#parent 是存在的,因此将事件绑定到它是可以的。
我上面代码中的第二个参数充当了一个“过滤器”,仅在事件从 #child 冒泡到 #parent 时才触发。

如果我在 $.load() 方法之后调用 $.on() 方法,那么为什么此时 #child 元素不存在呢? - Sean Thoman
6
你需要在.load()方法的成功处理程序中调用它,而不是在.load()方法后面的代码中调用。只有当成功处理程序实际执行时,才能确定#child已加载,而此前则不能确定。 - jfriend00
即使是这样,将获取的任何数据插入DOM所需的时间可能意味着它无法连接。最近我一直在做很多单页应用程序工作,我的标准是var $body = $('body'); $body.on("event", ".element/#class", function(e){}); 适用于所有情况的1个选择器。不必担心什么已经加载或未加载。 - lupos

23

$(document).on('click', '.selector', function() { /* do stuff */ });

这个例子中,你在整个文档上放置了一个监听器。当你点击任何匹配.selector的元素时,事件会冒泡到主文档——只要没有其他调用event.stopPropagation()方法的侦听器,它就会停止将事件冒泡到父元素。 你不是绑定到特定的元素或一组元素,而是监听来自符合指定选择器的元素的任何事件。这意味着你可以创建一个监听器,一次,它将自动匹配当前存在的元素以及任何动态添加的元素。 这样做有几个聪明之处,包括性能和内存利用(在大型应用程序中)。 显然,你可以监听最接近的父元素,并使用任何元素代替 document ,只要你想要监视事件的子级在该父元素内...但这实际上与问题无关。


23
他尝试使用 $(element).on(...)。这应该改为使用 $(document).on(...,element,...)。两者不同。 - cHao
@ArnoldRoa 是的,性能更好。您不必在多个子元素上添加监听器,也不必在 DOM 修改时重新应用监听器,并且事件默认会通过 DOM 冒泡。只需运行一次,每个与选择器匹配的子元素在单击时都会触发给定的函数。 - Larry Williamson
@L422Y在这里的评论非常误导人。如果你对性能影响感到好奇,可以看看@jfriend00的评论,他对@Jared的回答进行了解释。 - Gust van de Wal
你只是在谈论如何附加事件。现实世界中的性能瓶颈是太多的监听器触发(例如委托的监听器),而不是需要附加事件的大量元素。 - Gust van de Wal
另一个噩梦是从特定元素中删除绑定,这种方式更加清晰简洁。任何其他方式都可能(取决于编码人员)导致大量的内存泄漏。当您处理具有许多行或项目的界面时,事情会非常快速地失控! - Larry Williamson
显示剩余3条评论

14

在1.7中与.live()等价的方法看起来是这样的:

$(document).on('click', '#child', function() ...); 
基本上,监听文档的点击事件并过滤出 #child。

因为这是事件处理程序附加到文档的方式(就像.live()一样)。 - cHao
17
.live()方法被废弃的原因之一是因为将所有实时事件处理程序都放置在文档对象上会导致速度变慢。不仅需要将事件冒泡到文档,而且您可能需要查找文档对象上的许多事件处理程序。.on()的主要优点是您可以将其附加到更接近实际对象的父对象上,从而显着提高性能。因此...我不建议使用.on()与文档对象一起使用,选择一个更靠近的父对象会更好。 - jfriend00
感谢澄清。就我所知,使用on()函数可以大大提高事件的性能,因此它现在应该不再是一个问题。如果要将事件附加到父元素,则必须确保该父元素在文档准备就绪时已存在,否则可能会出现类似的问题。 - Jared
最坏的情况下,这确实模拟了已弃用的.live()方法; 然而,控制委托的能力肯定是有优势的。 - Dan Lugg

10

我知道回答有点晚,但是我已经为 .live() 方法创建了一个 polyfill。我在 jQuery 1.11 中测试过,它似乎工作得很好。我知道我们应该尽可能地实现 .on() 方法,但在大型项目中,由于某些原因无法将所有的 .live() 调用转换为等效的 .on() 调用,以下方法可能会起作用:

if(jQuery && !jQuery.fn.live) {
    jQuery.fn.live = function(evt, func) {
        $('body').on(evt, this.selector, func);
    }
}

在加载jQuery之后,调用live()方法之前,只需将其包含即可。


将内部调用替换为 return jQuery('body').on(evt, this.selector, func);,否则方法链接将无法工作,并且在 $ 不可用的环境中也无法工作。 - Skynet

6

.on()方法适用于jQuery版本1.7及以上。如果您使用的是旧版本,请使用以下方法:

$("#SomeId").live("click",function(){
    //do stuff;
});

8
OP说:“我正在使用jQuery v.1.7.1版本。” - Selvakumar Arumugam
1
@jfriend--为什么不发表自己的答案,而要对我的答案进行投票?我每天都使用所有版本年龄小于1.6的live(),它很好用。我可以看出你擅长阅读文档,但有时将某些东西实践给一个人更有帮助。.live()就是好用。没得说。 - Matt Cashatt
5
@MatthewPatrickCashatt - 我发表了自己的答案并没有给你的回答打负评 - 不要做出这样盲目的假设。在1.7版本之前,.delegate()的性能要比.live()好得多,这就是为什么jQuery文档推荐使用它并弃用.live()的原因。是的,.live()仍然可以使用,但是在SO上的答案应该建议更好的解决方法。我在SO上已经看到过这种情况20次了。如果你在SO上发布带有.live()的代码,它会被打负评(除非还有其他严重问题,否则通常不是由我打的)。 - jfriend00
1
我没有点踩,但是虽然.live()在1.7版本中确实有效,但你仍应该使用.delegate().on(),因为.live()已经被弃用了。只要你注意到两个新函数的语法变化,任何一个都可以正常工作。 - nnnnnn
1
@jfriend00- 你说得完全正确。今天是个漫长的一天,我道歉了。 - Matt Cashatt
显示剩余3条评论

4
我在项目中使用了'live',但我的一个朋友建议我应该使用'on'而不是live。当我尝试使用时,我遇到了一个问题,就像你所经历的那样。
在我的页面上,我动态创建按钮、表格行和许多DOM元素。但当我使用'on'时,魔法消失了。
其他解决方案是将其作为子项使用,每次单击都调用您的函数。但我找到了一种方法使它再次发生,这是解决方案。
编写代码如下:
function caller(){
    $('.ObjectYouWntToCall').on("click", function() {...magic...});
}

在创建对象后,像这样在页面中调用caller()。
$('<dom class="ObjectYouWntToCall">bla... bla...<dom>').appendTo("#whereeveryouwant");
caller();

通过这种方式,您的函数会在它应该被调用的时候被调用,而不是每次页面上的单击事件都触发它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接