Як включити кілька 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

У наведеному вище коді додавання відкладеного і вирішення його всередині $()- це як розміщення будь-якої іншої функції всередині дзвінка 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 );})сюди?
sozhen

11
Відкладений об’єкт насправді не має нічого спільного з обіцянкою, яка дозволяє завантажувати кілька сценаріїв і виконувати функцію, коли вони все виконано. Він перевіряє, чи готовий $ (), і вирішує, чи він є, іншими словами, він перевіряє, чи DOM готовий до виконання будь-якого коду, як і документ. Вже, і я знайшов хороший приклад прикріплення обіцяє дістати JavaScript в Інтернеті, який мав відкладений функціонал DOM в коді, і просто вирішив зберегти його, як це звучало як гарна ідея.
аденео

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

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

1
Для тих, хто не може це зробити, переконайтеся, що у ваших файлах JS немає помилок розбору, тобто перевіряйте, чи спочатку вони належним чином завантажуються у тег <script>. jQuery.getScript () вважатиме ці помилки помилковим завантаженням, а .fail () буде викликано замість .done (). @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
Невже цей метод завантажить сценарії послідовно, сповільнюючи час до остаточного виконання?
Джимбо

1
Правильно, @Jimbo - бути справедливим; бажано передчасне виконання. :)
Аластер

Водоспади майже завжди слід уникати. getScript поставляється з обіцянкою ... Ви можете використовувати $ .when слухати, коли обіцянки вирішуються.
Рой

@ Рой та хто-небудь інший: Якщо мені потрібні сценарії для завантаження в СПЕЦИФІЧНИЙ ЗАМОВЛЕННЯ, що, в принципі, не так у цьому підході? І яку перевагу, таким чином, приносить альтернатива обіцянки?
Ifedi Okonkwo

@IfediOkonkwo це спрацьовує тоді, коли вони повинні бути послідовно, але оригінал лише зазначив, що він хотів би кратних одразу (у яких це було б анти-закономірністю). Навіть тоді, якби я хотів їх послідовно, я би написав циклічну функцію, до якої можу передати масив. Глибоке гніздування швидко стає безладним.
Рой

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-файл?
sozhen

Це нове, і якщо чесно, я не розумію, для чого це потрібно. Дотримуйтесь документації трохи нижче списку останніх змін, і ви побачите більш звичайне використання.
Кріс Пратт

+1; Варто зазначити, що це правильно управляє залежностями (тобто не завантажуватиме один і той же сценарій двічі) на відміну від $.getScript. Це велика справа.
ов

yepnope.js тепер застарілий .
Стефан

4

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

Вибрана відповідь на оригінальне запитання не працює надійно.

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

У моєму тестовому випадку завантажувались файли для візуалізації веб-сторінки THREE.JS, а потім запускався сценарій візуалізації, коли ТРИ глобальний став доступний за допомогою інтервальної перевірки, переданої анонімному виклику функції 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();

Функція така, як це було для мене, коли я виявив, що в моїх випробуваннях використовуються відкладені (застарілі зараз?), Коли, «Успіх і завершення», щоб НЕ бути 100% надійними !?

Якщо ви хочете, ви можете додати помилку / помилку в поведінці.


+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 (наприклад, Requ.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, то замовлення є важливим.
Салман

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

Ось відповідь, використовуючи метод Мацея Савікі та реалізацію 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

Там є плагін, який розширює метод getScript jQuery. Дозволяє для асинхронного та синхронного завантаження та використовує кеш-механізм jQuery. Повне розкриття, я це написав. Будь ласка, не соромтеся робити внесок, якщо ви знайдете кращий метод.

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


0

Завантажує n сценаріїв по одному (корисно, якщо, наприклад, другому файлу потрібен перший):

(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.