Классической считается загрузка JavaScript кода посредством тега script в обычных приложениях на JavaScript. Но… что делать, если скрипты требуется подгружать динамически? Тут вариантов несколько:

1. Воспользоваться возможностями HTML и «добавить» в новый элемент с тегом script:

document.write('<script type="text/javascript" src="myScript.js"></script>');

… или…

    var loadedJS = document.createElement('script');
    loadedJS.src = "myScript.js";
    loadedJS.type = "text/javascript";
    loadedJS.language = "javascript";
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(loadedJS);

… или…

 var head = document.getElementsByTagName('head')[0];
 head.innerHTML+= '<script type="text/javascript" src="myScript.js"></script>';

2. Воспользоваться <iframe src=«loadJS.html#myScript.js», внутри которого уже подгрузим JS в зависимости от хэша и выполним его.

3. Воспользоваться AJAX.

Этот способ меня и заинтересовал в свое время. В чем его преимущество? В том, что скрипт можно подгрузить в любом месте выполнения программы на JS и он гарантированно выполнится до того, как интерпретатор перейдет к обработке следующей команды.

Создадим объект js с одним свойством «root», обозначающим путь к папке со скриптами:

js= {root : ''};

После этого добавим объекту свойство — modules, являющееся объектом, хранящим состояния подгруженных скриптов:

js.modules = {};

Теперь опишем функцию самой подгрузки скрипта и выполнения его:

js.include= function(_path) {
 // На тот случай, если файл в подпапке
 var _id= _path.replace(/\//g, ''); 
 // Если скрипт уже подгружен, выходим
 if (js.modules[_id]) return;
 // Открываем доступ через AJAX
 var reader = new XMLHttpRequest();  
 // Настраиваем синхронный метод подгрузки
 reader.open('GET', js.root+_path+'.js', false);
 // Отсылаем запрос на получение файла 
 reader.send(null);
 // Запоминаем исходный код файла
 var sourceCode= reader.responseText;
 // Устанавливаем флаг "загружен" для соответствующего модуля
 js.loaded(_id); 
 // Выполняем подгруженный код
 eval(sourceCode); 
}

Так же допишем функцию «loaded»:

js.loaded= function(_id) {
 js.modules[_id]= true; 
}

Теперь в любом месте программы можно вызвать

 js.include('myScript.js'); 

и скрипт выполнится, при этом программа подождет выполнения вашего скрипта, и только потом продолжит выполнение.

Благодаря «массиву» loaded скрипты, при циклической зависимости не будут «вешать» систему, так как каждый скрипт выполнится лишь единожды за время жизни страницы.

Также вы можете прописать js.loaded() в любом файле, который подгружаете через , чтобы он не выполнялся через include.

Это на тот случай, если требуется сделать подгрузчик скриптов «по-быстрому». Также можно воспользоваться библиотекой «RequireJS», выполняющей те же действия.

Если результат выполнения скрипта вам не нужен прямо сейчас, то загрузку можно выполнить в асинхронном режиме, и страница не будет ожидать, пока выполнится ваш файл, а продолжит выполнение следующих команд. Ваш файл выполнится сразу, как только загрузится. Сама функция include() будет выглядеть след. образом:
js.include= function(_path) {
 // На тот случай, если файл в подпапке
 var _id= _path.replace(/\//g, ''); 
 // Если скрипт уже подгружен, выходим
 if (js.modules[_id]) return;
 // Открываем доступ через AJAX
 var reader = new XMLHttpRequest();  
 // Настраиваем асинхронный метод подгрузки
 reader.open('GET', js.root+_path+'.js');
 // объявляем функцию после загрузки
 reader.onreadystatechange = function ()   
  {  
   // проверяем на готовность
   if(reader.readyState == 4)   
   {  
    // обрабатываем
    var sourceCode= reader.responseText;
    eval(sourceCode);
    // Устанавливаем флаг "загружен" для соответствующего модуля
    js.loaded(_id);
   }  
  }  
 // Отсылаем запрос на получение файла 
 reader.send(null);
}

Видео пример

Комментарии (36)


  1. lair
    02.09.2015 11:18
    +5

    Зачем писать собственный велосипед, когда есть AMD, CommonJS и RequireJS, их поддерживающий?


    1. m0sk1t
      02.09.2015 11:29
      +3

      Мне кажется автор не предложил свой велосипед а просто описал способы.


      1. lair
        02.09.2015 11:30
        +1

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


        1. m0sk1t
          02.09.2015 11:52
          -2

          Солидарен с вами) Из песочницы всё реже нормальные статьи идут…


  1. zxcabs
    02.09.2015 11:35
    +9

    Вы серьезно предлагаете вешать браузер на время загрузки скрипта?


  1. bertmsk
    02.09.2015 11:49
    +2

    Вообще динамическая подгрузка скриптов (именно скриптов, а не данных JSON) говорит об ужасной архитектуре системы.
    Обычно скрипты компонуются в 1 файл и «сжимаются» кложурой.


    1. Aingis
      02.09.2015 12:31

      Вполне годный способ — грузить сначала нужное для критического пути, а потом всё остальное. Например, зачем вам всегда грузить скрипты для формы обратной связи, если ими пользуются лишь считанные проценты пользователей? Другое дело, что вопрос модульной загрузки разработан вдоль и поперёк.


    1. Delphinum
      02.09.2015 13:02
      -1

      А если по ограничениям безопасности, применяемым в компании, нельзя загружать некоторый скрипт до определенного условия?


    1. Nadoedalo
      02.09.2015 16:54

      Серьёзно? У кого-то например ~100 КБ минифицированных и gzip-нутых исходников. Это не считая файлов переводов, шаблонов и css(ещё каждый примерно столько же). Итого пол-метра получится не считая библиотек и данных. Это если всё сразу тащить получится что сайт откроется примерно никогда, потому что @#%$ пользователь ваш сайт который открывается 5+ секунд. А на данный момент чё-то происходит на экране уже через 250 мс, а потом данные плавно подгружаются в течении 2-3 секунд после.


  1. XanderBass
    02.09.2015 12:58

    Ещё один случай, когда хочется ткнуть в проект «Грёбаный сайт». Вы всерьёз предлагаете..? Ну в общем выше написали уже этот вопрос про загрузку. Прелесть обычной компоновки тегами в том, что оная не вешает наглухо страницу во время подгрузки содержимого. К тому же остаётся актуальным самый простой вопрос: зачем? Где можно применять такую методику вообще? Если у меня к примеру странички генерируются при помощи того же PHP, задачу по отбору скриптов для подгрузки я возложу на шаблонизатор (теги script). Для фронтенда веб-приложения необходимые библиотеки так и так должны подгружаться. Так что тут опять теги. Форма обратной связи, если есть — тег в чанке формы.


    1. lair
      02.09.2015 13:00

      Где можно применять такую методику вообще?

      Конкретно авторскую — нигде. А вообще модульную загрузку — в сложных страницах, когда не вся функциональность нужна сразу (а иногда не нужна вообще). Проблема с тегами в том, что они грузят скрипты всегда, а зачем нам это счастье?


  1. volk0ff
    02.09.2015 14:37

    Также вы можете прописать js.loaded() в любом файле, который подгружаете через,

    Через что? по моему там слово пропущено


    1. symbix
      02.09.2015 17:47

      Наверное, это цензура :-)


  1. Yavanosta
    02.09.2015 15:12
    +2

    Eval + синхронный http запрос. Ультракилл.


    1. DenimTornado
      03.09.2015 11:39

      Лучше сразу в висок, чего уж там.


  1. oWeRQ
    02.09.2015 16:26

    Если хочется велосипед, почему бы не сделать его хотя бы с круглыми колесами, как-то так:

    function loadScript(src, onload) {
    	var head = document.head || document.querySelector('head') || document.getElementsByTagName('head')[0];
    	var script = document.createElement('script');
    	script.async = true;
    	script.type = 'text/javascript';
    	script.src = src;
    	script.onload = onload;
    	head.appendChild(script);
    }
    

    Или можно просто воспользоваться jQuery.getScript.


    1. Dobby007
      02.09.2015 19:09

      jQuery.getScript. не гарантирует выполнение функции success после выполнения загружаемого скрипта — она может выполниться еще до этого. Они так и пишут в документации: «The callback is fired once the script has been loaded but not necessarily executed.»


      1. oWeRQ
        02.09.2015 19:52

        Спасибо за уточнение, надо будет посмотреть реализацию в jQuery, вероятно тому же подвержена приведенная выше loadScript, но оно должно быть решено в shim в requirejs.


        1. Dobby007
          02.09.2015 22:27

          Там все очень-очень хитро… Можете тут почитать про порядок выполнения скриптов: https://hsivonen.fi/script-execution/


    1. Serator
      03.09.2015 12:40
      -2

      script.type = 'text/javascript';
      

      Можно опусть, 2015 год на дворе. ;)


  1. mwizard
    02.09.2015 19:36
    +1

    Вместо выдумывания велосипедов, обратите взор к стандарту.

    Для загрузки на этапе компоновки (синхронной с точки зрения скрипта, асинхронной с точки зрения платформы):

    import Foobar from './foobar';
    import { LolWutService, LolWutIntf } from './services/lolwut';
    import * from './oh-you';
    

    Для динамической загрузки (в зависимости от стандарта, который примут):
    try {
        let module1 = await System.import("./foobar", { ...options });
        let module2 = await Reflect.Loader.import("./foobar", { ...options });
    } catch(e) {
        // import error!
    }
    

    System.import возвращает Promise, с которым потом можно делать, что угодно — например, await-нуть.

    Реализация первого способа — babel. Реализация второго — es6-module-loader или systemjs.


  1. ko11ega
    02.09.2015 23:56
    -1

    А еще можно вот так:

    import 'package:deferred/hello.dart' deferred as hello;
    
    greet() async {
      await hello.loadLibrary();
      hello.printGreeting();
    }

    Не дожидаясь стандартов которые когда-нибудь примут, не изобретая собственных велосипедов, не используя сторонних библиотек. Уже год как можно, причем какой бы стандарт не приняли, код менять не придется…
    Но это на Dart, с трансляцией в JavaScript, а Dart ведь не нужен, так?
    Google обязательно его забросит как и другие свои проекты…
    Dart on mobile is incredibly exciting. Mobile has eaten the world, we think Dart is a great fit there. And don't forget, Dart is used by Ads engineers every day to build next-gen systems.?
    Seth Ladd


    1. mwizard
      03.09.2015 01:55

      Dart ведь не нужен, так?
      Ну, вообще да, так. Dart можно смело выбрасывать — это мертворожденная поделка без единого способа испооьзовать его на практике. Конечно, для академических целей оно может и то, что нужно, но для практических задач существуют ES6 и ES7 :)


      1. ko11ega
        04.09.2015 02:18

        Вам конечно виднее, как оно там в вашей версии реальности.
        Напомню просто

        Один пацан писал все на JavaScript, и клиент, и сервер, говорил что нравится, удобно, читабельно. Потом его в дурку забрали, конечно

        Я к тому, что эта шутка была популярна и уместна несколько лет назад и аргументация в ней, как и в ваших заявлениях про Dart, отсутствует начисто, а эффект ее применения основывался на нечастом использовании fullstack Javascript на тот момент и на ярких эмоциональных манипуляциях. :)

        А если пройтись по фактам, то можно понять как работает психика людей с стокгольмским синдромом. Если на протяжении многих лет учишься «взлетать из болота размахивая костылями» и гробишь на это кучу времени и нервных клеток, то защитные механизмы психики будут защищать и рационализировать эти вынужденные инвестиции.
        Не вы были вынуждены программировать на корявом языке с проблемной отладкой при помощи набора разнообразных хаков из-за того что его просто поддерживает каждый браузер, а JavaScript замечательный язык на котором можно писать и Enterprise решения, вот дождемся ES7 и все будет совсем хорошо.

        Теперь касательно фактов конкретно вашего персонально недопонимания того как можно использовать Dart на практике и зачем. Ниже будут примеры ребят которые понимают, используют и зарабатывают при помощи софта написанного на Dart.
        Там и картинки красивые есть, без лишнего академизма.
        Софт для визуальной настройки InternetOfThings, умный дом по нашему. А вот видео где они рассказывают как у них это сделано и что им дал Dart.
        Вот пример забавной игры.
        А вот пример того как с Node.js пересели на Dart и ради чего.

        Ну и написал я это конечно же не для того чтобы конкретно вас в чем то переубедить, а чтобы у тех «начинающих» кто дорос до того, что в его веб-проектах возникают вопросы важности отложенной загрузки кода(как думаете сколько строк кода д.б. в проекте что был смысл делать отложенную загрузку его частей и какие вопросы при разработке таких проектов самые значимые?), была возможность ознакомиться с тем, что можно программировать и без костылей и велосипедов, получая удовольствие от программирования и отладки, программировать эффективнее и быстрее чем на JavaScript, получая в результате более простой, понятный и лучше сопровождаемый код.


  1. lagranzh
    03.09.2015 10:52
    -1

    А выгружать javascript можно? столкнулся с тем, что чтобы поменять язык у гугл карт, нужно перегружать страницу.


    1. SkanerSoft
      03.09.2015 11:06

      Можете немного подробнее описать суть?


      1. lagranzh
        03.09.2015 11:18

        На сайте показывается карта от google maps. для этого подгружается javascript в заголовке страницы:

        maps.googleapis.com/maps/api/js?v=3.exp&libraries=places,drawing&language=ru

        И теперь карта будет показываться по русски. Но если пользователь меняет язык, то приходится перегружать страницу, т.к. google api не предусматривает смену языка.

        Вот я и подумал, раз можно подгузить javascript, может можно его и выгрузить что бы загрузить заново с другим языком?


        1. mwizard
          03.09.2015 11:43

          Универсального способа что-то выгрузить не существует. Например, допустим, что у вас скрипт вот такого вида:

          window.googleMaps.language = 'ru';

          Как вы предлагаете «отменить» эту операцию, учитывая, что предыдущее состояние language, если оно было, уже разрушено?

          Что касается именно вашего случая — попробуйте запихнуть карту в iframe. При изменении языка:
          1. затените существующую карту и выведите спиннер поверх;
          2. создайте второй такой же iframe, но невидимый;
          3. повесьте событие на window load в iframe, чтобы загрузилось вообще все — или изнутри iframe отправляйте сообщение родительскому окну через window.postMessage();
          4. начните загрузку карты с правильным языком;
          5. по завершению загрузки разместите второй iframe в точности на месте первого, и сделайте его видимым и уровнем выше, чем изначальный iframe;
          6. удалите предыдущий iframe;

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


          1. lagranzh
            03.09.2015 11:47

            спасибо. Думаю что действительно iframe, это то что мне надо.


        1. grayfolk
          03.09.2015 12:30

          А что-то такое не подойдет?


          1. lagranzh
            03.09.2015 12:56

            Проблема в том, что язык выбирается в при подгрузке скрипта, и изменить его потом нельзя. Но выше уже подсказали пользовать iframe.


            1. SkanerSoft
              03.09.2015 12:59

              iframe — в вашем случае это действительно хороший выбор. Выглядит натурально и всю страницу перегружать не нужно =)


            1. grayfolk
              03.09.2015 13:28
              +1

              Так а что мешает выполнить, как по ссылке, еще раз google.load() с другими параметрами?

              Подгрузить js с другим языком и выполнить инициализацию карты снова.


            1. grayfolk
              03.09.2015 14:26

              Да, согласен. Таки действительно нельзя.


  1. lair
    03.09.2015 15:00
    +1

    1. SkanerSoft
      03.09.2015 15:13
      -2

      24 подписчика даже есть)