Как заставить выполнение скрипта ждать загрузки jquery

106

У меня проблема, когда страница загружается так быстро, что jquery не завершил загрузку до того, как он будет вызван последующим скриптом. Есть ли способ проверить наличие jquery, и если он не существует, подождите немного, а затем повторите попытку?


В ответ на ответы / комментарии ниже я публикую часть разметки.

Ситуация ... главная страница asp.net и дочерняя страница.

На главной странице у меня есть ссылка на jquery. Затем на странице содержимого у меня есть ссылка на скрипт для конкретной страницы. Когда загружается скрипт для конкретной страницы, он жалуется, что «$ не определено».

Я разместил предупреждения в нескольких точках разметки, чтобы увидеть порядок, в котором срабатывают объекты, и подтвердил, что они срабатывают в следующем порядке:

  1. Заголовок главной страницы.
  2. Блок содержимого дочерней страницы 1 (находится внутри заголовка главной страницы, но после вызова скриптов главной страницы).
  3. Блок содержимого дочерней страницы 2.

Вот разметка вверху главной страницы:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="SiteMaster" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Reporting Portal</title>
    <link href="~/Styles/site.css" rel="stylesheet" type="text/css" />
    <link href="~/Styles/red/red.css" rel="stylesheet" type="text/css" />
    <script type="text/Scripts" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> 
    <script type="text/Scripts" language="javascript" src="../Scripts/jquery.dropdownPlain.js"></script>
    <script type="text/Scripts" language="javascript" src="../Scripts/facebox.js"></script>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>

Затем в теле мастер-страницы есть дополнительный ContentPlaceHolder:

 <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
                </asp:ContentPlaceHolder>

На дочерней странице это выглядит так:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Dashboard.aspx.cs" Inherits="Data.Dashboard" %>
<%@ Register src="../userControls/ucDropdownMenu.ascx" tagname="ucDropdownMenu" tagprefix="uc1" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
    <link rel="stylesheet" type="text/css" href="../Styles/paserMap.css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
***CONTENT HERE***
    <script src="../Scripts/Dashboard.js" type="text/javascript"></script>
</asp:Content>

Вот содержимое файла "../Script/Dashboard.js":

    $(document).ready(function () {

    $('.tgl:first').show(); // Show the first div

    //Description: East panel parent tab navigation
    $('.tabNav label').click(function () {
        $('.tabNav li').removeClass('active')
        $(this).parent().addClass('active');

        var index = $(this).parent('li').index();
        var divToggle = $('.ui-layout-content').children('div.tgl');

        //hide all subToggle divs
        divToggle.hide();
        divToggle.eq(index).show();
    });

});
Аманда Китсон
источник
4
ты используешь $(document).ready(function(){...});?
rownage
Если вы загружаете скрипты с простыми <script src="...">тегами, этого не произойдет. Вы используете что-то вроде RequireJS или LABjs?
Pointy
вы запускаете следующий сценарий в $(document).ready()или $(window).load()? Можем ли мы увидеть пример?
Джон Хартсок,
1
@AmandaMyer, теперь игнорируйте все предложения об использовании готового документа, поскольку вы уже делаете это, но я уверен, что это mimetype, который заставляет их не загружаться синхронно
Сандер
2
Попробуйте изменить type="text/Scripts"к type="text/javascript", или избавиться от нее все вместе это не нужно больше, а также избавиться от language="javascript. С таким же успехом можно начать пробовать.
Джек,

Ответы:

11

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

Не могли бы вы попробовать правильный тип для ваших тегов скрипта? Я вижу, вы используете text/Scriptsmimetype, который не подходит для javascript.

Использовать это:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> 
<script type="text/javascript" src="../Scripts/jquery.dropdownPlain.js"></script>
<script type="text/javascript" src="../Scripts/facebox.js"></script>

конец править

или вы можете взглянуть на require.js, который является загрузчиком вашего кода javascript.

