动态脚本加载同步

IT技术 javascript
2021-02-27 09:38:23

我有一个知道动态加载包含 javascript 类的脚本的脚本。我正在使用以下代码加载类脚本:

var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
head.appendChild(script);

然后我尝试使用 eval 创建新类:

var classObj = eval(" new MyClass()" );

问题是 eval 的代码正在执行 bofre 脚本已加载到内存中,我收到一个错误 MyClass is undefined.

有没有办法同步这些事件?我需要确保脚本完全加载到内存中,然后才能开始从中分配类。

6个回答

您需要将事件处理程序附加到 onload 方法(在符合 Web 标准的浏览器中)或 onreadystatechange,在 Internet Explorer 中检查 script.readyState 属性是否等于“已加载”或“完成”。

在您收到脚本已加载的通知之前,您可能正在尝试访问尚未声明或创建的对象、函数和属性。

这是一个示例函数,从我的Javascript 库bezen.org的模块bezen.dom.js提取

var appendScript = function(parent, scriptElt, listener) {
    // append a script element as last child in parent and configure 
    // provided listener function for the script load event
    //
    // params:
    //   parent - (DOM element) (!nil) the parent node to append the script to
    //   scriptElt - (DOM element) (!nil) a new script element 
    //   listener - (function) (!nil) listener function for script load event
    //
    // Notes:
    //   - in IE, the load event is simulated by setting an intermediate 
    //     listener to onreadystate which filters events and fires the
    //     callback just once when the state is "loaded" or "complete"
    //
    //   - Opera supports both readyState and onload, but does not behave in
    //     the exact same way as IE for readyState, e.g. "loaded" may be
    //     reached before the script runs.

    var safelistener = catchError(listener,'script.onload');

    // Opera has readyState too, but does not behave in a consistent way
    if (scriptElt.readyState && scriptElt.onload!==null) {
      // IE only (onload===undefined) not Opera (onload===null)
      scriptElt.onreadystatechange = function() {
        if ( scriptElt.readyState === "loaded" || 
             scriptElt.readyState === "complete" ) {
          // Avoid memory leaks (and duplicate call to callback) in IE
          scriptElt.onreadystatechange = null;
          safelistener();
        }
      };
    } else {
      // other browsers (DOM Level 0)
      scriptElt.onload = safelistener;
    }
    parent.appendChild( scriptElt );
};

为了适应您的需求,您可以替换对 catchError 的调用,它包装了侦听器以捕获和记录错误,并使用修改后的函数:

var appendScript = function(parent, scriptElt, listener) {
    // append a script element as last child in parent and configure 
    // provided listener function for the script load event
    //
    // params:
    //   parent - (DOM element) (!nil) the parent node to append the script to
    //   scriptElt - (DOM element) (!nil) a new script element 
    //   listener - (function) (!nil) listener function for script load event
    //
    // Notes:
    //   - in IE, the load event is simulated by setting an intermediate 
    //     listener to onreadystate which filters events and fires the
    //     callback just once when the state is "loaded" or "complete"
    //
    //   - Opera supports both readyState and onload, but does not behave in
    //     the exact same way as IE for readyState, e.g. "loaded" may be
    //     reached before the script runs.

    var safelistener = function(){
      try {
        listener();
      } catch(e) {
        // do something with the error
      }
    };

    // Opera has readyState too, but does not behave in a consistent way
    if (scriptElt.readyState && scriptElt.onload!==null) {
      // IE only (onload===undefined) not Opera (onload===null)
      scriptElt.onreadystatechange = function() {
        if ( scriptElt.readyState === "loaded" || 
             scriptElt.readyState === "complete" ) {
          // Avoid memory leaks (and duplicate call to callback) in IE
          scriptElt.onreadystatechange = null;
          safelistener();
        }
      };
    } else {
      // other browsers (DOM Level 0)
      scriptElt.onload = safelistener;
    }
    parent.appendChild( scriptElt );
};

既然您似乎能够编辑外部脚本(因为您使用警报对其进行了测试),为什么不将此代码放在该脚本中呢?

如果您不能这样做(可能会生成额外的代码或可能共享第一个文件),只需在您加载的脚本末尾添加一个函数调用,如下所示:

load_complete();

然后将额外的代码放入该函数中:

function load_complete() {
    var classObj = eval(" new MyClass()" );
}

它比任何类型的加载触发器都简单和万无一失。此外,如果 js 文件是共享的,那么您可以在使用它的每个页面上拥有不同的 load_complete 函数(只需确保始终定义一个 load_complete,即使它为空)。

我相信这实际上可以通过确保将加载外部脚本的代码和使用外部脚本的代码放在单独的脚本块中来解决,如下所示:

<script>
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
head.appendChild(script);

//this is in the same script block so it wouldn't work
//var classObj = eval(" new MyClass()" );
</script>

<script>
//this is in a separate script block so it will work
var classObj = eval(" new MyClass()" );
</script>

使用jQuery(一个 JavaScript 库)getScript function to load your script async. Use the callback function to create your objects. Example:

$.getScript("script.js", function () {
  var classObj = new MyClass();
});
根据getScript()文档,这是不可靠的:“一旦脚本加载但不一定执行,就会触发回调。”
2021-05-14 09:38:23

您确定向<script>DOM添加元素会导致浏览器实际评估脚本吗?我有一个模糊的记忆,在某个地方没有阅读过,但也许我昨天吸入了太多的烤箱清洁剂。

是的。它称为按需 JavaScript。您可以在此处阅读更多相关信息:webreference.com/programming/javascript/rg29
2021-04-24 09:38:23