8

有一个 JavaScript 函数,其中我对代码的控制为零,它调用了我编写的函数。我的函数使用 DOM 生成 iFrame,定义它的 src,然后将其附加到另一个 DOM 元素。但是,在我的函数返回并因此允许继续执行包含函数之前,必须完全加载 iFrame。

以下是我尝试过的事情以及为什么它们不起作用:

1. SetTimeout 选项:
99.999% 的时间,这就是答案。事实上,在我指导 JavaScript 的过去十年中,我一直坚持认为代码总是可以被重构以使用这个选项,并且从不相信存在不这样的场景。嗯,我终于找到了一个!问题是因为我的函数被内联调用,如果在我的 iFrame 完成加载之前执行下一行,它将完全中性我的脚本,并且从我的脚本完成的那一刻起,外部脚本继续。各种回调不起作用

2.“什么都不做”循环:
这个选项你使用while(//iFrame is not loaded){//do nothing}。从理论上讲,这在加载框架之前不会返回。问题是,由于这会占用所有资源,因此 iFrame 永远不会加载。这个技巧,虽然非常不专业,肮脏等当你只需要一个内联延迟时会起作用,但因为我需要一个外部线程来完成,所以它不会。
在 FF 中,几秒钟后,它会暂停脚本并弹出一个警报,指出有一个无响应的脚本。当该警报出现时,iFrame 能够加载,然后我的函数能够返回,但是让浏览器冻结 10 秒,然后要求用户正确消除错误是不行的。

3.模型对话:
我受到以下事实的启发,即 FF 弹出窗口允许 iFrame 在停止函数执行的同时加载,并考虑它,我意识到这是因为模态对话是一种暂停执行但允许其他线程继续的方式!太棒了,所以我决定尝试其他模态选项。像 alert() 这样的东西工作得很好!当它弹出时,即使只有 1/10 秒,iFrame 也能完成,并且一切正常。万一 1/10 秒不够用,我可以将模型对话放在解决方案 2 的 while 循环中,这样可以确保 iFrame 及时加载。甜吗?除了我现在必须弹出一个非常不专业的对话框让用户关闭以运行我的脚本这一事实。我与自己争论这个行动的成本/收益,但后来我遇到了一个场景,我的代码在一个页面上被调用了 10 次!在访问页面之前必须关闭 10 个警报?!这让我想起了 90 年代后期的脚本儿童页面,这不是一个选择。

4. 大量其他延迟脚本:
大约有 10 个 jQuery 延迟或睡眠函数,其中一些实际上开发得非常巧妙,但没有一个起作用。一些原型选项,同样,我发现没有一个可以做到!十几个其他库和框架声称他们有我需要的东西,但可惜他们都合谋给了我虚假的希望。

我确信,由于内置模型对话可以停止执行,同时允许其他线程继续,因此必须有一些代码可访问的方式来做同样的事情而无需用户输入。

该代码实际上有成千上万行并且是专有的,所以我写了这个问题的小例子供您使用。请务必注意,您可以更改的唯一代码位于 onlyThingYouCanChange 函数中

测试文件:

<html>
<head>
</head>
</html>
<body>
<div id='iFrameHolder'></div>
<script type='text/javascript'>
function unChangeableFunction()
{
    new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
    new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
    if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
    new_iFrame_body = new_iFrame_doc.body;
    if(new_iFrame_body.innerHTML != 'Loaded?')
    {
        //The world explodes!!!
        alert('you just blew up the world!  Way to go!');
    }
    else
    {
        alert('wow, you did it!  Way to go!');
    }
}
var iFrameLoaded = false;
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    iFrameLoaded = false;
    iframe=document.createElement('iframe');
    iframe.onload = new Function('iFrameLoaded = true');
    iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
    objectToAppendIFrameTo.appendChild(iframe);
    var it = 0;
    while(!iFrameLoaded) //I put the limit on here so you don't 
    {
        //If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
        //alert('test'); //This would work if it did not require user interaction!
    }
    return iframe;
}
unChangeableFunction();
</script>
</body>

空白帧.html:

<html>
<head>
</head>
<body style='margin:0px'>Loaded?</body>
</html>


这是我结合响应者的想法得出的答案!你们真棒!
我被允许更改的函数的新来源:

function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    iFrameLoaded = false;
    iframe=document.createElement('iframe');
    iframe.onload = new Function('iFrameLoaded = true');
    iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
    objectToAppendIFrameTo.appendChild(iframe);
    var it = 0;
    while(!iFrameLoaded) //I put the limit on here so you don't
    {
        if (window.XMLHttpRequest)
        {
            AJAX=new XMLHttpRequest();
        }
        else
        {
            AJAX=new ActiveXObject("Microsoft.XMLHTTP");
        }
        if (AJAX)
        {
            AJAX.open("GET", 'slow_page.php', false);
            AJAX.send(null);
        }
        else
        {
            alert('something is wrong with AJAX!');
        }

        //If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
        //alert('test'); //This would work if it did not require user interaction!
    }
    return iframe;
}

