在 DOM 中创建一个 < script > 元素和 Requre.js 之间有什么区别?

使用 Requre.JS 与仅仅在 DOM 中创建 <script>元素有什么区别?

我对 Requre.JS 的理解是,它提供了加载依赖项的能力,但是这难道不能简单地通过创建一个加载必要的外部 JS 文件的 <script>元素来实现吗?

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

按照 Requre.JS 的方法来做:

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

通过简单地创建一个 script 元素:

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
}

这两个都有用。然而,第二个版本并不要求我加载所有的 Requre.js 库。我看不出有什么功能上的区别。

32982 次浏览

下面是 Ajajxian.com 上关于为什么使用它的一篇好文章:

RequreJS: 异步 JavaScript 加载

  • 某种类型的 # include/import/demand
  • 加载嵌套依赖项的能力
  • 易于开发人员使用,但随后又有一个优化工具支持,有助于部署

与在 DOM 中简单地创建元素相比,Requre.JS 提供了哪些优势?

在您的示例中,您正在异步创建 script 标记,这意味着您的 needMe()函数将被调用 之前 the need _ me。Js 文件完成加载。这会导致未定义函数的未捕获异常。

相反,为了让你的建议真正奏效,你需要这样做:

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);


}

可以说,使用像 RequreJS 这样的包管理器或者如上所述使用纯 JavaScript 策略可能是最好的,也可能不是最好的。虽然 Web 应用程序的加载速度可能更快,但是调用站点上的功能和特性的速度会更慢,因为在执行该操作之前需要等待加载资源。

If a Web application is built as a single-page app, then consider that people won't actually be reloading the page very often. In these cases, preloading everything would help make the experience seem faster when actually 使用 the app. In these cases, you're right, one can merely load all resources simply by including the script tags in the head or body of the page.

但是,如果构建的网站或 Web 应用程序遵循更传统的模型,即从一个页面转换到另一个页面,从而导致重新加载资源,那么延迟加载方法可能有助于加速这些转换。

为什么使用 RequreJS 有意义的其他一些非常尖锐的原因:

  1. 对于大型项目,管理自己的依赖关系很快就会分崩离析。
  2. 您可以拥有任意多的小文件,而不必担心保持依赖关系或加载顺序的跟踪。
  3. RequreJS 使得编写一个完整的、模块化的应用程序不需要接触窗口对象成为可能。

取自 Rmurphey 在本文要点中的评论

抽象层可能是一个难以学习和适应的噩梦,但是当它服务于一个目的并且做得很好时,它就是有意义的。

这里有一个更具体的例子。

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

  1. 加载一个连接版本,1个大文件。(制作)

  2. 加载所有60个文件(开发)

我们使用一个加载程序,所以我们只有一个脚本在网页

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

默认为模式 # 1(加载一个大的连接文件)。为了运行 in mode # 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,因为我们正在进行开发,我们必须添加一个 injectScript("somenewfile.js")行到 loader.js

然后,为了生产,我们还必须向构建脚本中添加一些 ewfile.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 脚本,但是我们可以使用 Reque.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(...);
});

Js 将为我们注入这些脚本,在 lib1被加载之前它不会注入 lib2,因为我们告诉它 lib2依赖于 lib1。在 lib2和 lib1都加载之前,它也不会启动使用 lib1的模块。

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

作为一个额外的好处,我们可以使用 webpack 的 babel 插件来运行旧版浏览器的 babel 代码,同样我们也不需要维护那个构建脚本。

请注意,如果 Chrome (我们选择的浏览器)开始真正支持 import,我们可能会切换到它的开发,但这不会真正改变任何事情。我们仍然可以使用 webpack 来创建一个连接文件,我们可以使用它在所有浏览器的代码上运行 babel。

所有这些都是通过不使用脚本标记和使用 AMD 获得的