document.createElement(“script”)同步

时间:2010-07-14 16:34:09

标签: javascript dom synchronous

是否可以同步调用.js文件,然后立即使用它?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

这是简化的。在我的实现中,createElement的东西在一个函数中。我想在函数中添加一些东西,可以在返回控件之前检查某个变量是否被实例化。但是,当我从另一个我无法控制的网站中包含js时,仍然存在如何处理的问题。

思想?

编辑:

我现在接受了最好的答案,因为它为正在发生的事情提供了一个很好的解释。但如果有人对如何改进这一点有任何建议我会向他们开放。这是我想要做的一个例子。

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

我只是想避免过多地了解内部结构,并且能够说,“我希望使用这个模块,现在我将使用它的一些代码。”

12 个答案:

答案 0 :(得分:111)

您可以使用“onload”处理程序创建<script>元素,并在浏览器加载和评估脚本时调用该元素。

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

你无法同步。

编辑 - 有人指出,对于形式而言,IE不会在正在加载/评估的<script>标记上触发“加载”事件。因此,我认为接下来要做的就是使用XMLHttpRequest获取脚本,然后自己eval()。 (或者,我想,将文本填充到您添加的<script>标记中; eval()的执行环境受本地范围的影响,因此它不一定会按照您希望的方式执行。 )

编辑 - 截至2013年初,我强烈建议您查看更强大的脚本加载工具,例如Requirejs。有很多特殊情况需要担心。对于非常简单的情况,有yepnope,现在内置于Modernizr

答案 1 :(得分:23)

这不太好,但它有效:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

或者

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

该脚本必须包含在单独的<script>代码中或window.onload()之前。

这不起作用:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

与Pointy一样,创建节点也是如此,但仅限于FF。您无法保证脚本何时可以在其他浏览器中使用。

作为XML纯粹主义者我真的很讨厌这个。但它确实可行。您可以轻松地包裹那些丑陋的document.write(),这样您就不必查看它们了。您甚至可以进行测试并创建一个节点并将其追加,然后再回到document.write()

答案 2 :(得分:16)

这是迟到的,但为了将来参考任何想要这样做的人,您可以使用以下内容:

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

我前段时间做了一篇简短的博文http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/

答案 3 :(得分:7)

  

异步编程稍微更强复杂,因为后果   发出请求的请求封装在函数中,而不是跟在请求语句之后。 用户体验的实时行为可能显着   更好因为他们不会看到服务器缓慢或网络迟缓导致   浏览器表现得好像已经崩溃了。 同步编程不尊重   在人们使用的应用程序中不应使用

Douglas Crockford YUI Blog

好吧,扣上你的座位,因为这将是一个坎坷的旅程。越来越多的人质疑通过javascript动态加载脚本,这似乎是一个热门话题。

其成为如此受欢迎的主要原因是:

  • 客户端模块化
  • 更容易的依赖管理
  • 错误处理
  • 性能优势

关于模块化:很明显,应该在客户端正确处理客户端依赖关系。如果需要某个对象,模块或库,我们只需要它并动态加载它。

错误处理:如果资源失败,我们仍然有机会仅阻止依赖受影响脚本的部分,或者甚至可能会再次尝试延迟。

性能已成为网站之间的竞争优势,现在已成为搜索排名因素。动态脚本可以做的是模仿异步行为,而不是浏览器处理脚本的默认阻塞方式。 脚本阻止其他资源,脚本阻止进一步解析HTML文档,脚本阻止 UI。现在使用动态脚本标记及其跨浏览器替代方案,您可以执行真正的异步请求,并仅在相关代码可用时执行它们。即使使用其他资源,您的脚本也会并行加载,渲染将完美无瑕。

有些人坚持使用同步脚本的原因是因为他们习惯了它。他们认为这是默认方式,它是更容易的方式,有些人甚至认为这是唯一的方法。

但是,当需要就应用程序的设计做出决定时,我们唯一需要关心的是最终用户体验。而在这个领域异步不能打败。用户获得即时回复(或说承诺),承诺永远比没有好。一个空白的屏幕吓到了人们。开发人员不应该懒于提高感知性能

最后还有一些关于肮脏方面的话。你应该做些什么才能让它在浏览器中运行:

  1. 学会异步思考
  2. 将您的代码整理为模块化
  3. 整理代码以妥善处理错误和边缘情况
  4. 逐步提升
  5. 始终关注适量的反馈

答案 4 :(得分:4)

上面的答案指出了我正确的方向。这是我工作的通用版本:

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      

答案 5 :(得分:3)

这看起来像动态脚本加载的一个不错的概述: http://unixpapa.com/js/dyna.html

答案 6 :(得分:2)

我对此问题的现有答案存在以下问题(以及此问题在其他stackoverflow线程上的变体):

  • 所有加载的代码都没有可调试
  • 许多解决方案需要回调才能知道何时加载完成而不是真正阻塞,这意味着我会立即调用加载(即加载)代码来获得执行错误。

或者,更准确一点:

  • 所加载的代码都不是可调试的(除了HTML脚本标记块之外,当且仅当解决方案向dom添加了脚本元素时,并且永远不会作为单独的可查看脚本。) = &GT;鉴于我必须加载(和调试)多少脚本,这是不可接受的。
  • 使用'onreadystatechange'或'onload'事件的解决方案无法阻止,这是一个很大的问题,因为代码最初使用'require([filename,'dojo / domReady'])同步加载动态脚本;'我正在剥离道场。

我的最终解决方案,在返回之前加载脚本,并且在调试器中可以正确访问所有脚本(至少对于Chrome)如下:

警告:以下代码应该仅在'开发'模式下使用。 (对于'发布'模式,我建议预先包装和缩小而不使用动态脚本加载,或者至少不使用eval )。

//Code User TODO: you must create and set your own 'noEval' variable

require = function require(inFileName)
{
    var aRequest
        ,aScript
        ,aScriptSource
        ;

    //setup the full relative filename
    inFileName = 
        window.location.protocol + '//'
        + window.location.host + '/'
        + inFileName;

    //synchronously get the code
    aRequest = new XMLHttpRequest();
    aRequest.open('GET', inFileName, false);
    aRequest.send();

    //set the returned script text while adding special comment to auto include in debugger source listing:
    aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';

    if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
    {
        //create a dom element to hold the code
        aScript = document.createElement('script');
        aScript.type = 'text/javascript';

        //set the script tag text, including the debugger id at the end!!
        aScript.text = aScriptSource;

        //append the code to the dom
        document.getElementsByTagName('body')[0].appendChild(aScript);
    }
    else
    {
        eval(aScriptSource);
    }
};

答案 7 :(得分:2)

function include(file){
return new Promise(function(resolve, reject){
        var script = document.createElement('script');
        script.src = file;
        script.type ='text/javascript';
        script.defer = true;
        document.getElementsByTagName('head').item(0).appendChild(script);

        script.onload = function(){
        resolve()
        }
        script.onerror = function(){
          reject()
        }
      })

 /*I HAVE MODIFIED THIS TO  BE PROMISE-BASED 
   HOW TO USE THIS FUNCTION 

  include('js/somefile.js').then(function(){
  console.log('loaded');
  },function(){
  console.log('not loaded');
  })
  */
}

答案 8 :(得分:1)

我习惯在我的网站上有多个依赖于另一个的.js文件。要加载它们并确保以正确的顺序评估依赖项,我编写了一个函数来加载所有文件,然后,一旦收到它们,就eval()它们。主要缺点是,因为这不适用于CDN。对于这样的库(例如,jQuery),最好静态地包含它们。请注意,在HTML 动态中插入脚本节点并不能保证脚本按正确的顺序进行评估,至少不能在Chrome中进行评估(这是编写此功能的主要原因)。

function xhrs(reqs) {
  var requests = [] , count = [] , callback ;

  callback = function (r,c,i) {
    return function () {
      if  ( this.readyState == 4 ) {
        if (this.status != 200 ) {
          r[i]['resp']="" ;
        } 
        else {
          r[i]['resp']= this.responseText ;
        }
        c[0] = c[0] - 1 ;
        if ( c[0] == 0 ) {
          for ( var j = 0 ; j < r.length ; j++ ) {
            eval(r[j]['resp']) ;
          }
        }
      }
    }
  } ;
  if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
    requests.length = reqs.length ;
  }
  else {
    requests.length = 1 ;
    reqs = [].concat(reqs);
  }
  count[0] = requests.length ;
  for ( var i = 0 ; i < requests.length ; i++ ) {
    requests[i] = {} ;
    requests[i]['xhr'] = new XMLHttpRequest () ;
    requests[i]['xhr'].open('GET', reqs[i]) ;
    requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
    requests[i]['xhr'].send(null);
  }
}

我还没有想出如何在不创建数组(用于计数)的情况下引用相同的值。否则我认为这是不言自明的(当所有内容都被加载时,eval()按照给定的顺序每个文件,否则只是存储响应。)

用法示例:

xhrs( [
       root + '/global.js' ,
       window.location.href + 'config.js' ,
       root + '/js/lib/details.polyfill.min.js',
       root + '/js/scripts/address.js' ,
       root + '/js/scripts/tableofcontents.js' 
]) ;

答案 9 :(得分:1)


// ...

await import_script('https://cdnjs.cloudflare.com/ajax/libs/...js');

async function import_script(url) {

        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;


        document.head.appendChild(script);

        console.log(`import ${url} ...`);

        await new Promise((resolve) => script.onload = resolve);
    }

答案 10 :(得分:0)

具有讽刺意味的是,我有你想要的东西,但想要更接近你拥有的东西。

我正在动态地和异步地加载东西,但是使用load回调就像这样(使用dojo和xmlhtpprequest)

  dojo.xhrGet({
    url: 'getCode.php',
    handleAs: "javascript",
    content : {
    module : 'my.js'
  },
  load: function() {
    myFunc1('blarg');
  },
  error: function(errorMessage) {
    console.error(errorMessage);
  }
});

有关更详细的说明,请参阅here

问题在于代码被篡改的某个地方,如果代码有任何问题,console.error(errorMessage);语句将指示eval()所在的行,而不是实际错误。这是一个很大的问题,我实际上试图将其转换回<script>语句(请参阅here

答案 11 :(得分:0)

这适用于支持async/awaitfetch的现代“常绿” 浏览器。

此示例经过简化,没有错误处理,可以显示工作中的基本原理。

// This is a modern JS dependency fetcher - a "webpack" for the browser
const addDependentScripts = async function( scriptsToAdd ) {

  // Create an empty script element
  const s=document.createElement('script')

  // Fetch each script in turn, waiting until the source has arrived
  // before continuing to fetch the next.
  for ( var i = 0; i < scriptsToAdd.length; i++ ) {
    let r = await fetch( scriptsToAdd[i] )

    // Here we append the incoming javascript text to our script element.
    s.text += await r.text()
  }

  // Finally, add our new script element to the page. It's
  // during this operation that the new bundle of JS code 'goes live'.
  document.querySelector('body').appendChild(s)
}

// call our browser "webpack" bundler
addDependentScripts( [
  'https://code.jquery.com/jquery-3.5.1.slim.min.js',
  'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js'
] )