88

是否可以同步调用.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');

我只是不想过多地了解内部结构,并且可以说,“我希望使用这个模块,现在我将使用其中的一些代码。”

4

12 回答 12

142

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

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中。

于 2010-07-14T16:50:25.930 回答
26

这并不漂亮,但它有效:

<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()的 s 包裹起来,这样您就不必看它们了。您甚至可以进行测试并创建一个节点并附加它然后回退到document.write().

于 2010-07-20T17:42:44.487 回答
20

这已经很晚了,但为了将来参考任何想要这样做的人,您可以使用以下内容:

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 -加载/

于 2013-01-05T16:50:55.283 回答
7

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

  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
  }      
于 2016-03-16T02:41:00.923 回答
6

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

道格拉斯·克罗克福德 YUI 博客

好吧,扣好你的座位,因为这将是一个颠簸的旅程。越来越多的人询问如何通过 javascript 动态加载脚本,这似乎是一个热门话题。

它变得如此受欢迎的主要原因是:

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

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

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

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

有些人之所以坚持同步脚本,是因为他们习惯了。他们认为这是默认方式,这是更简单的方式,有些人甚至认为这是唯一的方式。

但是,当需要决定应用程序的设计时,我们唯一应该关心的是最终用户体验。在这方面,异步是不可战胜的。用户立即得到响应(或说承诺),承诺总比没有好。一个空白的屏幕吓坏了人们。开发人员不应该懒惰地提高感知性能

最后谈谈肮脏的一面。为了让它跨浏览器工作,你应该做什么:

  1. 学会异步思考
  2. 将您的代码组织为模块化
  3. 组织您的代码以很好地处理错误和边缘情况
  4. 逐步提高
  5. 始终注意适量的反馈
于 2010-07-14T22:41:50.707 回答
5
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');
  })
  */
}
于 2017-11-03T11:45:38.910 回答
4

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

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

或者,稍微更准确一点:

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

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

警告:以下代码可能仅在“开发”模式下使用。 (对于“发布”模式,我建议在不加载动态脚本的情况下进行预打包和缩小,或者至少不进行评估)。

//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);
    }
};
于 2014-02-15T08:29:06.810 回答
3

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

于 2010-07-14T16:38:28.733 回答
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' 
]) ;
于 2013-08-29T13:14:26.820 回答
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);
    }
于 2021-06-20T20:29:46.240 回答
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);
  }
});

有关更详细的说明,请参见此处

问题是代码在某处被评估,如果您的代码有任何问题,该console.error(errorMessage);语句将指示所在行eval(),而不是实际错误。这是一个很大的问题,我实际上正在尝试将其转换回<script>语句(请参阅此处.

于 2011-10-24T03:01:12.273 回答
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'
] )
于 2020-07-18T14:22:14.643 回答