JavaScript 异步脚本被阻塞

问题描述 投票:0回答:2

我在加载 connect.facebook.net/en_GB/all.js 时遇到问题。我不确定这是否发生在其他人身上,但这个脚本需要永远加载(有时不加载)。它甚至没有超时。 我有一个 PHP 脚本,它异步加载一些脚本,如下所示。即使执行此操作后,问题仍然存在。只要脚本正在加载,document.readyState 就会保持“交互式”状态,因此我的 domcontentloaded 事件永远不会触发。

这个问题在我过去几天尝试过的不同网站、计算机和浏览器上随机出现。

<script>(function(d, s, f){
    var a = d.getElementsByTagName(s)[0], i = f.length;
    while (i--) {
        j = d.createElement(s);
        j.src = f[i];
        j.async = true;
        a.parentNode.insertBefore(j, a);
    }
}(document, 'script', ['//connect.facebook.net/en_GB/all.js', '//platform.twitter.com/widgets.js', '//www.google-analytics.com/ga.js']));</script>

这是生成的脚本标签:

<script src="//connect.facebook.net/en_GB/all.js" async=""></script>
javascript asynchronous facebook-javascript-sdk nonblocking
2个回答
0
投票

由于有一段时间没有任何回复,我就简单说一下我做了什么。 我仍然无法解决问题,但我有一个解决方法。我没有立即加载脚本,而是在 DOM 加载之后加载它。我的 DOMContentLoaded 会触发,但 onload 仍然不会触发。


0
投票

这个问题可以通过polyfill来规避:

(function (window, document, undefined) {
const orig_add_ev_listener = Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener').value;

var dom_avail = false;
var page_loaded = false;

function exec_event(p_event, p_handler, p_param)
  {
var l_install_hand = true;
var l_custom_ev;

  if(typeof p_event != 'string')
    throw new TypeError('string expected');
  if(typeof p_handler != 'function')
    throw new TypeError('function expected');
  if(typeof p_param == 'undefined')
    p_param = { capture: false };
  if(typeof p_param == 'boolean')
    p_param = { capture: p_param };
  if(typeof p_param != 'object' || Array.isArray(p_param))
    throw new TypeError('boolean or non-array object expected');

  switch(p_event)
    {
    case 'DOMContentLoaded':
      if(dom_avail)
        {
        l_custom_ev = new Event('DOMContentLoaded');
        l_install_hand = false;
        }
      break;

    case 'load':
      if(this == window && page_loaded)
        {
        l_custom_ev = new Event('load');
        l_install_hand = false;
        }
      break;
    }
  if(l_install_hand)
    orig_add_ev_listener.call(this, p_event, p_handler, p_param);
  else
    queueMicrotask(p_handler.bind(this, l_custom_ev));
  }

document.addEventListener('DOMContentLoaded', p_event => { dom_avail = true; }, { once: true });
window.addEventListener('load', p_event => { page_loaded = true; }, { once: true });
Object.defineProperty(EventTarget.prototype, 'addEventListener', { value: exec_event });
})(window, document);

这个 polyfill 的作用很简单:存储原始函数

addEventListener
,创建我们感兴趣的两个事件的两个标志(即
DOMContentLoaded
load
)以及
EventTarget.prototype.addEventListener
的包装函数。注册这两个事件处理程序后,每个事件处理程序都会在我们感兴趣的两个事件中的一个特定事件上被调用。最后,本机
addEventListener
被我们的包装函数替换。

因此,每当您异步加载函数时,包装器都会检查文档当前所处的状态:

  1. 文档仍在加载中。
    如果异步加载的脚本在此阶段启动(即,到目前为止,
    DOMContentLoaded
    load
    事件都没有触发),这两个事件的任何处理程序都会像往常一样注册,以便在浏览器启动时调用它们。触发事件。
  2. DOM可用,但尚未加载所有资源。
    现在
    DOMContentLoaded
    事件已经触发,因此在此事件上注册的事件处理程序将不会再被触发。
    我们现在要做的就是人为地创建一个
    DOMContentLoaded
    事件,将
    this
    和新创建的元素绑定到我们收到的事件处理程序中,并将其添加到浏览器的微任务队列中。因此,一旦调用
    addEventListener
    的线程完成,添加的处理程序就会立即执行。
  3. 文档已完成加载。
    这里两个事件都已经触发,因此任何注册的处理程序都不会再执行。为了解决这个问题,这两个事件都按照 #2 添加到微任务队列中。

如果您正在注册任何其他事件,这些调用将直接传递到本机

addEventListener
函数。

使用这个polyfill(确保同步加载此脚本作为此列表中的第一个脚本!),您现在可以添加异步脚本,甚至无需修改它们。也就是说,如果您有一个同步加载或延迟加载的脚本(即设置了

defer
属性),但您决定稍后切换到异步加载,则您需要做的就是更改属性而不需要任何操作需要重新编写脚本。

© www.soinside.com 2019 - 2024. All rights reserved.