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有一些非常明确的理由:
- pipe理自己的依赖关系很快就会被大规模的项目所破坏。
- 您可以拥有尽可能多的小文件,而不必担心跟踪依赖性或加载顺序。
- RequireJS可以在不触碰窗口对象的情况下编写完整的模块化应用程序。
从rmurphey在这个Gist的评论 。
抽象层可能是一个学习和适应的噩梦,但是当它达到目的并且做得好的时候,这是有道理的。
这是一个更具体的例子。
我在一个有60个文件的项目中工作。 我们有两种不同的运行模式。
-
加载连接版本,1个大文件。 (生产)
-
加载全部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来实现的