仅在元素完全加载时执行一次的函数

Posted

技术标签:

【中文标题】仅在元素完全加载时执行一次的函数【英文标题】:Function that executes once and only when an element is fully loaded 【发布时间】:2012-04-30 08:34:42 【问题描述】:

javascript 函数执行 一次 并且仅在 DOM 元素完全加载时执行有点困难。我尝试了无数 setIntervals、seTimeouts、FOR 循环、IF 语句、WHILE 循环等的组合,但一无所获。

我确实设法让它工作了一次,但它是偶然的,因为我只能通过将功能延迟 2 秒来让它工作(这不好,因为没有确切的时间告诉需要加载)并一遍又一遍地快速重新启动相同的功能,这也不好。

我只需要不断扫描页面以判断元素是否存在并包含内容(innerhtml != undefined 或其他内容),在加载后立即执行代码块(并且仅执行一次)然后停止扫描页面。

有没有人找到办法做到这一点?另外,我需要 JavaScript,而不是 jQuery。

谢谢。 原码

function injectLink_Bridge()
    setTimeout(function()
        injectLink();
    , 2000);


function injectLink()
    var headerElement = document.getElementsByClassName("flex-module-header");

    if (headerElement.innerHTML == undefined) 
        console.log("Element doesn't exist. Restarting function");

        setTimeout(function()
            injectLink_Bridge(); //I can't remember if the bridge is necessary or not
        , 2000); 
     else 
        console.log("Element exists. Injecting");

        setTimeout(function()
            headerElement[1].innerHTML += "code" //Inject code into the second instance of the class-array
        , 2000);
    

完成的代码

function injectLink()
    var headerElement = document.getElementsByClassName("flex-module-header")[1]; //Get the second instance

    if(headerElement && headerElement.innerHTML != "")
        console.log("Element exists and has content. Injecting code...");
        headerElement.innerHTML += "code"; //Currently revising, due to T.J. Crowder's side-note
     else 
        console.log("Element doesn't exist or has no content. Refiring function...");
        setTimeout(injectLink, 250);
    

【问题讨论】:

对不起。我正在辩论包括代码,但它经历了很多不同的版本,我不知道它是否会有所帮助或阻碍。无论如何我现在都会包含它。 【参考方案1】:

在您发布代码后更新答案

getElementsByClassName 返回一个NodeList,而不是一个元素; NodeList 没有 innerHTML 属性。如果您想要第二个匹配元素,请使用[1] 获取它,然后测试您是否得到了一些东西(或者如果您愿意,您可以使用.length > 1 先检查,但NodeList 记录为返回null对于不存在的索引)。此外,在将现有函数传递给setTimeout 时,您不需要围绕现有函数的函数包装器。所以:

function injectLink()
    var headerElement = document.getElementsByClassName("flex-module-header")[1];

    if (!headerElement) 
        console.log("Element doesn't exist. Restarting function");

        setTimeout(injectLink, 2000); 
     else 
        console.log("Element exists. Injecting");

        // Do you really want a further two second delay?
        setTimeout(function()
            headerElement.innerHTML += "code"; //Inject code into the second instance of the class-array
        , 2000);
    

旁注:使用.innerHTML += "markup"; 通常不是将内容附加到元素的好方法。当您这样做时,浏览器会执行以下操作:

    遍历元素及其具有的任何后代元素,构建一个 HTML 字符串以提供给 JavaScript 层。 从元素中删除所有内容,这会带来删除后代元素上的任何事件处理程序的副产品。 解析 HTML 字符串并为其构建新节点和元素。

所以需要做一些工作,并且还有可能清除事件处理程序。如果要添加新的子元素,请查看使用 createElementappendChild。如果您要添加更多文本内容,请查看createTextNodeappendChild


原答案

如果你给你的元素一个id(尽管无论你如何找到元素,相同的概念都有效),你可以这样做:

(function() 
    setTimeout(check, 0);
    function check() 
        var elm = document.getElementById('theId');
        if (!elm) 
            setTimeout(check, 250); // Check again in a quarter second or so
            return;
        

        // Use the element
    
)();

在那里,我们几乎立即启动第一次检查,如果我们没有找到该元素,我们会在 250 毫秒后再次安排检查。因为我们只在前一个没有找到元素的情况下安排下一次检查,所以我们只循环查找元素所需的次数。

如果该元素没有id,那么您可能会以某种方式找到它(querySelectorquerySelectorAll 等),因此会知道您是否找到它并且能够安排下一次检查。

可能值得对其进行限制,例如:

(function() 
    var start = new Date();
    setTimeout(check, 0);
    function check() 
        var elm = document.getElementById('theId');
        if (!elm) 
            if (new Date() - start > 5000)  // More than five seconds
                throw "Couldn't find the element";
            
            setTimeout(check, 250); // Check again in a quarter second or so
            return;
        

        // Use the element
    
)();

...因此,如果您更改 HTML 并且该元素不再存在,您会在测试时看到错误并提醒您更新代码。

【讨论】:

太完美了,非常感谢。我不知道您在代码中使用的一些方法。很高兴知道。 :) 另外,我认为需要进一步延迟,因为页面在填充内容之前定义了元素 - 除非延迟,否则无法注入 HTML,否则会出现控制台错误。 @BenHooper:很好,很高兴有帮助。顺便说一句,我添加了一条关于使用 .innerHTML += "content" 的注释,这可能(或可能没有)有用。 啊,是的。这为其他链接停止工作的原因提供了一些线索。我现在看看链接。谢谢。 :)【参考方案2】:

编辑:既然你已经展示了你的代码,它有一些问题。

例如,getElementsByClassName() 返回一个数组,所以你不能这样做:

var headerElement = document.getElementsByClassName("flex-module-header");
if (headerElement.innerHTML == undefined) 

你必须这样做:

var headerElement = document.getElementsByClassName("flex-module-header");
if (!headerElement || headerElement.length == 0 || headerElement[0].innerHTML == undefined) 

问题中提供代码之前的原始答案:

如果您向我们展示了整个问题,我们可能会想出更优雅的方法,但根据您告诉我们的内容,我们现在只能建议使用钝力法:

(function() 
    var item, cnt = 0; 
    var pollTimer = setInterval(function() 
        ++cnt;
        if (!item) 
            // cache the item if found so future checks will be faster
            item = document.getElementById("testObject");
        
        if (item && item.innerHTML != "") 
            // the item has innerHTML now, you can process it
            // with code here
            clearInterval(pollTimer);
         else 
            if (cnt > 100) 
               // if not found after 100 checks (20 seconds)
               // then stop looking so we don't keep going forever
               clearInterval(pollTimer);
            
        
    , 200);
);

【讨论】:

我现在将阅读您的回答,谢谢。 :) 顺便说一句,我现在已经包含了代码。 @BenHooper - 您使用getElementsByClassName() 的方式存在一些基本问题,因为它返回一个数组,而不是单个元素。我已经在我对这个问题的回答的开头发表了评论。 感谢您的回答和时间,但我已将答案交给了 T.J.克劳德,他修改了我现有的代码,以便我能理解我在做什么。 :)

以上是关于仅在元素完全加载时执行一次的函数的主要内容,如果未能解决你的问题,请参考以下文章

Django模板仅在for循环中第一次出现匹配时执行

如何仅在视口之外的元素上执行 jquery Waypoints?

mongo 仅在每个元素都符合条件时才查找

jQuery的事件处理

dtrace 仅在函数返回特定模块时执行操作

设计模式:只执行一次的函数(续)