Динамически загружать файл JavaScript - PullRequest
151 голосов
/ 22 августа 2008

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

Клиент, использующий компонент, не обязан загружать все файлы сценариев библиотеки (и вручную вставлять теги <script> на их веб-страницу), которые реализуют этот компонент - просто файл сценария основного компонента.

Как основные библиотеки JavaScript достигают этого (Prototype, jQuery и т. Д.)? Объединяют ли эти инструменты несколько файлов JavaScript в одну распространяемую версию файла сценария для сборки? Или они динамически загружают вспомогательные «библиотечные» скрипты?

Дополнение к этому вопросу: есть ли способ обработки события после загрузки динамически включенного файла JavaScript? Прототип имеет document.observe для событий всего документа. Пример:

document.observe("dom:loaded", function() {
  // initially hide all containers for tab content
  $$('div.tabcontent').invoke('hide');
});

Какие события доступны для элемента скрипта?

Ответы [ 24 ]

73 голосов
/ 28 октября 2008

Вы можете написать динамические теги сценария (используя Prototype ):

new Element("script", {src: "myBigCodeLibrary.js", type: "text/javascript"});

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

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

if (iNeedSomeMore) {
    Script.load("myBigCodeLibrary.js"); // includes code for myFancyMethod();
    myFancyMethod(); // cool, no need for callbacks!
}

Существует умный способ внедрить зависимости скрипта без необходимости обратных вызовов. Вам просто нужно вытащить скрипт с помощью синхронного AJAX-запроса и проверить скрипт на глобальном уровне.

Если вы используете Prototype, метод Script.load выглядит следующим образом:

var Script = {
    _loadedScripts: [],
    include: function(script) {
        // include script only once
        if (this._loadedScripts.include(script)) {
            return false;
        }
        // request file synchronous
        var code = new Ajax.Request(script, {
            asynchronous: false,
            method: "GET",
            evalJS: false,
            evalJSON: false
        }).transport.responseText;
        // eval code on global level
        if (Prototype.Browser.IE) {
            window.execScript(code);
        } else if (Prototype.Browser.WebKit) {
            $$("head").first().insert(Object.extend(
                new Element("script", {
                    type: "text/javascript"
                }), {
                    text: code
                }
            ));
        } else {
            window.eval(code);
        }
        // remember included script
        this._loadedScripts.push(script);
    }
};
54 голосов
/ 09 февраля 2013

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

1 - Вы можете загрузить его с помощью вызова AJAX, а затем использовать eval.

Это самый простой способ, но он ограничен вашим доменом из-за настроек безопасности Javascript, а использование eval открывает путь к ошибкам и взломам.

2 - Добавить тег сценария с URL-адресом сценария в HTML.

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

Оба эти решения обсуждаются и иллюстрируются здесь.

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

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

E.G: my_lovely_script.js содержит MySuperObject

var js = document.createElement("script");

js.type = "text/javascript";
js.src = jsFilePath;

document.body.appendChild(js);

var s = new MySuperObject();

Error : MySuperObject is undefined

Затем вы перезагрузите страницу, нажав F5. И это работает! Смешение ...

Так что с этим делать?

Ну, вы можете использовать взлом, который предлагает автор, по ссылке, которую я вам дал. Таким образом, для спешащих людей он использует событие en для запуска функции обратного вызова при загрузке скрипта. Таким образом, вы можете поместить весь код с помощью удаленной библиотеки в функцию обратного вызова. E.G:

function loadScript(url, callback)
{
    // adding the script tag to the head as suggested before
   var head = document.getElementsByTagName('head')[0];
   var script = document.createElement('script');
   script.type = 'text/javascript';
   script.src = url;

   // then bind the event to the callback function 
   // there are several events for cross browser compatibility
   script.onreadystatechange = callback;
   script.onload = callback;

   // fire the loading
   head.appendChild(script);
}

Затем вы пишете код, который хотите использовать ПОСЛЕ того, как скрипт загружается в лямбда-функцию:

var myPrettyCode = function() {
    // here, do what ever you want
};

Затем вы запускаете все это:

loadScript("my_lovely_script.js", myPrettyCode);

Хорошо, я понял. Но писать все эти вещи - боль.

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

$.getScript("my_lovely_script.js", function() {
    alert("Script loaded and executed.");
    // here you can use anything you defined in the loaded script
});
25 голосов
/ 23 августа 2008

Я недавно использовал гораздо менее сложную версию с jQuery :

<script src="scripts/jquery.js"></script>
<script>
  var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"];
  var $head = $("head");
  for (var i = 0; i < js.length; i++) {
    $head.append("<script src=\"" + js[i] + "\"></scr" + "ipt>");
  }
</script>

Он прекрасно работал в каждом браузере, в котором я его тестировал: IE6 / 7, Firefox, Safari, Opera.

Обновление: jQuery-версия:

<script>
  var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"];
  for (var i = 0, l = js.length; i < l; i++) {
    document.getElementsByTagName("head")[0].innerHTML += ("<script src=\"" + js[i] + "\"></scr" + "ipt>");
  }
</script>
19 голосов
/ 22 августа 2008

Я сделал в основном то же самое, что вы сделали Адам, но с небольшой модификацией, чтобы убедиться, что я добавляю тег head, чтобы выполнить работу. Я просто создал функцию включения (код ниже) для работы с файлами сценариев и CSS.

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

