Comment inclure plusieurs fichiers js à l'aide de la méthode jQuery $ .getScript ()

127

J'essaye d'inclure dynamiquement des fichiers javascript dans mon fichier js. J'ai fait des recherches à ce sujet et j'ai trouvé que la méthode jQuery $ .getScript () serait une solution souhaitée.

// 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? 
});

Mais je me demande si cette méthode peut charger plusieurs scripts à la fois? Pourquoi je demande cela, c'est parce que parfois mon fichier javascript dépend de plusieurs fichiers js.

Merci d'avance.

sozhen
la source

Réponses:

315

La réponse est

Vous pouvez utiliser des promesses avec getScript()et attendre que tous les scripts soient chargés, quelque chose comme:

$.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
    
});

VIOLON

UN AUTRE VIOLON

Dans le code ci-dessus, ajouter un Deferred et le résoudre à l'intérieur $()revient à placer n'importe quelle autre fonction dans un appel jQuery, comme $(func), c'est la même chose que

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

c'est-à-dire qu'il attend que le DOM soit prêt, donc dans l'exemple ci-dessus $.whenattend que tous les scripts soient chargés et que le DOM soit prêt à cause de l' $.Deferredappel qui se résout dans le rappel DOM ready.


Pour une utilisation plus générique, une fonction pratique

Une fonction utilitaire qui accepte n'importe quel tableau de scripts pourrait être créée comme ceci:

$.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);
}

qui peut être utilisé comme ça

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

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

où le chemin sera ajouté au début de tous les scripts, et est également facultatif, ce qui signifie que si le tableau contient l'URL complète, on peut également le faire, et omettre le chemin tous ensemble

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

Arguments, erreurs, etc.

En donepassant , notez que le callback contiendra un certain nombre d'arguments correspondant aux scripts passés, chaque argument représentant un tableau contenant la réponse

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

où chaque tableau contiendra quelque chose comme [content_of_file_loaded, status, xhr_object]. Nous n'avons généralement pas besoin d'accéder à ces arguments car les scripts seront de toute façon chargés automatiquement, et la plupart du tempsdone rappel est tout ce que nous recherchons vraiment pour savoir que tous les scripts ont été chargés, je l'ajoute juste pour être complet , et pour les rares occasions où le texte réel du fichier chargé doit être accédé, ou lorsque l'on a besoin d'accéder à chaque objet XHR ou quelque chose de similaire.

De plus, si l'un des scripts ne parvient pas à se charger, le gestionnaire d'échec sera appelé et les scripts suivants ne seront pas chargés

$.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
});
adeneo
la source
11
Merci pour une excellente réponse. Pourriez-vous m'expliquer pourquoi ajouter $.Deferred(function( deferred ){$( deferred.resolve );})ici?
sozhen
11
L'objet différé n'a vraiment rien à voir avec la promesse qui vous permet de charger plusieurs scripts et d'exécuter une fonction lorsqu'ils sont tous terminés. Il suffit de vérifier si $ () est prêt, et de résoudre si c'est le cas, en d'autres termes, il vérifie que le DOM est prêt avant d'exécuter un code, un peu comme le ferait document.ready, et j'ai trouvé un bon exemple d'attachement promet de getScript en ligne qui avait la fonctionnalité prête DOM différée dans le code, et a juste décidé de le garder car cela semblait être une bonne idée.
adeneo
4
C'est parce que la mise en cache dans ajax est désactivée par défaut dans jQuery, pour l'activer et supprimer la chaîne de requête: $.ajaxSetup({ cache: true });mais cela pourrait également affecter d'autres appels ajax que vous ne voulez pas mettre en cache, il y a beaucoup plus à ce sujet dans la documentation pour getScript , et il existe même un petit guide sur la création d'une fonction getScript mise en cache appelée cachedScript.
adeneo
3
Désolé pour l'acceptation tardive. Parfois, votre chemin ne fonctionne toujours pas tout à fait. Je ne sais pas quelle est la raison. J'essaye d'incorporer quatre fichiers de script à la fois. $.when($.getScript(scriptSrc_1), $.getScript(scriptSrc_2), $.getScript(scriptSrc_3), $.getScript(scriptSrc_4), $.Deferred(function(deferred) { $(deferred.resolve); })).done(function() { ... })
sozhen
1
Pour tous ceux qui ne peuvent pas faire fonctionner cela, assurez-vous qu'il n'y a pas d'erreurs d'analyse dans vos fichiers JS, c'est-à-dire vérifiez qu'ils se chargent correctement dans une balise <script> en premier. jQuery.getScript () considérera ces erreurs comme un échec de chargement et .fail () sera appelé à la place de .done (). @SongtaoZ.
puissance du prisme lunaire
29

J'ai implémenté une fonction simple pour charger plusieurs scripts en parallèle:

Fonction

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

Usage

