jQuery函数在.append之后的使用

102

如何在jQuery .append 完全完成后调用一个函数?

这是一个例子:

$("#root").append(child, function(){
   // Action after append is completly done
});
问题:在添加复杂的DOM结构时,在附加函数回调中计算根元素的新大小是错误的。假设DOM仍未完全加载,只有添加的复杂DOM的第一个子级加载完成。

你可以放心地链接事件;append() 几乎不需要什么时间。 - Bojangles
请参见以下链接,了解有关WebKit浏览器上DOM“回流”的内容:https://dev59.com/k2ox5IYBdhLWcg3w_ZR0 - schellmax
这可能会有所帮助:http://stackoverflow.com/a/1489987/1375015 - Konstantin Schubert
18个回答

75

这里有很多有效的答案,但它们没有真正告诉你为什么以这种方式工作。

在JavaScript中,命令按顺序同步执行,除非您通过使用超时或间隔显式地将它们设置为异步执行。

这意味着您的 .append 方法将被执行,除非该方法完成其工作(不考虑任何可能存在的超时或间隔),否则不会执行其他任何操作。

总之,由于.append将同步运行,因此不需要回调函数。


44
这一切都是真实的,但这是我的问题:我添加了复杂的DOM,在添加完之后,我计算根元素的新尺寸,但结果是错误的。我认为问题在于DOM还没有完全加载,只有第一个子元素(.append("<div id="child"><div id="subChild_with_SIZE"></div></div>"))。 - David Horák
@JinDave,你所提到的size是什么?你需要计算的是孩子数量、父母的身高还是其他什么? - mekwall
1
@marcus-ekwall http://jsfiddle.net/brszE/3/ 这只是一个写方法的代码,不是可运行的代码。 - David Horák
23
@DavidHorák,为什么这个答案被接受了呢?虽然改变发生了,但代码不会阻塞直到完全重新排版,我对其获得32个赞感到惊讶!(或者它甚至可能不会导致重新排版)。类似于[这里的答案](https://dev59.com/k2ox5IYBdhLWcg3w_ZR0),可能更加相关。 - Andreas
18
我同意Andreas的观点,这个答案是不正确的。问题在于即使“append”已经返回,该元素仍然可能不在DOM中可用(取决于浏览器,在某些浏览器中有效,在其他浏览器中则无效)。使用延时仍然不能保证该元素已经在DOM中。 - Severun
显示剩余6条评论

37

虽然Marcus Ekwall关于append方法的同步性是完全正确的,但我也发现在一些特殊情况下,当下一行代码运行时,浏览器有时无法完全渲染DOM。

在这种情况下,shadowdiver的解决方案沿用了使用.ready方法的正确思路,但更加简洁的方式是将原始的append方法与其链接起来。

$('#root')
  .append(html)
  .ready(function () {
    // enter code here
  });

1
这在任何情况下都是绝对错误和无用的。 http://api.jquery.com/ready - Kevin B
嗨,Kevin,以什么方式? - Daniel - SDS Group
使用.ready()等待附加元素“渲染”与仅使用setTimeout相比没有任何好处,但前提是文档尚未准备就绪。如果文档已经准备就绪,则代码将同步运行,因此没有任何有用的影响。 - Kevin B
我认为ready不会等待附加的文档加载完成,而是会等待整个DOM加载完成。我写下这个答案已经有大约3年了,但我认为当时更多地是在评论上面的shadow diver,但当时没有评论的功能。 - Daniel - SDS Group
1
实际上,这比预期的要好。比这里的任何其他答案都要好。 - Blue
显示剩余3条评论

22

我也遇到了尺寸重新计算的同样问题,经过几个小时的头疼之后,我不得不反对.append()在严格意义上的同步行为。至少在Google Chrome浏览器中是这样的。请看下面的代码。

var input = $( '<input />' );
input.append( arbitraryElement );
input.css( 'padding-left' );

在 Firefox 中可以正确地检索到 padding-left 属性,但在 Chrome 中为空。我想其他 CSS 属性也是如此。经过一些实验,我不得不将 CSS "getter" 包装在带有 10 毫秒延迟的 setTimeout() 中,这很丑陋但是在 Chrome 中是唯一可行的方法。 如果您有更好的解决方法,请告诉我,我将不胜感激。


15
这对我帮助很大:https://dev59.com/k2ox5IYBdhLWcg3w_ZR0 - 意思是,append() 是同步的,但 DOM 更新不是。 - schellmax
在这种情况下,setTimeout并不丑陋(只有10毫秒是)。每次您请求“dom”操作时,Chrome都会等待您的代码结束后再执行它。因此,如果您调用item.hide,然后是item.show,它将什么也不做。当您的执行函数结束时,它会将更改应用于DOM。如果您需要在继续之前等待“dom”更新,请执行您的CSS / DOM操作,然后调用settimeout(function(){what to do next})。 settimeout就像vb6中的一个好老的“doevents”!它告诉浏览器执行其等待任务(更新DOM),然后返回到您的代码。 - foxontherock
看到使用.ready()的答案更好,更优雅的解决方案。 - Mark Carpenter Jr

11
我对这里的所有答案感到惊讶...尝试这个:
window.setTimeout(function() { /* your stuff */ }, 0);

请注意0超时时间。这不是一个任意的数字...据我理解(尽管我的理解可能有点摇摆不定),有两个javascript事件队列——一个用于宏事件,另一个用于微事件。 "更大"的范围队列保存更新UI(和DOM)的任务,而微队列执行快速任务类型操作。
还要意识到,设置超时并不能保证代码恰好在指定值执行。这样做实质上是将函数放入更高的队列(处理UI/DOM的队列),并且不会在指定时间之前运行它。
这意味着将超时设置为0会将其放入javascript事件队列的UI/DOM部分,以便在下一次可能的机会运行。
这意味着DOM将使用所有先前的队列项进行更新(例如通过$.append(...);插入),当您的代码运行时,DOM完全可用。
(附言-我从《JavaScript Ninja的秘密》中学到了这一点-一本很好的书:https://www.manning.com/books/secrets-of-the-javascript-ninja

多年来,我一直在添加setTimeout(),但不知道为什么。感谢您的解释! - steampowered
1
我必须在我的append()完成之前进行13个setTimeout,间隔为50毫秒。0对于所有情况都不足够。 - PRMan
@PRMan 我认为这不是这个答案的重点。setTimeout(fn(), 0)不会等待几秒钟,它只是给setTimeout()之前的代码更高的优先级。 - NHerwich
当然。但是如果你真的想要处理附加内容,最好准备好循环1-2秒钟来实现它。试一下吧,你会发现这个答案行不通。 - PRMan

6
我遇到了同样的问题并找到了一个简单的解决方案。在调用append函数后添加一个文档准备好的操作。

$("#root").append(child);
$(document).ready(function () {
    // Action after append is completly done
});


不错。这对我有用(我正在使用Handlebars和JSON追加HTML,当然普通的document.ready发生太早了)。 - Katharine Osborne
1
@KatharineOsborne 这与“常规的document.ready”没有任何区别。 document.ready只在一个特定的场景中被调用,以不同的方式使用它并不能使其工作方式有所不同。 - Kevin B
好吧,自从我留下这个评论已经过去一年多了,但是如果我没记错的话,在你调用它的上下文中很重要(即在一个函数中)。代码已经移交给客户,所以我无法再访问它来深入挖掘。 - Katharine Osborne

5
我有一个变体可能对某些人有用:

我还有另一种变体可能对某些人有用:

$('<img src="http://example.com/someresource.jpg">').load(function() {
    $('#login').submit();
}).appendTo("body");

1
对于那些认为这已经被弃用和删除的人来说,你是正确的,但你仍然可以像这样使用它:...on('load', function(){ ... 在这里查看:https://dev59.com/vFoU5IYBdhLWcg3wK0pX#37751413 - caramba

5

使用MutationObserver可以像jQuery的append方法回调一样:

我在另一个问题中已经解释了它,这一次我只会为现代浏览器提供例子:

// Somewhere in your app:
var observeDOM = (() => {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

    return function(obj, callback){
        if( MutationObserver ){
            // define a new observer
            var obs = new MutationObserver(function(mutations, observer){
                if( mutations[0].addedNodes.length || mutations[0].removedNodes.length )
                    callback(mutations);
            });
            // have the observer observe foo for changes in children
            obs.observe( obj, { childList:true, subtree:true });

            return obs;
        }
    }
})();

//////////////////
// Your code:

// setup the DOM observer (on the appended content's parent) before appending anything
observeDOM( document.body, ()=>{
    // something was added/removed
}).disconnect(); // don't listen to any more changes

// append something
$('body').append('<p>foo</p>');

+1。你的答案是最好的。但是在我的情况下它没有起作用,因为我要向表中添加各种行,这会导致无限循环。排序表的插件将更改DOM,因此它将无限次调用观察器。 - Azevedo
@Azevedo - 为什么会是无限的?我确定它是有限的。无论如何,你可以通过一些创意将排序功能与添加行“事件”分开。有方法可以做到。 - vsync
当排序插件对表格进行排序时,它会触发观察器(DOM更改),从而再次触发排序。现在我在思考...我可以计算表格行数,并使观察器仅在行计数发生变化时调用排序器。 - Azevedo
我得到的只是...未捕获的类型错误: 无法在'MutationObserver'上执行'observe': 参数1不是'type为'Node''。 - Scott
@Scott - 你可能是在DOM准备好之前编写了你的脚本.....只有在DOM准备好后再加载你的脚本,或者将<script>标签放在</body>之前的底部位置。 - vsync
@vsync - 谢谢 - 我用jquery promise $.when()... then()解决了我的问题 - 我们有大量的dom更改,必须等待ajax/dom更改完成 - 运行得很好。 - Scott

3

我认为已经有很好的回答了,但是这更加具体,涉及到处理“subChild_with_SIZE”(如果这是从父级传递而来的,但你可以从其他地方进行调整)。

$("#root").append(
    $('<div />',{
        'id': 'child'
    })
)
.children()
.last()
.each(function() {
    $(this).append(
        $('<div />',{
            'id': $(this).parent().width()
        })
    );
});

2

JQuery的append函数返回一个jQuery对象,因此您可以在结尾处添加一个方法

$("#root").append(child).anotherJqueryMethod();

我需要在 .append 之后调用我的自定义 JavaScript 函数,我需要重新计算根元素的大小。 - David Horák
你可以尝试使用jQuery的each方法,但是append是一个同步方法,所以你只需要在下一行做你需要的操作就可以了。 - David
@JinDave,您需要重新计算什么?孩子的数量,父母的身高,或者“尺寸”意味着什么? - mekwall

1

$.when($('#root').append(child)).then(anotherMethod());


8
$.when 只适用于返回 Promise 对象的对象。 - Rifat
它能够工作但在控制台上报错:"then不是一个函数"。 - Karan Datwani
1
这仅在您立即调用 anotherMethod() 的情况下才起作用。它不会被传递为回调函数。 - yts
这没有任何意义。 - Kevin B

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