slow_page.php :

<?
usleep(100000);//sleep for 1/10th of a second, to allow iFrame time to load without DOSing our own server!
?>

我确实想指出,我声明该函数之外没有什么可以更改的,并且添加 php 页面确实违反了该“规则”,但在可能的情况下我能够做到这一点。如果我不能这样做,我可以调用blank_frame.html而不是slow_page.php,并且它应该只需要调用一次(所以每帧加载2次)假设它以相同数量的响应iFrame 加载的时间。如果由于某种原因 iFrame 加载速度较慢,它可能会调用 2ce(总共 3 次调用服务器)

4

8 回答 8

3

是的,javascript 是单线程的这一事实真的让你很着急。您可以使用对故意慢速页面的同步 ajax 调用来模拟睡眠,但您不会得到您想要的结果。为什么不确保在调用不可更改函数之前加载 IFrame?

于 2011-01-02T00:21:03.763 回答
2

您真正需要的是在 iFrame 内容加载时触发的事件。这实际上非常简单,因为 iFrame 内的页面有自己的事件,并且可以访问父页面上的脚本。不过,您将需要能够更改 iFrame 的内容。

在您的 iFrame 中,您将需要这段代码

// 使用你喜欢的任何 DOMReady 函数,否则 window.onload 会起作用
window.addEventListener('DOMContentLoaded', function() {
    if (parent.window.myFunction) {
        parent.window.myFunction();
    }
}, 错误的);

然后在您的父页面中,创建一个名为“myFunction”的函数并将您需要触发的所有脚本放在那里。这应该每次都有效。

编辑:要让它工作,你真的需要两个功能。我假设这真的不是一个选项,所以我们将破解一个函数以包含两个函数,并在需要时调用正确的部分。

函数只有ThingYouCanChange(stringOrObject) {
    函数 createIFrame(objectToAppendIFrameTo) {
        // 此注释表示附加 iFrame 的所有代码
    }
    函数 onIFrameReady() {
        // 这个注释代表了当 iFrame 准备好时你想要发生的所有事情
    }

    // 它的骨头
    if (stringOrObject === "iFrameLoaded") {
        onIFrameReady();
    } 别的 {
        createIFrame(stringOrObject);
    }
}

现在应该将 iFrame 中的脚本更改为以下内容:

// 使用你喜欢的任何 DOMReady 函数,否则 window.onload 会起作用
window.addEventListener('DOMContentLoaded', function() {
    if (parent.window.onlyThingYouCanChange) {
        parent.window.onlyThingYouCanChange('iFrameLoaded');
    }
}, 错误的);

我没有测试过,但理论上应该可以

于 2011-01-02T00:15:24.797 回答
2

注意这是非常hacky,我不会在任何现实世界的情况下使用它。在其他潜在问题中,如果有足够的流量,您最终可能会自己进行 DDOS。

您可以通过进行非异步 (A)JAX 调用来创建睡眠功能。在某些较旧的浏览器中,这可能会冻结所有内容,但至少不需要任何类型的用户响应。

while (!iFrameLoaded)
{
    if (XMLHTTPRequest) {
        var request = new XMLHttpRequest();
    } else {
        var request = new ActiveXObject("Microsoft.XMLHTTP");
    }

    request.open('GET', 'anyoldfile.htm', false);
    request.send();

    // check if the iframe is loaded and set iFrameLoaded
}
于 2011-01-02T00:17:37.090 回答
1

一个非常简单的 ;-} 使用 XPCOM 的答案:

// Get instance of the XPCOM thread manager.
var threadManager=Components.classes['@mozilla.org/thread-manager;1'].getService(
                      Components.interfaces.nsIThreadManager);
// Release current thread.
function doThread() {threadManager.currentThread.processNextEvent(false);};

// Event enabled delay, time in ms.
function delay(time) {
  var end;
  var start=Date.now();
  do {
    end=Date.now();
    doThread();
  } while ((end-start) <= time);
}

适用于最新版本的 Firefox。对不起,Explorer 没有希望了!

于 2011-09-27T22:23:43.493 回答
1

在这种情况下,递归函数可能会有所帮助。只需调用该函数,直到全局变量指示帧已加载

var iFrameStarted = false; //you need two global vars
var iFrameLoaded  = false;