в зависимости от вашего проекта, это может быть немного излишним

шлифовальный
источник
205

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

function defer(method) {
    if (window.jQuery) {
        method();
    } else {
        setTimeout(function() { defer(method) }, 50);
    }
}

Он будет рекурсивно вызывать метод defer каждые 50 мс, пока не будет window.jQueryсуществовать, когда он завершится и вызоветmethod()

Пример с анонимной функцией:

defer(function () {
    alert("jQuery is now loaded");
});
Дарбио
источник
2
У меня это не сработало. Внутри метода $ не было определено ... пока не знаю почему.
Аарон Лифшин
Это было отличным решением для того, чтобы заставить некоторый пользовательский код работать на сайте, который был разработан кем-то другим в Adobe Muse.
Buggabill
@gidim Не будет, потому что это таймер, а не цикл. Но он будет работать, пока пользователь остается на странице.
Micros
Было бы лучше разобраться с порядком загрузки скриптов. Я обнаружил, что если у scriptтега, который загружает jQuery, есть deferатрибут, это вызовет проблему, не загрузив его позже, несмотря на определенный в коде порядок скриптов.
ADTC
19

вы можете использовать атрибут defer для загрузки скрипта в самом конце.

<script type='text/javascript' src='myscript.js' defer='defer'></script>

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

Если ваш код находится на странице, а не в отдельном js-файле, вам нужно выполнить свой скрипт только после того, как документ будет готов, и инкапсуляция вашего кода, как это, тоже должно работать:

$(function(){
//here goes your code
});
Малько
источник
Это нормально, если у вас одна зависимость
Гийом Массе
17

Еще один способ сделать это, хотя метод отсрочки Darbio более гибкий.

(function() {
  var nTimer = setInterval(function() {
    if (window.jQuery) {
      // Do something with jQuery
      clearInterval(nTimer);
    }
  }, 100);
})();
thdoan
источник
16

Самый простой и безопасный способ - использовать что-то вроде этого:

var waitForJQuery = setInterval(function () {
    if (typeof $ != 'undefined') {

        // place your code here.

        clearInterval(waitForJQuery);
    }
}, 10);
Moisrex
источник
2
Гениальное решение. Спасибо
Диего Фортес
13

Вы можете попробовать событие onload. Он возникает после загрузки всех скриптов:

window.onload = function () {
   //jquery ready for use here
}

Но имейте в виду, что вы можете переопределить другие скрипты, в которых используется window.onload.

Нигриммист
источник
это работает, но вы, вероятно, увидите вспышку нестилизованного контента, если ваш jquery изменит внешний вид страницы
Мэтью Лок
2
Вы можете добавить прослушиватель событий, чтобы избежать переопределения других скриптов: stackoverflow.com/a/15564394/32453 FWIW '
rogerdpack
@rogerdpack согласен, это лучшее решение.
Nigrimmist
10

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

document.addEventListener('DOMContentLoaded', function load() {
    if (!window.jQuery) return setTimeout(load, 50);
    //your synchronous or asynchronous jQuery-related code
}, false);
Annarfych
источник
Спасибо, проверка один раз при загрузке и только в качестве резервного запуска цикла каждые 50 мс намного лучше, чем просто позволить ему зацикливаться с setInterval, как ответ, получивший наибольшее количество голосов. В лучшем случае у вас будет задержка 0 мс вместо среднего 25 мс для верхнего ответа.
Люк
Красиво и элегантно - мне нравится, как вы передаете loadфункцию, но потом снова ее используете setTimeout.
Nemesarial
6

Это обычная проблема, представьте, что вы используете классный шаблонизатор PHP, поэтому у вас есть базовый макет:

HEADER
BODY ==> dynamic CONTENT/PAGE
FOOTER

И, конечно, вы где-то читали, что лучше загружать Javascript внизу страницы, чтобы ваш динамический контент не знал, кто такой jQuery (или $).

