Загрузка JS по требованию — с учетом некоторых особенностей
На работе передо мной встала задача: реализовать некий функционал, который бы подключался к стороннему сайту одним скриптом, дабы не напрягать неискушенного вебмастера (коими и являлись бы в большинстве случаев потребителями этого продукта).
Но дело в том, что данный функционал зависел от jQuery и некоторого количества плагинов к ней (в зависимости от пожеланий пользователя). Конечно, можно было бы грузить эти скрипты в любом случае — выполняя в локальной области видимости или, наплевав на вебмастеров, в глобальной; любым способом — через динамическое создание тега script или просто запихнув все необходимое в тот самый единственный скрипт. Но — это не наш метод.
Порыскав по интернетам, нашел несколько библиотек/плагинов, которые обладали необходимым функционалом (динамическая загрузка JS, учитывая зависимости), за исключением важного — предполагалось, что тот, кто будет их использовать, является полноправным хозяином той страницы, куда их будет устанавливать, и все скрипты будут подключены ч/з этот плагин/библиотеку (т. е. проверка на то, подключен ли уже некий скрипт к странице будет выполнено лишь после того, как этот скрипт будет загружен с помощью выбранной библиотеки/плагина). Впрочем, возможно, я просто поленился в них разобраться и решил изобрести велосипед — как вариант.
Да, в этом случае можно было бы выполнить необходимые скрипты в локальной области видимости, чтобы не мешать остальному функционалу на сайте, но решено было пойти дальше — дабы минимизировать количество HTTP-запросов, проверять страницу на наличие необходимого функционала и загружать соответствующие скрипты лишь в случае его отсутствия.
Все это вылилось в функцию require (да, название сверх оригинальное), листинг которой ниже:
var require = function (lib, obj, libs_obj) { if (typeof obj === 'function') obj = { callback: obj, count: 1 } if (typeof(lib) === 'object') { // this is list of libs for (var i in lib) require(lib[i], obj, libs_obj); return; } var lib = libs_obj[lib]; if (lib.callbacks === undefined) lib.callbacks = []; if (lib.check()) { if (obj.callback) obj.callback(); return; } lib.callbacks.push(obj); if (lib.pending) { return; } lib.pending = true; function ready() { function script_downloaded() { lib.pending = false; var obj; while (obj = lib.callbacks.pop()) { obj.count--; if (obj.count == 0) obj.callback(); } } download_script(lib.link, script_downloaded); } function download_script(src, callback) { var script = document.createElement('script'); script.type = 'text/javascript'; script.async = 'async'; script.src = src; // Based on jQuery jsonp trick if (callback) { script.onload = script.onreadystatechange = function() { if (!script.readyState || /loaded|complete/.test(script.readyState)) { script.onload = script.onreadystatechange = null; callback(); } }; } document.getElementsByTagName('head')[0].appendChild(script); } var deps_count = lib.deps ? lib.deps.length : 0; if (deps_count < 1) { ready(); return; } var new_obj = { callback: ready, count: deps_count }; require(lib.deps, new_obj, libs_obj); };
Функция на чистом JS, работает в IE6+ (ну и других браузерах, разумеется). Вызов выглядит так:
code lang=”js”]require([‘list’, ‘of’, ‘libraries’], function () { alert ‘All libs loaded’; }, libs_obj) [/code]
где libs_obj — объект вида:
{ list: { // Функция для проверки, доступен ли данный функционал. // Здесь могут проверяться некоторые параметры, // однозначно определяющие, что функционал доступен check: function() { return list_exist(); }, // Ссылка на скрипт link: 'js/list_js_file.js', // Список библиотек, от которых зависит данная. // Может отсутствовать, если зависимостей нет deps: ['libraries', 'of'] }, of: { check: function() { return of_exist_and_version_is('1.2.3'); }, link: 'js/another_file.js', }, libraries: { check: funcion() { return libraries_exist() || analog_exist(); }, link: 'http://www.example.com/js/libraries.js', deps: ['of'] } }
Таким образом, несмотря на то что в вызове мы указали все три библиотеки, они будут выполнены в следующем порядке: сперва of, т. к. от нее зависит libraries, а она ни от кого не зависит, затем libraries, т. к. от нее зависит list, а затем и сам list — его зависимости удовлетворены. После этого будет выполнена callback-функция.
Загрузка библиотек, зависимости которых выполнены, производится параллельно.
Callback функцию можно заменить на false или undefined, если ее вызов нам не нужен.
В принципе, в моем случае можно было обойтись и более простым вариантом, но мне подумалось — а почему бы не поэкспериментировать? Хотелось бы конструктивной критики, дабы юный падаван возрастал в навыках.