getScripts(["script1.js", "script2.js"], function () {
    // do something...
});
Andrei
la source
1
Pour une raison quelconque, celui-ci fonctionnait mieux que celui d'Emanuel. C'est aussi une mise en œuvre très transparente.
Todd
@satnam merci :)! top answer ne fonctionne pas pour certaines personnes (y compris moi), j'ai donc publié cette solution. Et ça a l'air beaucoup plus simple.
Andrei
10

Chargez le script nécessaire suivant dans le rappel du précédent comme:

$.getScript('scripta.js', function()
{
   $.getScript('scriptb.js', function()
   {
       // run script that depends on scripta.js and scriptb.js
   });
});
Frankey
la source
1
Cette méthode va sûrement télécharger les scripts séquentiellement, ralentissant le temps jusqu'à l'exécution finale?
Jimbo
1
Correct, @Jimbo - pour être juste; c'est préférable à une exécution prématurée. :)
Alastair
Les chutes d'eau doivent presque toujours être évitées. getScript est livré avec une promesse ... Vous pouvez utiliser $ .when pour écouter lorsque les promesses se résolvent.
Roi
@Roi et n'importe qui d'autre: Si j'ai besoin que les scripts se chargent dans un ORDRE SPÉCIFIQUE, qu'est-ce qui ne va pas dans cette approche? Et quel avantage, par conséquent, la promesse alternative apporte-t-elle?
Ifedi Okonkwo
@IfediOkonkwo cela fonctionne quand ils ont besoin d'être en série, mais l'orig op vient de mentionner qu'il aimerait des multiples à la fois (dans lesquels, ce serait un anti-modèle). Même dans ce cas, si je les voulais en série, j'écrirais une fonction de bouclage à laquelle je pourrais passer un tableau. L'imbrication profonde devient rapidement désordonnée.
Roi
9

Parfois, il est nécessaire de charger les scripts dans un ordre spécifique. Par exemple, jQuery doit être chargé avant l'interface utilisateur jQuery. La plupart des exemples de cette page chargent des scripts en parallèle (de manière asynchrone), ce qui signifie que l'ordre d'exécution n'est pas garanti. Sans commande, script yqui dépend dex pourrait se casser si les deux sont correctement chargés mais dans le mauvais ordre.

Je propose une approche hybride qui permet le chargement séquentiel de scripts dépendants + chargement parallèle optionnel + objets différés :

