Require.js和在DOM中创建<script>元素之间的区别是什么?</script>

时间:2011-02-06 18:22:27

标签: javascript requirejs dynamic-script-loading js-amd

使用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库。我真的没有看到任何功能上的差异......

4 个答案:

答案 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

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

答案 2 :(得分:9)

使用RequireJS的其他一些非常尖锐的原因是有道理的:

  1. 对于规模较大的项目,快速管理您自己的依赖项。
  2. 您可以拥有任意数量的小文件,而不必担心跟踪依赖关系或加载顺序。
  3. RequireJS可以在不触及窗口对象的情况下编写整个模块化应用程序。
  4. 取自rmurphey's comments here in this Gist

    抽象层可能是学习和适应的噩梦,但是当它有用并且做得好时,它才有意义。

答案 3 :(得分:0)

这是一个更具体的例子。

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

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

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

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

    <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.jswebpack,只需阅读构建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

    获得的