function include( url, type ){
    // First make sure it hasn't been loaded by something else.
    if( Array.contains( includedFile, url ) )
        return;

    // Determine the MIME-type
    var jsExpr = new RegExp( "js$", "i" );
    var cssExpr = new RegExp( "css$", "i" );
    if( type == null )
        if( jsExpr.test( url ) )
            type = 'text/javascript';
        else if( cssExpr.test( url ) )
            type = 'text/css';

    // Create the appropriate element.
    var tag = null;
    switch( type ){
        case 'text/javascript' :
            tag = document.createElement( 'script' );
            tag.type = type;
            tag.src = url;
            break;
        case 'text/css' :
            tag = document.createElement( 'link' );
            tag.rel = 'stylesheet';
            tag.type = type;
            tag.href = url;
            break;
    }

    // Insert it to the <head> and the array to ensure it is not
    // loaded again.
    document.getElementsByTagName("head")[0].appendChild( tag );
    Array.add( includedFile, url );
}
14 голосов
/ 04 апреля 2012

еще один потрясающий ответ

$.getScript("my_lovely_script.js", function(){


   alert("Script loaded and executed.");
  // here you can use anything you defined in the loaded script

 });

https://stackoverflow.com/a/950146/671046

8 голосов
/ 22 августа 2008

Вот пример кода, который я нашел ... У кого-нибудь есть лучший способ?

  function include(url)
  {
    var s = document.createElement("script");
    s.setAttribute("type", "text/javascript");
    s.setAttribute("src", url);
    var nodes = document.getElementsByTagName("*");
    var node = nodes[nodes.length -1].parentNode;
    node.appendChild(s);
  }
6 голосов
/ 14 февраля 2012

Если у вас уже загружен jQuery, вы должны использовать $. GetScript .

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

3 голосов
/ 26 марта 2009

Только что узнал о замечательной функции в YUI 3 (на момент написания статьи доступно в предварительном выпуске) Вы можете легко вставлять зависимости в библиотеки YUI и во «внешние» модули (то, что вы ищете) без слишком большого количества кода:

Он также отвечает на ваш второй вопрос о вызываемой функции, как только внешний модуль загружен.

Пример:

YUI({
    modules: {
        'simple': {
            fullpath: "http://example.com/public/js/simple.js"
        },
        'complicated': {
            fullpath: "http://example.com/public/js/complicated.js"
            requires: ['simple']  // <-- dependency to 'simple' module
        }
    },
    timeout: 10000
}).use('complicated', function(Y, result) {
    // called as soon as 'complicated' is loaded
    if (!result.success) {
        // loading failed, or timeout
        handleError(result.msg);
    } else {
        // call a function that needs 'complicated'
        doSomethingComplicated(...);
    }
});

Отлично сработало для меня и имеет преимущество в управлении зависимостями. Обратитесь к документации YUI за примером с календарем YUI 2 .

3 голосов
/ 06 июня 2013

Если вы хотите загрузить скрипт SYNC , вам нужно добавить текст скрипта непосредственно в тег HTML HEAD. Добавление его как приведет к загрузке ASYNC . Чтобы синхронно загрузить текст скрипта из внешнего файла, используйте XHR. Ниже приведен краткий пример (он использует части других ответов в этом и других постах):

/*sample requires an additional method for array prototype:*/

if (Array.prototype.contains === undefined) {
Array.prototype.contains = function (obj) {
    var i = this.length;
    while (i--) { if (this[i] === obj) return true; }
    return false;
};
};

/*define object that will wrap our logic*/
var ScriptLoader = {
LoadedFiles: [],

LoadFile: function (url) {
    var self = this;
    if (this.LoadedFiles.contains(url)) return;

    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                self.LoadedFiles.push(url);
                self.AddScript(xhr.responseText);
            } else {
                if (console) console.error(xhr.statusText);
            }
        }
    };
    xhr.open("GET", url, false);/*last parameter defines if call is async or not*/
    xhr.send(null);
},

AddScript: function (code) {
    var oNew = document.createElement("script");
    oNew.type = "text/javascript";
    oNew.textContent = code;
    document.getElementsByTagName("head")[0].appendChild(oNew);
}
};

/*Load script file. ScriptLoader will check if you try to load a file that has already been loaded (this check might be better, but I'm lazy).*/

ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js");
ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js");
/*this will be executed right after upper lines. It requires jquery to execute. It requires a HTML input with id "tb1"*/
$(function () { alert($('#tb1').val()); });
3 голосов
/ 26 августа 2008

Я использовал еще одно решение, которое я нашел в сети ... Это под CreativeCommons и проверяет, был ли включен источник до вызова функции ...

Вы можете найти файл здесь: include.js

/** include - including .js files from JS - bfults@gmail.com - 2005-02-09
 ** Code licensed under Creative Commons Attribution-ShareAlike License 
 ** http://creativecommons.org/licenses/by-sa/2.0/
 **/              
var hIncludes = null;
function include(sURI)
{   
  if (document.getElementsByTagName)
  {   
    if (!hIncludes)
    {
      hIncludes = {}; 
      var cScripts = document.getElementsByTagName("script");
      for (var i=0,len=cScripts.length; i < len; i++)
        if (cScripts[i].src) hIncludes[cScripts[i].src] = true;
    }
    if (!hIncludes[sURI])
    {
      var oNew = document.createElement("script");
      oNew.type = "text/javascript";
      oNew.src = sURI;
      hIncludes[sURI]=true;
      document.getElementsByTagName("head")[0].appendChild(oNew);
    }
  }   
} 
...