document.createElement («скрипт») синхронно - PullRequest
68 голосов
/ 14 июля 2010

Можно ли вызвать файл .js синхронно, а затем сразу же использовать его?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

Это упрощено.В моей реализации материал createElement находится в функции.Я думал о добавлении чего-то в функцию, которая могла бы проверить, была ли создана конкретная переменная перед возвратом управления.Но тогда остается проблема с тем, что делать, если включить js с другого сайта, который я не могу контролировать.

Мысли?

Редактировать:

Я приняллучший ответ на данный момент, потому что он дает хорошее объяснение того, что происходит.Но если у кого-то есть какие-либо предложения по улучшению этого, я открыт для них.Вот пример того, что я хотел бы сделать.

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

Я просто не хочу слишком много знать о внутренностях и просто могу сказать: «Я хочу использовать этот модуль, итеперь я буду использовать некоторый код из этого. "

Ответы [ 10 ]

111 голосов
/ 14 июля 2010

Вы можете создать свой элемент <script> с помощью обработчика «onload», и он будет вызываться, когда скрипт загружается и оценивается браузером.

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

Вы не можете этого сделатьсинхронно.

edit - отмечалось, что, верный форме, IE не запускает событие "load" для <script> тегов, которые загружаются / оцениваются.Таким образом, я полагаю, что следующее, что нужно сделать, это получить скрипт с XMLHttpRequest, а затем eval() самостоятельно.(Или, я полагаю, вставьте текст в добавляемый вами тег <script>; среда выполнения eval() зависит от локальной области видимости, поэтому он не обязательно будет делать то, что вы хотите.)

edit - По состоянию на начало 2013 года я настоятельно рекомендовал бы изучить более надежный инструмент загрузки скриптов, такой как Requirejs .Есть много особых случаев, о которых нужно беспокоиться.Для действительно простых ситуаций есть yepnope , который теперь встроен в Modernizr .

23 голосов
/ 20 июля 2010

Это не красиво, но работает:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

Или

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

Сценарий должен быть включен либо в отдельный тег <script>, либо перед window.onload().

Это не будет работать:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

То же самое можно сделать с созданием узла, как это делал Pointy, но только в FF.У вас нет гарантии, когда скрипт будет готов в других браузерах.

Будучи XML Purist, я действительно ненавижу это.Но это работает предсказуемо.Вы можете легко обернуть эти уродливые document.write() с, чтобы вам не пришлось смотреть на них.Вы даже можете выполнить тесты, создать узел и добавить его, а затем вернуться к document.write().

16 голосов
/ 05 января 2013

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

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

Некоторое время назад я сделал небольшую запись в блоге http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/

7 голосов
/ 15 июля 2010

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

Дуглас Крокфорд ( YUI Blog )

Хорошо, пристегните сиденья, потому что это будет ухабистая поездка.Все больше и больше людей спрашивают о динамической загрузке сценариев с помощью javascript, это кажется горячей темой.

Основные причины, по которым это стало настолько популярным:

  • модульность на стороне клиента
  • упрощенное управление зависимостями
  • обработка ошибок
  • преимущества производительности

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

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

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

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

Но единственное, о чем мы должны заботиться, когда это должно быть решено относительно дизайна приложений, это опыт конечного пользователя .И в этой области асинхронный не может быть побежден.Пользователь получает немедленных ответов (или, скажем, обещания), и обещание всегда лучше, чем ничего.Пустой экран пугает людей.Разработчики не должны лениться улучшать воспринимаемую производительность .

И, наконец, несколько слов о грязной стороне.Что нужно сделать, чтобы заставить его работать в разных браузерах:

  1. научиться асинхронно мыслить
  2. организовать свой код как модульный
  3. организовать свой код для обработки ошибоки крайние случаи хорошо
  4. постепенно улучшаются
  5. всегда заботятся о правильном количестве обратной связи
4 голосов
/ 16 марта 2016

Ответы выше указали мне в правильном направлении.Вот общая версия того, что я получил, работая:

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      
3 голосов
/ 14 июля 2010

Это выглядит как приличный обзор динамической загрузки скрипта: http://unixpapa.com/js/dyna.html

2 голосов
/ 03 ноября 2017
function include(file){
return new Promise(function(resolve, reject){
        var script = document.createElement('script');
        script.src = file;
        script.type ='text/javascript';
        script.defer = true;
        document.getElementsByTagName('head').item(0).appendChild(script);

        script.onload = function(){
        resolve()
        }
        script.onerror = function(){
          reject()
        }
      })

 /*I HAVE MODIFIED THIS TO  BE PROMISE-BASED 
   HOW TO USE THIS FUNCTION 

  include('js/somefile.js').then(function(){
  console.log('loaded');
  },function(){
  console.log('not loaded');
  })
  */
}
2 голосов
/ 15 февраля 2014

