Как включить несколько файлов js с помощью метода jQuery $ .getScript ()


127

Я пытаюсь динамически включать файлы javascript в свой файл js. Я провел небольшое исследование по этому поводу и обнаружил, что метод jQuery $ .getScript () был бы желательным путем.

// jQuery
$.getScript('/path/to/imported/script.js', function()
{
    // script is now loaded and executed.
    // put your dependent JS here.
    // what if the JS code is dependent on multiple JS files? 
});

Но мне интересно, может ли этот метод загружать несколько скриптов одновременно? Я спрашиваю об этом, потому что иногда мой файл javascript зависит от нескольких файлов js.

Заранее спасибо.

Ответы:


315

Ответ

Вы можете использовать обещания getScript()и ждать, пока загрузятся все скрипты, например:

$.when(
    $.getScript( "/mypath/myscript1.js" ),
    $.getScript( "/mypath/myscript2.js" ),
    $.getScript( "/mypath/myscript3.js" ),
    $.Deferred(function( deferred ){
        $( deferred.resolve );
    })
).done(function(){
    
    //place your code here, the scripts are all loaded
    
});

FIDDLE

ДРУГОЙ FIDDLE

В приведенном выше коде добавление Deferred и его разрешение внутри $()похоже на размещение любой другой функции внутри вызова jQuery, например $(func), это то же самое, что

$(function() { func(); });

т.е. он ожидает, пока DOM будет готов, поэтому в приведенном выше примере $.whenожидает загрузки всех скриптов и готовности DOM из-за $.Deferredвызова, который разрешается в обратном вызове готовности DOM.


Для более общего использования удобная функция

Служебная функция, которая принимает любой массив скриптов, может быть создана следующим образом:

$.getMultiScripts = function(arr, path) {
    var _arr = $.map(arr, function(scr) {
        return $.getScript( (path||"") + scr );
    });
        
    _arr.push($.Deferred(function( deferred ){
        $( deferred.resolve );
    }));
        
    return $.when.apply($, _arr);
}

который можно использовать так

var script_arr = [
    'myscript1.js', 
    'myscript2.js', 
    'myscript3.js'
];

$.getMultiScripts(script_arr, '/mypath/').done(function() {
    // all scripts loaded
});

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

