document.createElement("script")同步

Jos*_*son 73 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>
Run Code Online (Sandbox Code Playgroud)

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

思考?

编辑:

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

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

myFunc1('blarg');
myFunc2('bleet');
Run Code Online (Sandbox Code Playgroud)

我只是想从不必知道的内部太多,只能够说,保持"我希望用这个模块,现在我会使用一些代码吧."

Poi*_*nty 120

您可以<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);
Run Code Online (Sandbox Code Playgroud)

你不能同步做到这一点.

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

编辑 - 截至2013年初,我强烈建议您研究一个更强大的脚本加载工具,如Requirejs.有很多特殊情况需要担心.对于非常简单的情况,有yepnope,现在已经内置到Modernizr中.

  • 真??加载脚本时谁不会触发"加载"事件?**等等** - 别告诉我. (67认同)
  • 当然,document.write()就是你要找的.不漂亮,但它的工作原理. (4认同)
  • 不幸的是,它不是跨浏览器. (3认同)
  • 但是,requirejs怎么做呢?它们如何包含许多脚本并以正确的顺序触发它们? (3认同)

Jos*_*son 23

这不是很好,但它有效:

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

<script type="text/javascript">
  functionFromOther();
</script>
Run Code Online (Sandbox Code Playgroud)

要么

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

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

这不起作用:

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

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

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

  • 你不应该再使用 document.write() 。请参阅:https://developers.google.com/web/updates/2016/08/removing-document-write 和 https://varvy.com/pagespeed/avoid-document-write.html (3认同)

zco*_*rts 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);
}
Run Code Online (Sandbox Code Playgroud)

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


gbl*_*zex 7

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

Douglas Crockford (YUI博客)

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

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

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

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

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

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

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

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

最后一些关于脏的方面的话.你应该怎么做才能让它跨浏览器工作:

  1. 学会异步思考
  2. 组织您的代码是模块化的
  3. 组织您的代码以便很好地处理错误和边缘情况
  4. 逐步提升
  5. 始终关注适量的反馈

  • Galam,非常好点.用户体验非常重要.要清楚,我不愿意牺牲用户体验或质量,可维护的代码.我将调查关闭和实验,看看他们能为我做些什么.但是暂时我可能需要坚持使用<script>标签.不幸的是,我不是自己做这个.我与一个中等规模的开发团队合作,因此可维护的代码是高优先级.如果每个人都无法弄清楚如何有效地使用lib,那么用户exp就会出现在窗外.回调很直观.因为您导入包而不是回调. (2认同)
  • 如果需要同步加载怎么办?如果您确实需要阻止以保留用户体验。如果您使用基于 JavaScript 的 A/B 或 MVT 测试系统。您希望如何异步加载内容并替换默认内容,而又不会出现破坏用户体验的闪烁效果?我愿意接受建议。我有 500 多名同事想知道这个问题的解决方案。如果您没有,请不要使用诸如“同步编程是不尊重的,不应该在人们使用的应用程序中使用”之类的表达方式。 (2认同)

小智 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
  }      
Run Code Online (Sandbox Code Playgroud)

  • @JoshJohnson `script.addEventListener('load', postLoadFunction);` 表示在脚本加载时调用 postLoadFunction。 (2认同)

Dag*_*nqx 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');
  })
  */
}
Run Code Online (Sandbox Code Playgroud)