Какая разница между Require.js и просто созданием элемента <script> в DOM?

В чем разница между использованием Require.JS amd, просто созданием элемента <script> в DOM?

Мое понимание Require.JS заключается в том, что он предлагает возможность загружать зависимости, но может ли это не просто сделать, создав элемент <script>, который загружает необходимый внешний JS файл?

Например, допустим, что у меня есть функция 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
    }
});

Выполняя это, просто создав элемент 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
}

Обе эти работы. Однако вторая версия не требует загрузки всей библиотеки Require.js. Я не вижу никакой функциональной разницы...

Ответ 1

Вот хорошая статья на ajaxian.com о том, зачем ее использовать:

RequireJS: загрузка асинхронного JavaScript

  • что-то вроде # include/import/require
  • возможность загрузки вложенных зависимостей
  • простота использования для разработчика, но затем поддерживается инструментом оптимизации, который помогает развертывать

Ответ 2

Какие преимущества предлагает Require.JS по сравнению с просто созданием элемента в DOM?

В вашем примере вы создаете тег script асинхронно, что означает, что ваша функция needMe() будет вызвана до того, как файл 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 safe to use it!");

            // do some stuff
            needMe();
            //do some more stuff

        }, false);

    document.getElementsByTagName('head')[0].appendChild(scriptElement);

}

Возможно, может быть или не лучше использовать диспетчер пакетов, такой как RequireJS, или использовать стратегию чистого JavaScript, как показано выше. Хотя ваше веб-приложение может загружаться быстрее, вызывать функции и функции на сайте будут медленнее, поскольку это потребует ожидания загрузки ресурсов до того, как это действие может быть выполнено.

Если веб-приложение создано как одностраничное приложение, тогда подумайте, что на самом деле люди не будут перегружать страницу очень часто. В этих случаях предварительная загрузка всех поможет ускорить работу при использовании приложения. В этих случаях вы правы, вы можете просто загрузить все ресурсы, просто включив теги script в голову или тело страницы.

Однако, если вы создаете веб-сайт или веб-приложение, которое следует за более традиционной моделью, где переходы со страницы на страницу, заставляя ресурсы перезагружаться, подход с ленивой загрузкой может помочь ускорить эти переходы.

Ответ 3

Некоторые другие очень острые причины, почему использование RequireJS имеет смысл:

  • Управление вашими зависимостями быстро разваливается для значительных проектов.
  • У вас может быть столько маленьких файлов, сколько вы хотите, и вам не нужно беспокоиться о отслеживании зависимостей или порядка загрузки.
  • RequireJS позволяет писать целое модульное приложение, не касаясь оконного объекта.

Взято из rmurphey комментарии здесь, в этом Gist.

Слои абстракции могут быть кошмаром, чтобы учиться и приспосабливаться, но когда он служит цели и делает это хорошо, это имеет смысл.

Ответ 4

Вот более конкретный пример.

Я работаю над проектом с 60 файлами. У нас есть два разных режима работы.

  • Загрузите конкатенированную версию, 1 большой файл. (Изготовление)

  • Загрузите все 60 файлов (разработка)

Мы используем загрузчик, поэтому у нас есть только один script на веб-странице

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

По умолчанию используется режим # 1 (загрузка одного большого конкатенированного файла). Для запуска в режиме # 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");
}

Строка script - это просто файл .sh, который выглядит как

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

и т.д...

Если новый файл будет добавлен, мы, скорее всего, будем использовать режим # 2, так как мы делаем разработку, мы должны добавить строку injectScript("somenewfile.js") в loader.js

Затем позже для производства нам также нужно добавить somenewfile.js в нашу сборку script. Шаг, который мы часто забываем, а затем получаем сообщения об ошибках.

При переключении на AMD нам не нужно редактировать 2 файла. Проблема сохранения loader.js и сборки script синхронизируется. Используя r.js или webpack, он может просто прочитать код для сборки large-concantinated.js

Он также может работать с зависимостями, например, у нас было 2 файла lib1.js и lib2.js, загруженные как это

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

lib2 требуется lib1. У этого есть код внутри, который делает что-то вроде

lib1Api.installPlugin(...);

Но поскольку загруженные сценарии загружаются асинхронно, нет гарантии, что они будут загружаться в правильном порядке. Эти 2 скрипта не являются сценариями 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 будет вводить скрипты для нас, и он не будет вводить lib2 до загрузки lib1, так как мы сказали, что lib2 зависит от lib1. Он также не запустит наш модуль, который будет использовать lib1 до тех пор, пока не загрузятся как lib2, так и lib1.

Это делает разработку приятной (без шага сборки, не беспокоясь о порядке загрузки), и это делает производство приятным (нет необходимости обновлять сборку script для каждого добавленного script).

В качестве дополнительного бонуса мы можем использовать плагин webpack babel для запуска babel над кодом для старых браузеров, и снова нам не нужно поддерживать эту сборку script.

Обратите внимание, что если Chrome (наш браузер по выбору) начнет поддерживать import для реального, мы, вероятно, переключимся на это для разработки, но это ничего не изменит. Мы могли бы использовать webpack для создания конкатенированного файла, и мы могли бы использовать его для запуска браузера для кода для всех браузеров.

Все это достигается за счет использования тегов script и использования AMD