$.getMultiScripts(script_arr).done(function() { ...

Аргументы, ошибки и т. Д.

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

$.getMultiScripts(script_arr).done(function(response1, response2, response3) { ...

где каждый массив будет содержать что-то вроде [content_of_file_loaded, status, xhr_object]. Обычно нам не нужно обращаться к этим аргументам, поскольку скрипты в любом случае будут загружаться автоматически, и большую часть времени doneобратный вызов - это все, что нам действительно нужно, чтобы узнать, что все скрипты были загружены, я просто добавляю его для полноты , и в редких случаях, когда требуется доступ к фактическому тексту из загруженного файла, или когда требуется доступ к каждому объекту XHR или чему-то подобному.

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

$.getMultiScripts(script_arr).done(function() {
     // all done
}).fail(function(error) {
     // one or more scripts failed to load
}).always(function() {
     // always called, both on success and error
});

11
Спасибо за отличный ответ. Не могли бы вы объяснить мне, почему добавили $.Deferred(function( deferred ){$( deferred.resolve );})сюда?
сожен 03

11
Отложенный объект на самом деле не имеет ничего общего с обещанием, которое позволяет загружать несколько сценариев и выполнять функцию, когда все они будут выполнены. Он просто проверяет, готов ли $ (), и решает, готов ли он, другими словами, он проверяет, готова ли DOM перед выполнением любого кода, так же, как это сделал бы document.ready, и я нашел хороший пример присоединения обещает получить в сети getScript, в коде которого есть функция отложенной DOM-готовности, и просто решил оставить ее, поскольку это звучало как хорошая идея.
adeneo

4
Это потому, что кеширование в ajax по умолчанию отключено в jQuery, чтобы включить его и удалить строку запроса do: $.ajaxSetup({ cache: true });но это также может повлиять на другие вызовы ajax, которые вы не хотите кэшировать, в документации для getScript , и есть даже небольшое руководство по созданию кэшированной функции getScript, называемой cachedScript.
adeneo 03

3
Приносим извинения за позднее принятие. Иногда ваш способ все еще не совсем работает. Не уверен, в чем причина. Я пытаюсь включить сразу четыре файла сценария. $.when($.getScript(scriptSrc_1), $.getScript(scriptSrc_2), $.getScript(scriptSrc_3), $.getScript(scriptSrc_4), $.Deferred(function(deferred) { $(deferred.resolve); })).done(function() { ... })
сожень 07

1
Для тех, кто не может заставить это работать, убедитесь, что в ваших файлах JS нет ошибок синтаксического анализа, т.е. сначала проверьте, правильно ли они загружаются в теге <script>. jQuery.getScript () будет считать эти ошибки неудачной загрузкой, и вместо .done () будет вызываться .fail (). @SongtaoZ.
мощность лунной призмы

29

Я реализовал простую функцию для параллельной загрузки нескольких скриптов:

функция

function getScripts(scripts, callback) {
    var progress = 0;
    scripts.forEach(function(script) { 
        $.getScript(script, function () {
            if (++progress == scripts.length) callback();
        }); 
    });
}

использование

getScripts(["script1.js", "script2.js"], function () {
    // do something...
});

1
По какой-то причине этот сработал лучше, чем у Эмануэля. Это тоже очень прозрачная реализация.
Тодд

@satnam спасибо :)! верхний ответ не работает для некоторых людей (включая меня), поэтому я опубликовал это решение. И это выглядит намного проще.
Андрей

10

Загрузите следующий необходимый скрипт в обратном вызове предыдущего, например:

$.getScript('scripta.js', function()
{
   $.getScript('scriptb.js', function()
   {
       // run script that depends on scripta.js and scriptb.js
   });
});

1
Неужто этот метод будет скачивать скрипты последовательно, замедляя время до финального исполнения?
Jimbo

1
Правильно, @Jimbo - если честно; это предпочтительнее преждевременного исполнения. :)
Alastair

Практически всегда следует избегать водопадов. getScript поставляется с обещанием ... Вы можете использовать $ .when, чтобы слушать, когда обещания разрешаются.
Рой,

@Roi и кто-либо еще: если мне нужно, чтобы скрипты загружались в КОНКРЕТНОМ ПОРЯДКЕ, что, по сути, не так в этом подходе? Итак, какие преимущества дает альтернатива обещаниям?
Ifedi Okonkwo

@IfediOkonkwo это действительно работает, когда они должны быть последовательно, но исходный оператор только что упомянул, что он хотел бы сразу несколько (в этом случае это будет анти-шаблон). Даже тогда, если бы я хотел, чтобы они были последовательно, я бы написал функцию цикла, в которую я мог бы передать массив. Глубокое вложение быстро становится беспорядочным.
Roi

9

Иногда необходимо загружать скрипты в определенном порядке. Например, jQuery должен быть загружен до пользовательского интерфейса jQuery. Большинство примеров на этой странице загружают скрипты параллельно (асинхронно), что означает, что порядок выполнения не гарантируется. Без упорядочивания сценарий, yкоторый зависит от, xможет сломаться, если оба успешно загружены, но в неправильном порядке.

Я предлагаю гибридный подход, который позволяет последовательную загрузку зависимых скриптов + необязательную параллельную загрузку + отложенные объекты :

/*
 * loads scripts one-by-one using recursion
 * returns jQuery.Deferred
 */
function loadScripts(scripts) {
  var deferred = jQuery.Deferred();

  function loadScript(i) {
    if (i < scripts.length) {
      jQuery.ajax({
        url: scripts[i],
        dataType: "script",
        cache: true,
        success: function() {
          loadScript(i + 1);
        }
      });
    } else {
      deferred.resolve();
    }
  }
  loadScript(0);

  return deferred;
}

/*
 * example using serial and parallel download together
 */

// queue #1 - jquery ui and jquery ui i18n files
var d1 = loadScripts([
  "https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min.js",
  "https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/i18n/jquery-ui-i18n.min.js"
]).done(function() {
  jQuery("#datepicker1").datepicker(jQuery.datepicker.regional.fr);
});

// queue #2 - jquery cycle2 plugin and tile effect plugin
var d2 = loadScripts([
  "https://cdn.rawgit.com/malsup/cycle2/2.1.6/build/jquery.cycle2.min.js",
  "https://cdn.rawgit.com/malsup/cycle2/2.1.6/build/plugin/jquery.cycle2.tile.min.js"

]).done(function() {
  jQuery("#slideshow1").cycle({
    fx: "tileBlind",
    log: false
  });
});

// trigger a callback when all queues are complete
jQuery.when(d1, d2).done(function() {
  console.log("All scripts loaded");
});
@import url("https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/blitzer/jquery-ui.min.css");

#slideshow1 {
  position: relative;
  z-index: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<p><input id="datepicker1"></p>

<div id="slideshow1">
  <img src="https://dummyimage.com/300x100/FC0/000">
  <img src="https://dummyimage.com/300x100/0CF/000">
  <img src="https://dummyimage.com/300x100/CF0/000">
</div>

Скрипты в обеих очередях будут загружаться параллельно, однако скрипты в каждой очереди будут загружаться последовательно, обеспечивая упорядоченное выполнение. График водопада:

водопадная диаграмма скриптов


Разве нам не нужно добавлять тег скрипта в HTML после загрузки скрипта в память?
Ankush Jain

4

Используйте yepnope.js или Modernizr (который включает yepnope.js какModernizr.load ).

ОБНОВИТЬ

Чтобы продолжить, вот хороший эквивалент того, что вы используете в настоящее время с помощью yepnope, показывая зависимости от нескольких скриптов:

yepnope({
  load: ['script1.js', 'script2.js', 'script3.js'],
  complete: function () {
      // all the scripts have loaded, do whatever you want here
  }
});

Спасибо. Могу ли я использовать несколько yepnope.injectJs( scriptSource )в начале моего файла javascript, как включение <script>тегов в файл html?
сожень 03

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

+1; Стоит отметить, что это правильно управляет зависимостями (т.е. не загружает один и тот же скрипт дважды) в отличие от $.getScript. Это большое дело.
ov

yepnope.js устарел .
Стефан

4

Я столкнулся с рядом проблем с загрузкой нескольких сценариев, в том числе с одной (по крайней мере, в Chrome) горячей загрузкой сценариев в одном и том же домене, которые фактически не выполнялись после успешной загрузки с помощью Ajax, тогда как Cross Domain работает отлично! :(

Выбранный ответ на исходный вопрос не работает надежно.

После многих итераций вот мой окончательный ответ на getScript (s) и асинхронная загрузка нескольких сценариев в определенном строгом порядке с опцией обратного вызова для каждого загружаемого сценария и общим обратным вызовом по завершении, протестировано в jQuery 2.1+ и современных версиях Chrome, Firefox и заброшенный Internet Explorer.

Мой тестовый пример загружал файлы для рендеринга THREE.JS webGL, а затем запускал скрипт рендеринга, когда THREE global стало доступным с использованием интервальной проверки, переданной анонимному вызову функции в onComplete.

Функция прототипа (getScripts)

function getScripts( scripts, onScript, onComplete )
{
    this.async = true;
    this.cache = false;
    this.data = null;
    this.complete = function () { $.scriptHandler.loaded(); };
    this.scripts = scripts;
    this.onScript = onScript;
    this.onComplete = onComplete;
    this.total = scripts.length;
    this.progress = 0;
};

getScripts.prototype.fetch = function() {
    $.scriptHandler = this;
    var src = this.scripts[ this.progress ];
    console.log('%cFetching %s','color:#ffbc2e;', src);

    $.ajax({
        crossDomain:true,
        async:this.async,
        cache:this.cache,
        type:'GET',
        url: src,
        data:this.data,
        statusCode: {
            200: this.complete
        },
        dataType:'script'
    });
};

getScripts.prototype.loaded = function () {
    this.progress++;
    if( this.progress >= this.total ) {
        if(this.onComplete) this.onComplete();
    } else {
        this.fetch();
    };
    if(this.onScript) this.onScript();
};

Как пользоваться

var scripts = new getScripts(
    ['script1.js','script2.js','script.js'],
    function() {
        /* Optional - Executed each time a script has loaded (Use for Progress updates?) */
    },
    function () {
        /* Optional - Executed when the entire list of scripts has been loaded */
    }
);
scripts.fetch();

Эта функция такая же, как и в случае использования Deferred (Deprecated now?), When, Success & Complete в моих испытаниях, чтобы НЕ быть на 100% надежными!?, Отсюда эта функция и использование statusCode, например.

Вы можете захотеть добавить поведение обработки ошибок / сбоев, если хотите.


+1 за их загрузку в правильном порядке, что часто необходимо, и что-то принятый ответ не гарантирует, если я чего-то не упускаю? Я опубликовал аналогичный более короткий ответ ниже, но это более общий случай.
Whelkaholism

2

Вы можете использовать $.when-метод, попробовав следующую функцию:

function loadScripts(scripts) {
  scripts.forEach(function (item, i) {
    item = $.getScript(item);
  });
  return $.when.apply($, scripts);
}

Эта функция будет использоваться так:

loadScripts(['path/to/script-a.js', 'path/to/script-b.js']).done(function (respA, respB) {
    // both scripts are loaded; do something funny
});

Это способ использовать обещания и минимизировать накладные расходы.


Обеспечивает ли это последовательную загрузку скриптов?
CyberMonk

2

Отличный ответ, аденео.

Мне потребовалось некоторое время, чтобы понять, как сделать ваш ответ более общим (чтобы я мог загрузить массив сценариев, определенных кодом). Обратный вызов вызывается, когда все скрипты загружены и выполнены. Вот мое решение:

    function loadMultipleScripts(scripts, callback){
        var array = [];

        scripts.forEach(function(script){
            array.push($.getScript( script ))
        });

        array.push($.Deferred(function( deferred ){
                    $( deferred.resolve );
                }));

        $.when.apply($, array).done(function(){
                if (callback){
                    callback();
                }
            });
    }

2

Добавить скрипты с async = false

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

  • Загружает их асинхронно, потому что именно так браузеры оптимизируют загрузку страницы.
  • Выполняет скрипты по порядку, потому что именно так браузеры анализируют HTML-теги.
  • Нет необходимости в обратном вызове, потому что скрипты выполняются по порядку. Просто добавьте еще один скрипт, и он будет выполнен после других скриптов.

Подробнее здесь: https://www.html5rocks.com/en/tutorials/speed/script-loading/

var scriptsToLoad = [
   "script1.js", 
   "script2.js",
   "script3.js",
]; 
    
scriptsToLoad.forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.body.appendChild(script);
});

1

Вы ищете загрузчик, совместимый с AMD (например, require.js).

http://requirejs.org/

http://requirejs.org/docs/whyamd.html

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


1

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

function loadFiles(files, fn) {
    if (!files.length) {
        files = [];
    }
    var head = document.head || document.getElementsByTagName('head')[0];

    function loadFile(index) {
        if (files.length > index) {
            var fileref = document.createElement('script');
            fileref.setAttribute("type", "text/javascript");
            fileref.setAttribute("src", files[index]);
            head.appendChild(fileref);
            index = index + 1;
            // Used to call a callback function
            fileref.onload = function () {
                loadFile(index);
            }
        } else if(fn){
            fn();
        }
    }
    loadFile(0);
}

Возможно, лучше, чем принятый ответ, поскольку он загружает файлы по порядку. Если script2 требует загрузки script1, порядок важен.
Salman A

1

Это работает для меня:

function getScripts(scripts) {
    var prArr = [];
    scripts.forEach(function(script) { 
        (function(script){
            prArr .push(new Promise(function(resolve){
                $.getScript(script, function () {
                    resolve();
                });
            }));
        })(script);
    });
    return Promise.all(prArr, function(){
        return true;
    });
}

И используйте это:

var jsarr = ['script1.js','script2.js'];
getScripts(jsarr).then(function(){
...
});

1

Вот ответ с использованием Maciej Sawicki и его реализация Promiseкак обратный вызов:

function loadScripts(urls, path) {
    return new Promise(function(resolve) {
        urls.forEach(function(src, i) {

            let script = document.createElement('script');        
            script.type = 'text/javascript';
            script.src = (path || "") + src;
            script.async = false;

            // If last script, bind the callback event to resolve
            if(i == urls.length-1) {                    
                // Multiple binding for browser compatibility
                script.onreadystatechange = resolve;
                script.onload = resolve;
            }

            // Fire the loading
            document.body.appendChild(script);
        });
    });
}

Использование:

let JSDependencies = ["jquery.js",
                      "LibraryNeedingJquery.js",
                      "ParametersNeedingLibrary.js"];

loadScripts(JSDependencies,'JavaScript/').then(taskNeedingParameters);

Все файлы Javascript загружаются при первой возможности и выполняются в указанном порядке. Потом taskNeedingParametersназывается.


0

Более короткая версия исчерпывающего ответа Эндрю Марка Ньютона выше. Он не проверяет код состояния на успешность, что вы должны делать, чтобы избежать неопределенного поведения пользовательского интерфейса.

Этот был для надоедливой системы, в которой я мог гарантировать jQuery, но никакие другие не включать, поэтому мне нужен был достаточно короткий метод, чтобы его нельзя было поместить во внешний скрипт в случае принудительного использования. (Вы могли бы сделать его еще короче, передав индекс 0 первому «рекурсивному» вызову, но сила привычек стиля заставила меня добавить сахар).

Я также назначаю список зависимостей имени модуля, поэтому этот блок может быть включен в любое место, где вам нужен "module1", а скрипты и зависимая инициализация будут включены / запущены только один раз (вы можете войти indexв обратный вызов и увидеть один упорядоченный набор запущенных запросов AJAX)

if(typeof(__loaders) == 'undefined') __loaders = {};

if(typeof(__loaders.module1) == 'undefined')
{
    __loaders.module1 = false;

    var dependencies = [];

    dependencies.push('/scripts/loadmefirst.js');
    dependencies.push('/scripts/loadmenext.js');
    dependencies.push('/scripts/loadmelast.js');

    var getScriptChain  = function(chain, index)        
    {
        if(typeof(index) == 'undefined')
            index = 0;

        $.getScript(chain[index], 
            function()
            {
                if(index == chain.length - 1)
                {
                    __loaders.module1 = true;

                    /* !!!
                        Do your initialization of dependent stuff here 
                    !!! */
                }
                else 
                    getScriptChain(chain, index + 1);
            }
        );
    };

    getScriptChain(dependencies);       
}

0

Есть плагин, расширяющий метод jQuery getScript. Обеспечивает асинхронную и синхронную загрузку и использует механизм кэширования jQuery. Полное раскрытие, я это написал. Пожалуйста, не стесняйтесь вносить свой вклад, если найдете лучший метод.

https://github.com/hudsonfoo/jquery-getscripts


0

Загружает n скриптов один за другим (полезно, если, например, для 2-го файла нужен 1-й):

(function self(a,cb,i){
    i = i || 0; 
    cb = cb || function(){};    
    if(i==a.length)return cb();
    $.getScript(a[i++],self.bind(0,a,cb,i));                    
})(['list','of','script','urls'],function(){console.log('done')});

0

на основе ответа @adeneo выше: объединение загрузки файлов css и js

есть предложения по улучшению ??

// Usage
//$.getMultiResources(['script-1.js','style-1.css'], 'assets/somePath/')
//  .done(function () {})
//  .fail(function (error) {})
//  .always(function () {});

(function ($) {
  $.getMultiResources = function (arr, pathOptional, cache) {
    cache = (typeof cache === 'undefined') ? true : cache;
    var _arr = $.map(arr, function (src) {
      var srcpath = (pathOptional || '') + src;
      if (/.css$/i.test(srcpath)) {
        return $.ajax({
          type: 'GET',
          url: srcpath,
          dataType: 'text',
          cache: cache,
          success: function () {
            $('<link>', {
              rel: 'stylesheet',
              type: 'text/css',
              'href': srcpath
            }).appendTo('head');
          }
        });

      } else {
        return $.ajax({
          type: 'GET',
          url: srcpath,
          dataType: 'script',
          cache: cache
        });
      }
    });
    //
    _arr.push($.Deferred(function (deferred) {
      $(deferred.resolve);
    }));
    //
    return $.when.apply($, _arr);
  };
})(jQuery);

0

Вы можете попробовать это с помощью рекурсии. Это будет загружать их синхронно, один за другим, пока не будет завершена загрузка всего списка.

var queue = ['url/links/go/here'];

ProcessScripts(function() { // All done do what ever you want

}, 0);

function ProcessScripts(cb, index) {
    getScript(queue[index], function() {
        index++;
        if (index === queue.length) { // Reached the end
            cb();
        } else {
            return ProcessScripts(cb, index);
        }
    });
}

function getScript(script, callback) {
    $.getScript(script, function() {
        callback();
    });
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.