/*
 * 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>

Les scripts des deux files d'attente seront téléchargés en parallèle, cependant, les scripts de chaque file d'attente seront téléchargés en séquence, garantissant une exécution ordonnée. Graphique en cascade:

graphique en cascade de scripts

Salman A
la source
N'avons-nous pas besoin d'ajouter la balise de script en HTML une fois que le script est chargé en mémoire?
Ankush Jain
4

Utilisez yepnope.js ou Modernizr (qui inclut yepnope.js commeModernizr.load ).

METTRE À JOUR

Juste pour faire un suivi, voici un bon équivalent de ce que vous utilisez actuellement avec yepnope, montrant les dépendances sur plusieurs scripts:

yepnope({
  load: ['script1.js', 'script2.js', 'script3.js'],
  complete: function () {
      // all the scripts have loaded, do whatever you want here
  }
});
Chris Pratt
la source
Merci. Puis-je utiliser plusieurs yepnope.injectJs( scriptSource )au début de mon fichier javascript, tout comme inclure des <script>balises dans un fichier html?
sozhen
C'est nouveau, et honnêtement, je ne comprends pas pourquoi c'est nécessaire. Suivez la documentation un peu au-delà de la liste des modifications récentes et vous verrez l'utilisation plus conventionnelle.
Chris Pratt
+1; à noter que cela gère correctement les dépendances (c'est-à-dire ne chargera pas le même script deux fois) contrairement à $.getScript. Ceci est une grosse affaire.
ov
yepnope.js est maintenant obsolète .
Stephan
4

J'ai rencontré un certain nombre de problèmes avec le chargement de plusieurs scripts inculquant un problème avec (au moins dans Chrome) le chargement à chaud du même domaine de scripts ne fonctionnant pas réellement après avoir été chargés avec succès par Ajax où Cross Domain fonctionne parfaitement bien! :(

La réponse sélectionnée à la question d'origine ne fonctionne pas de manière fiable.

Après de nombreuses itérations, voici ma réponse finale à getScript (s) et au chargement asynchrone de plusieurs scripts dans un ordre strict spécifique avec une option de rappel chargée par script et un rappel global à la fin, Testé dans jQuery 2.1+ et les versions modernes de Chrome, Firefox plus le abandonné Internet Explorer.

Mon scénario de test chargeait des fichiers pour un rendu webGL THREE.JS, puis démarrait le script de rendu lorsque THREE global est devenu disponible en utilisant une vérification d'intervalle passée à un appel de fonction anonyme à onComplete.

La fonction Prototype (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();
};

Comment utiliser

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();

La fonction est la même que celle que j'ai trouvée en utilisant Deferred (obsolète maintenant?), When, Success & Complete dans mes essais pour NE PAS être fiable à 100%!?, D'où cette fonction et l'utilisation de statusCode par exemple.

Vous pouvez ajouter un comportement de gestion des erreurs / échecs si vous le souhaitez.

Marc
la source
+1 pour les charger dans le bon ordre, souvent essentiel, et quelque chose que la réponse acceptée ne garantira pas à moins que je ne manque quelque chose? J'ai publié une réponse plus courte similaire ci-dessous, mais c'est un cas plus général.
Whelkaholism
2

Vous pouvez utiliser la $.whenméthode-en essayant la fonction suivante:

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

Cette fonction serait utilisée comme ceci:

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

C'est la façon d'utiliser Promises et d'avoir un minimum de frais généraux.

Emanuel Kluge
la source
Cela garantit-il que les scripts sont chargés en séquence?
CyberMonk
2

Excellente réponse, adeneo.

Il m'a fallu un peu de temps pour comprendre comment rendre votre réponse plus générique (afin que je puisse charger un tableau de scripts définis par le code). Le rappel est appelé lorsque tous les scripts sont chargés et exécutés. Voici ma solution:

    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();
                }
            });
    }
RoccoB
la source
2

Ajouter des scripts avec async = false

Voici une approche différente, mais super simple. Pour charger plusieurs scripts, vous pouvez simplement les ajouter au corps.

  • Les charge de manière asynchrone, car c'est ainsi que les navigateurs optimisent le chargement de la page
  • Exécute les scripts dans l'ordre, car c'est ainsi que les navigateurs analysent les balises HTML
  • Pas besoin de rappel, car les scripts sont exécutés dans l'ordre. Ajoutez simplement un autre script, et il sera exécuté après les autres scripts

Plus d'informations ici: 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);
});
Maciej Sawicki
la source
1

Ce que vous recherchez, c'est un chargeur compatible AMD (comme require.js).

http://requirejs.org/

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

Il y en a beaucoup de bons open source si vous le recherchez. Fondamentalement, cela vous permet de définir un module de code, et s'il dépend d'autres modules de code, il attendra que ces modules aient fini de télécharger avant de continuer à s'exécuter. De cette façon, vous pouvez charger 10 modules de manière asynchrone et il ne devrait y avoir aucun problème même si l'un dépend de quelques-uns des autres à exécuter.

dqhendricks
la source
1

Cette fonction s'assurera qu'un fichier est chargé une fois le fichier de dépendance complètement chargé. Il vous suffit de fournir les fichiers dans une séquence en gardant à l'esprit les dépendances sur d'autres fichiers.

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);
}
Monsieur green
la source
Peut-être mieux que la réponse acceptée car il charge les fichiers dans l'ordre. Si script2 nécessite le chargement de script1, la commande est essentielle.
Salman A
1

Cela fonctionne pour moi:

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;
    });
}

Et utilisez-le:

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

Voici la réponse en utilisant celle de Maciej Sawicki et en l'implémentant Promisecomme rappel:

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);
        });
    });
}

Utilisation:

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

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

Tous les fichiers Javascript sont téléchargés dès que possible et exécutés dans l'ordre indiqué. Puis taskNeedingParametersest appelé.

H4dr1en
la source
0

Version plus courte de la réponse complète d'Andrew Marc Newton ci-dessus. Celui-ci ne vérifie pas le code d'état pour la réussite, ce que vous devez faire pour éviter un comportement d'interface utilisateur non défini.

Celui-ci était destiné à un système ennuyeux où je pouvais garantir jQuery mais pas d'autres inclus, donc je voulais une technique suffisamment courte pour ne pas être transformée en script externe si elle y était forcée. (Vous pourriez le rendre encore plus court en passant l'indice 0 au premier appel "récursif" mais la force des habitudes de style m'a fait ajouter le sucre).

J'attribue également la liste de dépendances à un nom de module, donc ce bloc peut être inclus partout où vous avez besoin de "module1" et les scripts et l'initialisation dépendante ne seront inclus / exécutés qu'une seule fois (vous pouvez vous connecter indexau rappel et voir un seul commandé ensemble de requêtes AJAX exécutées)

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);       
}
Whelkaholism
la source
0

Il existe un plugin qui étend la méthode getScript de jQuery. Permet le chargement asynchrone et synchrone et utilise le mécanisme de mise en cache de jQuery. Divulgation complète, j'ai écrit ceci. N'hésitez pas à contribuer si vous trouvez une meilleure méthode.

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

user3455212
la source
0

Charge n scripts un par un (utile si par exemple le 2ème fichier a besoin du 1er):

(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')});

la source
0

basé sur la réponse de @adeneo ci-dessus: Combinaison du chargement de fichiers css et js

des suggestions d'améliorations ??

// 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);
Django
la source
0

Vous pouvez essayer ceci en utilisant la récursivité. Cela les téléchargera en synchronisation, l'un après l'autre jusqu'à ce qu'il termine le téléchargement de la liste entière.

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();
    });
}
Meule
la source