使用Require.JS和在DOM中创建<script>
元素之间的区别是什么?
我对Require.JS的理解是它提供了加载依赖项的能力,但这不是简单地通过创建加载必要的外部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
}
});
只需创建一个脚本元素即可:
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库。我真的没有看到任何功能上的差异......
答案 0 :(得分:52)
与简单地在DOM中创建元素相比,Require.JS提供了哪些优势?
在您的示例中,您将异步创建脚本标记,这意味着在 need_me.js文件完成加载之前,将调用您的needMe()
函数。这会导致未定义函数的未捕获异常。
相反,为了使你所建议的实际工作,你需要做这样的事情:
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之类的包管理器或使用如上所示的纯JavaScript策略可能是也可能不是最好的。虽然您的Web应用程序可能加载速度更快,但调用站点上的功能和功能会更慢,因为它会涉及在执行该操作之前等待加载资源。
如果将Web应用程序构建为单页应用程序,那么请考虑人们实际上不会经常重新加载页面。在这些情况下,预加载所有内容有助于在实际使用应用时让体验看起来更快。在这些情况下,你是对的,只需在页面的头部或正文中包含脚本标记,就可以加载所有资源。
但是,如果构建一个遵循更传统模型的网站或Web应用程序,其中一个页面从一个页面转换到另一个页面,导致资源重新加载,则延迟加载方法可能有助于加速这些转换。
答案 1 :(得分:43)
以下是ajaxian.com上关于使用它的好文章:
RequireJS: Asynchronous JavaScript loading
答案 2 :(得分:9)
使用RequireJS的其他一些非常尖锐的原因是有道理的:
取自rmurphey's comments here in this Gist。
抽象层可能是学习和适应的噩梦,但是当它有用并且做得好时,它才有意义。
答案 3 :(得分:0)
这是一个更具体的例子。
我正在一个包含60个文件的项目中工作。我们有两种不同的运行模式。
加载连接版本,1个大文件。 (生产)
加载所有60个文件(开发)
我们正在使用加载器,所以我们在网页上只有一个脚本
<script src="loader.js"></script>
默认为模式#1(加载一个大的连接文件)。要运行in模式#2(单独的文件),我们设置了一些标志。它可能是任何东西。查询字符串中的一个键。在这个例子中我们只是这样做
<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");
}
构建脚本只是一个看起来像这样的.sh文件
cat > large-concantinated.js app.js somelib.js someotherlib.js anotherlib.js
等...
如果添加了新文件,我们可能会使用模式#2,因为我们正在进行开发,我们必须向loader.js添加injectScript("somenewfile.js")
行
然后在生产中我们还必须将一些newfile.js添加到我们的构建脚本中。我们经常忘记的一步,然后收到错误消息。
通过切换到AMD,我们不必编辑2个文件。保持loader.js和构建脚本同步的问题消失了。使用r.js
或webpack
,只需阅读构建large-concantinated.js
它也可以处理依赖项,例如我们有两个文件lib1.js和lib2.js像这样加载
injectScript("lib1.js");
injectScript("lib2.js");
lib2需要lib1。它内部的代码类似于
lib1Api.installPlugin(...);
但是,由于注入的脚本是异步加载的,因此无法保证它们将以正确的顺序加载。这两个脚本不是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。在lib2和lib1都加载之前,它也不会启动使用lib1的模块。
这使得开发很好(没有构建步骤,不用担心加载顺序)并且它使生产更好(不需要为每个添加的脚本更新构建脚本)。
作为一个额外的好处,我们可以使用webpack的babel插件为旧浏览器的代码运行babel,而且我们也不必维护该构建脚本。
请注意,如果Chrome(我们选择的浏览器)开始支持import
,我们可能会切换到开发版,但这不会改变任何内容。我们仍然可以使用webpack来创建一个连接文件,我们可以使用它运行babel而不是所有浏览器的代码。
所有这些都是通过不使用脚本标签和使用AMD
获得的