Require.js和在DOM中简单地创build一个<script>元素有什么区别?

在使用Require.JS和在DOM中简单地创build<script>元素之间有什么区别?

我对Require.JS的理解是,它提供了加载依赖的能力,但是这不能简单地通过创build加载必要的外部JS文件的<script>元素来完成。

例如,让我们假设我有函数doStuff() ,它需要函数needMe()doStuff()在外部文件do_stuff.js ,而needMe()在外部文件need_me.js

这样做的Require.JS方式:

 define(['need_me'],function(){ function doStuff(){ //do some stuff needMe(); //do some more stuff } }); 

通过简单地创build一个脚本元素来做到这一点:

 function doStuff(){ var scriptElement = document.createElement('script'); scriptElement.src = 'need_me.js'; scriptElement.type = 'text/javascript'; document.getElementsByTagName('head')[0].appendChild(scriptElement); //do some stuff needMe(); //do some more stuff } 

这两个工作。 但是,第二个版本不需要我加载所有的Require.js库。 我真的没有看到任何function差异…

这里是关于为什么使用它的ajaxian.com上的好文章:

RequireJS:asynchronousJavaScript加载

  • 某种#include / import / require
  • 加载嵌套的依赖关系的能力
  • 易于使用的开发人员,但后来由一个优化工具,有助于部署支持

与仅仅在DOM中创build元素相比,Require.JS提供了什么优势?

在你的例子中,你是asynchronous创build脚本标记,这意味着你的needMe()函数将在need_me.js文件加载完成之前被调用。 这会导致您的函数未定义的未捕获exception。

相反,为了使你的build议实际工作,你需要做这样的事情:

 function doStuff(){ var scriptElement = document.createElement('script'); scriptElement.src = 'need_me.js'; scriptElement.type = 'text/javascript'; scriptElement.addEventListener("load", function() { console.log("script loaded - now it's safe to use it!"); // do some stuff needMe(); //do some more stuff }, false); document.getElementsByTagName('head')[0].appendChild(scriptElement); } 

可以说,使用诸如RequireJS之类的包pipe理器或者如上所述地使用纯粹的JavaScript策略可能是也可能不是最好的。 虽然您的Web应用程序加载速度更快,但调用网站上的function和function会比较慢,因为在执行操作之前,需要等待资源加载。

如果Web应用程序构build为单页面应用程序,那么请考虑人们实际上不会经常重新加载页面。 在这些情况下,预先加载所有内容有助于在实际使用应用程序时让体验看起来更快。 在这些情况下,您是对的,只需在页面的头部或主体中包含脚本标记即可加载所有资源。

但是,如果构build一个遵循更传统模式的网站或Web应用程序(每个页面在页面之间切换),导致资源被重新加载,则延迟加载方法可能有助于加速这些转换。

为什么使用RequireJS有一些非常明确的理由:

  1. pipe理自己的依赖关系很快就会被大规模的项目所破坏。
  2. 您可以拥有尽可能多的小文件,而不必担心跟踪依赖性或加载顺序。
  3. RequireJS可以在不触碰窗口对象的情况下编写完整的模块化应用程序。

从rmurphey在这个Gist的评论 。

抽象层可能是一个学习和适应的噩梦,但是当它达到目的并且做得好的时候,这是有道理的。

这是一个更具体的例子。

我在一个有60个文件的项目中工作。 我们有两种不同的运行模式。

  1. 加载连接版本,1个大文件。 (生产)

  2. 加载全部60个文件(开发)

我们正在使用一个加载器,所以我们只有一个脚本在网页上

 <script src="loader.js"></script> 

默认为模式#1(加载一个大的连接文件)。 要运行模式#2(单独的文件),我们设置一些标志。 这可能是任何事情。 查询string中的一个键。 在这个例子中,我们只是这样做

 <script>useDebugVersion = true;</script> <script src="loader.js"></script> 

loader.js看起来像这样

 if (useDebugVersion) { injectScript("app.js"); injectScript("somelib.js"); injectScript("someotherlib.js"); injectScript("anotherlib.js"); ... repeat for 60 files ... } else { injectScript("large-concatinated.js"); } 

构build脚本只是一个看起来像这样的.sh文件

 cat > large-concantinated.js app.js somelib.js someotherlib.js anotherlib.js 

等等…

如果添加一个新文件,我们可能会使用模式#2,因为我们正在开发,我们必须添加一个injectScript("somenewfile.js")行到loader.js

然后,为了生产,我们还必须添加一些newfile.js到我们的构build脚本。 我们经常忘记一个步骤,然后得到错误信息。

通过切换到AMD,我们不必编辑2个文件。 保持loader.js和构build脚本同步的问题消失了。 使用r.js或者webpack就可以读取代码来构buildlarge-concantinated.js

它也可以处理依赖关系,例如我们有两个文件lib1.js和lib2.js像这样加载

 injectScript("lib1.js"); injectScript("lib2.js"); 

lib2需要lib1。 它有代码里面有类似的东西

 lib1Api.installPlugin(...); 

但是,由于注入的脚本是asynchronous加载的,因此不能保证它们将以正确的顺序加载。 这两个脚本不是AMD脚本,但使用require.js我们可以告诉它的依赖关系

 require.config({ paths: { lib1: './path/to/lib1', lib2: './path/to/lib2', }, shim: { lib1: { "exports": 'lib1Api', }, lib2: { "deps": ["lib1"], }, } }); 

我使用lib1我们的模块,我们这样做

 define(['lib1'], function(lib1Api) { lib1Api.doSomething(...); }); 

现在require.js会为我们注入脚本,直到lib1被加载后才会注入lib2,因为我们告诉它lib2依赖于lib1。 它也不会启动我们的模块使用lib1,直到lib2和lib1已经加载。

这使得开发更好(没有构build步骤,不用担心加载顺序),它使生产更好(不需要为每个添加的脚本更新构build脚本)。

作为一个额外的好处,我们可以使用webpack的babel插件来运行babel而不是旧版浏览器的代码,我们也不必维护这个构build脚本。

请注意,如果Chrome(我们select的浏览器)开始支持import ,我们可能会切换到开发版,但这并不会改变任何内容。 我们仍然可以使用webpack创build一个连接的文件,我们可以使用它运行所有浏览器的代码babel。

所有这一切都是通过不使用脚本标签和使用AMD来实现的