У меня были следующие проблемы с существующими ответами на этот вопрос (и вариации этого вопроса в других потоках stackoverflow):

  • Ни один из загруженного кода не был отлаживаемым
  • Многие из решений требовали, чтобы обратные вызовы знали, когда загрузка была завершена, вместо того, чтобы действительно блокировать, то есть я получал бы ошибки выполнения при немедленном вызове загруженного (т.е. загрузочного) кода.Или чуть точнее:
    • Ни один из загруженного кода не был отлаживаемым (кроме блока тегов HTML-сценария, если и только если решение добавило элементы сценария в dom, и никогдакогда-либо как отдельные видимые сценарии.) => Учитывая, сколько сценариев мне нужно загрузить (и отладить), это было неприемлемо.
    • Решения, использующие события 'onreadystatechange' или 'onload', не смогли заблокировать, чтобыла большая проблема, так как код изначально загружал динамические скрипты синхронно, используя 'require ([filename,' dojo / domReady ']);'и я убираю dojo.

    Мое окончательное решение, которое загружает скрипт перед возвратом, И все скрипты должным образом доступны в отладчике (по крайней мере, для Chrome), выглядит следующим образом:

    ВНИМАНИЕ: следующий код ДОЛЖЕН использоваться только в режиме «разработки». (Для режима «выпуска» я рекомендую предварительную упаковку и минификацию БЕЗ динамической загрузки скрипта или, по крайней мере, без eval).

    //Code User TODO: you must create and set your own 'noEval' variable
    
    require = function require(inFileName)
    {
        var aRequest
            ,aScript
            ,aScriptSource
            ;
    
        //setup the full relative filename
        inFileName = 
            window.location.protocol + '//'
            + window.location.host + '/'
            + inFileName;
    
        //synchronously get the code
        aRequest = new XMLHttpRequest();
        aRequest.open('GET', inFileName, false);
        aRequest.send();
    
        //set the returned script text while adding special comment to auto include in debugger source listing:
        aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';
    
        if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
        {
            //create a dom element to hold the code
            aScript = document.createElement('script');
            aScript.type = 'text/javascript';
    
            //set the script tag text, including the debugger id at the end!!
            aScript.text = aScriptSource;
    
            //append the code to the dom
            document.getElementsByTagName('body')[0].appendChild(aScript);
        }
        else
        {
            eval(aScriptSource);
        }
    };
    
1 голос
/ 29 августа 2013

Я привык иметь на своем веб-сайте несколько файлов .js, которые зависят друг от друга. Чтобы загрузить их и убедиться, что зависимости оценены в правильном порядке, я написал функцию, которая загружает все файлы и затем, как только они все получены, eval() их. Основным недостатком является то, что это не работает с CDN. Для таких библиотек (например, jQuery) лучше включать их статически. Обратите внимание, что вставка узлов сценариев в HTML динамически не гарантирует, что сценарии будут оцениваться в правильном порядке, по крайней мере, не в Chrome (это было основной причиной написания этой функции).

function xhrs(reqs) {
  var requests = [] , count = [] , callback ;

  callback = function (r,c,i) {
    return function () {
      if  ( this.readyState == 4 ) {
        if (this.status != 200 ) {
          r[i]['resp']="" ;
        } 
        else {
          r[i]['resp']= this.responseText ;
        }
        c[0] = c[0] - 1 ;
        if ( c[0] == 0 ) {
          for ( var j = 0 ; j < r.length ; j++ ) {
            eval(r[j]['resp']) ;
          }
        }
      }
    }
  } ;
  if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
    requests.length = reqs.length ;
  }
  else {
    requests.length = 1 ;
    reqs = [].concat(reqs);
  }
  count[0] = requests.length ;
  for ( var i = 0 ; i < requests.length ; i++ ) {
    requests[i] = {} ;
    requests[i]['xhr'] = new XMLHttpRequest () ;
    requests[i]['xhr'].open('GET', reqs[i]) ;
    requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
    requests[i]['xhr'].send(null);
  }
}

Я не понял, как сделать ссылки на одно и то же значение без создания массива (для подсчета). В противном случае я думаю, что это само за себя (когда все загружено, eval() каждый файл в указанном порядке, в противном случае просто сохранить ответ).

Пример использования:

xhrs( [
       root + '/global.js' ,
       window.location.href + 'config.js' ,
       root + '/js/lib/details.polyfill.min.js',
       root + '/js/scripts/address.js' ,
       root + '/js/scripts/tableofcontents.js' 
]) ;
0 голосов
/ 24 октября 2011

Как ни странно, у меня есть то, что вы хотите, но хотите что-то ближе к тому, что было у вас.

Я загружаю вещи динамически и асинхронно, но с помощью обратного вызова load, как это делается (с использованием dojo и xmlhtpprequest)

  dojo.xhrGet({
    url: 'getCode.php',
    handleAs: "javascript",
    content : {
    module : 'my.js'
  },
  load: function() {
    myFunc1('blarg');
  },
  error: function(errorMessage) {
    console.error(errorMessage);
  }
});

Для более подробного объяснения см. здесь

Проблема в том, что где-то вдоль строки код исчезает, и если с вашим кодом что-то не такоператор console.error(errorMessage); укажет строку, где eval(), а не фактическую ошибку.Это ТАКАЯ большая проблема, которую я пытаюсь преобразовать обратно в <script> операторы (см. здесь .

...