function onlyThingYouCanChange(objectToAppendIFrameTo)
{
  if (iFrameLoaded=false)  // if the frame has loaded then you are done. skip everything and return iframe
  { if (iFrameStarted = false) //otherwise start the frame if it has not been
  {
   iFrameStarted = true;
   iframe=document.createElement('iframe');
   iframe.onload = new Function('iFrameLoaded = true');
   iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure
   objectToAppendIFrameTo.appendChild(iframe);
   var it = 0;
   for (i=0;i<10000;i++) {}  //slow down execution so you are not recursing yourself to death
   onlyThingYouCanChange(objectToAppendIFrameTo);   //start the recursion process

  }
  else  //the frame has been started so continue recursion until the frame loaded
  {
   for (i=0;i<10000;i++) {}  //slow down execution so you are not recursing yourself to death
   onlyThingYouCanChange(objectToAppendIFrameTo);   recursively call your function until the frame is loaded

  }

}

return iframe;  //you only get here when all the recursions are finished   
}
于 2012-07-01T08:03:28.363 回答
0

为什么不能修改基础代码?例如,将核心功能从

function unChangeableFunction()
{
    new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
    new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
    if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
    new_iFrame_body = new_iFrame_doc.body;
    if(new_iFrame_body.innerHTML != 'Loaded?')
    {
        //The world explodes!!!
        alert('you just blew up the world!  Way to go!');
    }
    else
    {
        alert('wow, you did it!  Way to go!');
    }
}

对于这样的事情:

function unChangeableFunction()
{
    var new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
    new_iFrame.onload = function()
    {
        new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
        if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
        new_iFrame_body = new_iFrame_doc.body;
        if(new_iFrame_body.innerHTML != 'Loaded?')
        {
            //The world explodes!!!
            alert('you just blew up the world!  Way to go!');
        }
        else
        {
            alert('wow, you did it!  Way to go!');
        }
    };
}

如果这对您不起作用,那么对原始代码进行透明修改怎么样?使用Javascript Strands编译它并使用内置的期货支持来处理它。请注意,Javascript 1.7 也支持延续,但需要手动更改代码才能使用它们。

于 2011-01-02T00:16:59.810 回答
0

另一种可能不适用的解决方案,取决于您对原始代码的简化程度。您可以设置一个 onload 处理程序,然后抛出一个错误,然后调用unChangeableFunction您的 onload 处理程序:

function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    // using global variable func_called
    if (!func_called) {
        func_called = true;
        var iframe=document.createElement('iframe');
        iframe.src = 'blank_frame.html';
        iframe.id = 'myIframe';
        iframe.onload = function() {
            unChangeableFunction();
        };
        objectToAppendIFrameTo.appendChild(iframe);

        throw new Error('not an error');
    } else {
        return document.getElementById('myIframe');
    }
}

这个函数(如unChangeableFunction)将被调用两次:第一次是在第一个实例中,然后在onload触发处理程序时再次调用。两种不同的途径反映了这一点。

同样,这是 hacky,并且是对 JS 错误功能的明确滥用。

于 2011-01-02T00:39:32.837 回答
0

你可以像这样使用 cookie 和 setTimeout :

在 blank_frame.html 添加一个脚本:

<script type="text/javascript">
function deleteCookie(cookie_name)
{
  var cookie_date=new Date();
  cookie_date.setTime(cookie_date.getTime()-1);
  document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
}
function setCookie(name,value,expires,path,domain,secure){
    document.cookie=name+"="+escape(value)+((expires)?"; expires="+expires.toGMTString():"")+((path)?"; path="+path:"")+((domain)?"; domain="+domain:"")+((secure)?"; secure":"");
}

window.onload=function(){
    setCookie('iframe_loaded','yes',false,'/',false,false);
}
</script>

基本上,您正在添加一个iframe_loaded带有 value的 cookie yes。IMO 最好删除 cookie,因为如果您要重新加载页面,您也需要这样做。您也可以在 setCookie 函数调用中设置域。

现在在主文件中,我们将使用 setTimeout 和函数来检查 cookie 是否存在,如果存在,那么该函数将返回 iframe,就像在您的代码中一样:

function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    function get_cookie(cookie_name){
        var results = document.cookie.match('(^|;) ?'+cookie_name+'=([^;]*)(;|$)');
        return results?unescape(results[2]):null;
    }
    function deleteCookie(cookie_name){
        var cookie_date=new Date();
        cookie_date.setTime(cookie_date.getTime()-1);
        document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
    }

    iFrameLoaded = false;
    iframe=document.createElement('iframe');
    iframe.onload = new Function('iFrameLoaded = true');
    iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
    objectToAppendIFrameTo.appendChild(iframe);
    var it = 0;

    function checkiframe(){
        if(get_cookie('iframe_loaded')=="yes"){
            alert('iframe loaded');
            deleteCookie('iframe_loaded');
            return iframe;
        }else{
            setTimeout(checkiframe,1000);
        }
    }

    checkiframe();
}

由于该文件中的故障安全 cookie 也被删除。希望这会给你一些工作:)

干杯

G。

于 2011-01-02T00:40:13.687 回答