Загрузка JS по требованию — с учетом некоторых особенностей

16 мая 2011 г.

На работе передо мной встала задача: реализовать некий функционал, который бы подключался к стороннему сайту одним скриптом, дабы не напрягать неискушенного вебмастера (коими и являлись бы в большинстве случаев потребителями этого продукта).
Но дело в том, что данный функционал зависел от 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, если ее вызов нам не нужен.

В принципе, в моем случае можно было обойтись и более простым вариантом, но мне подумалось — а почему бы не поэкспериментировать? Хотелось бы конструктивной критики, дабы юный падаван возрастал в навыках.

Теги: рубрика JavaScript, Сайтостроение
  • Похожие статьи
  • Предыдущие из рубрики