Также вы где-то читали, что полезно встроить небольшой Javascript, поэтому представьте, что вам нужен jQuery на странице, baboom, $ не определен (.. пока ^^).

Мне нравится решение, которое предоставляет Facebook

window.fbAsyncInit = function() { alert('FB is ready !'); }

Итак, как ленивый программист (я бы сказал, хороший программист ^^), вы можете использовать эквивалент (на своей странице):

window.jqReady = function() {}

И добавьте внизу вашего макета после того, как jQuery include

if (window.hasOwnProperty('jqReady')) $(function() {window.jqReady();});
Томас Деко
источник
Спасибо! Это именно тот случай, с которым я столкнулся.
KumZ
Или переместите загрузку jquery наверх :) stackoverflow.com/a/11012073/32453
rogerdpack
5

Вместо того, чтобы «ждать» (что обычно выполняется с помощью setTimeout), вы также можете использовать определение объекта jQuery в самом окне в качестве ловушки для выполнения вашего кода, который на него полагается. Это достигается с помощью определения свойства, определенного с помощью Object.defineProperty.

(function(){
  var _jQuery;
  Object.defineProperty(window, 'jQuery', {
    get: function() { return _jQuery; },
    set: function($) {
      _jQuery = $;

      // put code or call to function that uses jQuery here

    }
  });
})();
Защитник один
источник
это было бы весьма полезно, если бы jQuery действительно определял свойство окна, но, похоже, это не так?
Джим
На самом деле он не устанавливает свойство, но устанавливает window.jQueryзначение, определяя jQueryпеременную в глобальной области (т.е. window). Это вызовет функцию установки свойств для объекта окна, определенного в коде.
Protector one
Это не работает, если свойство jQuery определяется до выполнения этого кода; т.е. если отложенный jquery.js загружается до загрузки этого кода. Одно из решений - поместить отложенный jquery.js раньше, </body>а не раньше</head>
user36388
@user: просто выполните код до загрузки jQuery (отложенного или другого). Неважно, куда вы его загружаете, просто перед ним идет мой фрагмент кода.
Protector one
1
Это очень полезно, если вы можете гарантировать, что скрипты, использующие это, появятся до включения jQuery в ваш документ. Ни неэффективности, ни сценариев гонок из описанных выше циклов тоже нет.
RedYetiCo
4

Использование:

$(document).ready(function() {
    // put all your jQuery goodness in here.
});

Ознакомьтесь с этим для получения дополнительной информации: http://www.learningjquery.com/2006/09/introduction-document-ready

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

Обновить:

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

function yourFunctionToRun(){
    //Your JQuery goodness here
}

function runYourFunctionWhenJQueryIsLoaded() {
    if (window.$){
        //possibly some other JQuery checks to make sure that everything is loaded here

        yourFunctionToRun();
    } else {
        setTimeout(runYourFunctionWhenJQueryIsLoaded, 50);
    }
}

runYourFunctionWhenJQueryIsLoaded();
Бригуй37
источник
6
это ожидание до тех пор, пока dom не будет готов, а не до загрузки jquery. у него, вероятно, есть 2 файла сценария, 1 из CDN (jquery из Google и плагин локально), где 1 загружается раньше, чем другой .... ваш код не решает этого, если плагин загружается быстрее, чем сам jquery
Sander
2
я не согласен с вами @Sander, это должно работать, так как загрузка синхронная
malko
вы правы, мое предположение о том, что они загружаются асинхронно, если они происходят из другого домена, совершенно неверно! извините за это
Сандер
Это вызовет исключение, если jquery недоступен, из-за чего он никогда не перейдет в оператор else, который кажется. Думаю, это должна быть попытка поймать или что-то в этом роде.
Sam Stoelinga
@SamStoelinga: Спасибо, теперь это нужно исправить.
Briguy37
3

Я не очень люблю интервальные штучки. Когда я хочу отложить jquery или что-то еще, обычно происходит что-то вроде этого.

Начнем с:

<html>
 <head>
  <script>var $d=[];var $=(n)=>{$d.push(n)}</script>
 </head>

Затем:

 <body>
  <div id="thediv"></div>

  <script>
    $(function(){
       $('#thediv').html('thecode');
    });
  </script>

  <script src="http://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>

И наконец:

  <script>for(var f in $d){$d[f]();}</script>
 </body>
<html>

Или менее ошеломляющая версия:

<script>var def=[];function defer(n){def.push(n)}</script>
<script>
defer(function(){
   $('#thediv').html('thecode');
});
</script>
<script src="http://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>
<script>for(var f in def){def[f]();}</script>

А в случае async вы можете выполнять push-функции при загрузке jquery.

<script async onload="for(var f in def){def[f]();}" 
src="jquery.min.js" type="text/javascript"></script>

Альтернативно:

function loadscript(src, callback){
  var script = document.createElement('script');
  script.src = src
  script.async = true;
  script.onload = callback;
  document.body.appendChild(script);
};
loadscript("jquery.min", function(){for(var f in def){def[f]();}});
Саймон
источник
3

Давайте расширим defer()Dario, чтобы он был более многоразовым.

function defer(toWaitFor, method) {
    if (window[toWaitFor]) {
        method();
    } else {
        setTimeout(function () { defer(toWaitFor, method) }, 50);
    }
}

Что затем запускается:

function waitFor() {
    defer('jQuery', () => {console.log('jq done')});
    defer('utag', () => {console.log('utag done')});
}
мяук
источник
2

Я не думаю, что это твоя проблема. По умолчанию загрузка сценария выполняется синхронно, поэтому, если вы не используете deferатрибут или не загружаете сам jQuery через другой запрос AJAX, ваша проблема, вероятно, больше похожа на ошибку 404. Можете ли вы показать свою разметку и сообщить нам, если вы заметите что-нибудь подозрительное в firebug или веб-инспектор?

jmar777
источник
1

Проверь это:

https://jsfiddle.net/neohunter/ey2pqt5z/

Он создаст поддельный объект jQuery, который позволяет вам использовать методы onload jquery, и они будут выполнены, как только jquery будет загружен.

Это не идеально.

// This have to be on <HEAD> preferibly inline
var delayed_jquery = [];
jQuery = function() {
  if (typeof arguments[0] == "function") {
    jQuery(document).ready(arguments[0]);
  } else {
    return {
      ready: function(fn) {
        console.log("registering function");
        delayed_jquery.push(fn);
      }
    }
  }
};
$ = jQuery;
var waitForLoad = function() {
  if (typeof jQuery.fn != "undefined") {
    console.log("jquery loaded!!!");
    for (k in delayed_jquery) {
      delayed_jquery[k]();
    }
  } else {
    console.log("jquery not loaded..");
    window.setTimeout(waitForLoad, 500);
  }
};
window.setTimeout(waitForLoad, 500);
// end



// now lets use jQuery (the fake version)
jQuery(document).ready(function() {
  alert('Jquery now exists!');
});

jQuery(function() {
  alert('Jquery now exists, this is using an alternative call');
})

// And lets load the real jquery after 3 seconds..
window.setTimeout(function() {
  var newscript = document.createElement('script');
  newscript.type = 'text/javascript';
  newscript.async = true;
  newscript.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js';
  (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(newscript);
}, 3000);

Арнольд Роа
источник
это
простое
0

Косвенное замечание о подходах здесь, которые нагружают setTimeoutили setInterval. В таких случаях возможно, что при повторном запуске проверки DOM уже будет загружена, и DOMContentLoadedсобытие браузера будет запущено, поэтому вы не сможете надежно обнаружить это событие, используя эти подходы. Я обнаружил, что jQuery все readyеще работает, поэтому вы можете встроить свой обычный

jQuery(document).ready(function ($) { ... }

внутри вашего setTimeoutили setIntervalи все должно работать как обычно.

Ричард Дж
источник