Jak sprawić, by wykonanie skryptu czekało na załadowanie jquery

106

Mam problem, gdy strona ładuje się tak szybko, że jquery nie zakończyło ładowania przed wywołaniem jej przez kolejny skrypt. Czy jest sposób, aby sprawdzić istnienie jquery, a jeśli nie istnieje, poczekaj chwilę, a następnie spróbuj ponownie?


W odpowiedzi na poniższe odpowiedzi / komentarze zamieszczam część znaczników.

Sytuacja ... asp.net masterpage i childpage.

Na masterpage mam odniesienie do jquery. Następnie na stronie treści mam odniesienie do skryptu specyficznego dla strony. Kiedy ładowany jest skrypt specyficzny dla strony, narzeka, że ​​„$ jest niezdefiniowane”.

Umieściłem alerty w kilku punktach znaczników, aby zobaczyć kolejność, w jakiej zostały uruchomione, i potwierdziłem, że odpala w tej kolejności:

  1. Nagłówek strony wzorcowej.
  2. Blok zawartości strony podrzędnej 1 (znajduje się w nagłówku strony głównej, ale po wywołaniu skryptów strony głównej).
  3. Blok zawartości strony podrzędnej 2.

Oto znaczniki u góry strony głównej:

<%@ 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>

Następnie w treści strony głównej znajduje się dodatkowy element ContentPlaceHolder:

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

Na stronie podrzędnej wygląda to tak:

<%@ 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>

Oto zawartość pliku „../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();
    });

});
Amanda Kitson
źródło
4
używasz $(document).ready(function(){...});?
rownage
Jeśli ładujesz skrypty z prostymi <script src="...">tagami, to nie może się zdarzyć. Czy używasz czegoś takiego jak RequireJS lub LABjs?
Pointy
uruchamiasz kolejny skrypt w $(document).ready()lub $(window).load()? Czy możemy zobaczyć przykład?
John Hartsock,
1
@AmandaMyer teraz, zignoruj ​​wszystkie sugestie dotyczące używania gotowego dokumentu, ponieważ już to robisz, ale założę się, że to typ MIME powoduje, że nie ładują się synchronicznie
Sander
2
Spróbuj zmienić type="text/Scripts"się type="text/javascript", lub pozbyć się go wszyscy razem nie jest już potrzebne, a także pozbyć się language="javascript. Równie dobrze mógłbym zacząć próbować różnych rzeczy.
Jack

Odpowiedzi:

11

edytować

Czy możesz wypróbować poprawny typ tagów skryptu? Widzę, że używasz text/Scripts, co nie jest właściwym typem MIME dla javascript.

Użyj tego:

<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>

koniec edycji

lub możesz rzucić okiem na require.js, który jest programem ładującym twój kod javascript.

w zależności od projektu może to być jednak trochę przesada

Szlifierka
źródło
205

Spóźniony na imprezę i podobny do pytania Briguy37, ale dla przyszłego odniesienia używam następującej metody i przekazuję funkcje, które chcę odroczyć, aż do załadowania jQuery:

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

Będzie rekurencyjnie wywoływać metodę odroczenia co 50 ms, aż będzie window.jQueryistniała, kiedy kończy działanie i wywołujemethod()

Przykład z funkcją anonimową:

defer(function () {
    alert("jQuery is now loaded");
});
Darbio
źródło
2
To nie zadziałało dla mnie. W metodzie nie zdefiniowano znaku $ ... jeszcze nie wiem, dlaczego.
Aaron Lifshin
To było dobre rozwiązanie, aby uzyskać niestandardowy kod działający w witrynie zaprojektowanej przez kogoś innego w programie Adobe Muse.
Buggabill
@gidim Nie będzie, ponieważ jest to licznik czasu, a nie pętla. Ale będzie działać tak długo, jak długo użytkownik pozostanie na stronie.
Micros
Lepiej byłoby zbadać kolejność ładowania skryptów. Zauważyłem, że gdyby scripttag deferładujący jQuery miał atrybut, powodowałby problem, ładując się dopiero później, pomimo zdefiniowanej w kodzie kolejności skryptów.
ADTC
19

możesz użyć atrybutu defer, aby załadować skrypt na samym końcu.

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

ale normalnie ładowanie skryptu we właściwej kolejności powinno załatwić sprawę, więc pamiętaj, aby umieścić włączenie jquery przed własnym skryptem

Jeśli twój kod znajduje się na stronie, a nie w oddzielnym pliku js, musisz wykonać skrypt dopiero po przygotowaniu dokumentu, a hermetyzacja kodu również powinna działać:

$(function(){
//here goes your code
});
malko
źródło
To jest w porządku, o ile masz jedną zależność
Guillaume Massé
17

Jeszcze inny sposób, aby to zrobić, chociaż metoda odroczenia Darbio jest bardziej elastyczna.

(function() {
  var nTimer = setInterval(function() {
    if (window.jQuery) {
      // Do something with jQuery
      clearInterval(nTimer);
    }
  }, 100);
})();
thdoan
źródło
16

najłatwiej i najbezpieczniej jest użyć czegoś takiego:

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

        // place your code here.

        clearInterval(waitForJQuery);
    }
}, 10);
moisrex
źródło
2
Genialne rozwiązanie. Dziękuję
Diego Fortes
13

Możesz spróbować zdarzenia onload. Został podniesiony, gdy wszystkie skrypty zostały załadowane:

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

Pamiętaj jednak, że możesz przesłonić inne skrypty, w których window.onload używa.

Nigrimmist
źródło
to działa, ale prawdopodobnie zobaczysz flash niestylizowanej treści, jeśli twoje jquery zmieni wygląd strony
Matthew Lock
2
Możesz dodać detektor zdarzeń, aby uniknąć zastępowania innych skryptów: stackoverflow.com/a/15564394/32453 FWIW '
rogerdpack
@rogerdpack zgadzam się, to lepsze rozwiązanie.
Nigrimmist
10

Odkryłem, że sugerowane rozwiązanie działa tylko z myślą o kodzie asynchronicznym. Oto wersja, która będzie działać w obu przypadkach:

document.addEventListener('DOMContentLoaded', function load() {
    if (!window.jQuery) return setTimeout(load, 50);
    //your synchronous or asynchronous jQuery-related code
}, false);
Annarfych
źródło
Dzięki, sprawdzanie raz przy ładowaniu i tylko jako rozpoczęcie zapętlania awaryjnego co 50 ms jest o wiele lepsze niż zwykłe zapętlenie z setInterval, jak najlepsza głosowana odpowiedź. W najlepszym przypadku masz opóźnienie 0 ms, zamiast przeciętnego 25 ms najwyższej odpowiedzi.
Luc
Ładny i elegancki - podoba mi się sposób, w jaki przekazujesz tę loadfunkcję, ale potem też ją ponownie wykorzystuję setTimeout.
Nemesarial
6

To częsty problem, wyobraź sobie, że używasz fajnego silnika do tworzenia szablonów PHP, więc masz swój podstawowy układ:

HEADER
BODY ==> dynamic CONTENT/PAGE
FOOTER

I oczywiście, gdzieś czytasz, lepiej załadować JavaScript na dole strony, aby Twoja dynamiczna zawartość nie wiedziała, kto to jest jQuery (lub $).

Czytałeś też gdzieś, że dobrze jest wbudować mały Javascript, więc wyobraź sobie, że potrzebujesz jQuery na stronie, baboom, $ nie jest zdefiniowane (.. jeszcze ^^).

Uwielbiam rozwiązanie oferowane przez Facebooka

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

Więc jako leniwy programista (powinienem powiedzieć dobry programista ^^) możesz użyć odpowiednika (na swojej stronie):

window.jqReady = function() {}

I dodaj u dołu swojego układu, po dołączeniu jQuery

if (window.hasOwnProperty('jqReady')) $(function() {window.jqReady();});
Thomas Decaux
źródło
Dzięki! To jest dokładnie sprawa, z którą mam do czynienia.
KumZ
Lub przenieś ładowanie jquery na górę :) stackoverflow.com/a/11012073/32453
rogerdpack
5

Zamiast „czekać” (co jest zwykle wykonywane za pomocą setTimeout), możesz również użyć zdefiniowania obiektu jQuery w samym oknie jako punktu zaczepienia do wykonania kodu, który na nim opiera się. Można to osiągnąć poprzez definicję właściwości, zdefiniowaną za pomocą 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

    }
  });
})();
Obrońca pierwszy
źródło
byłoby to mało przydatne, gdyby jQuery faktycznie zdefiniowało właściwość okna, ale wydaje się, że tak nie jest?
Jim
Tak naprawdę nie ustawia właściwości, ale ustawia window.jQuerywartość, definiując jQueryzmienną w zakresie globalnym (tj window.). Spowoduje to wyzwolenie funkcji ustawiającej właściwości w obiekcie okna zdefiniowanym w kodzie.
Obrońca jeden
To nie działa, jeśli właściwość jQuery zostanie zdefiniowana przed wykonaniem tego kodu; tj. jeśli odroczony plik jquery.js zostanie załadowany przed załadowaniem tego kodu. Jednym z rozwiązań jest umieszczenie odroczonego pliku jquery.js przed </body>zamiast przed</head>
user36388
@user: Po prostu wykonaj kod przed załadowaniem jQuery (odroczonego lub w inny sposób). Nie ma znaczenia, gdzie go załadujesz, tylko że mój fragment kodu znajduje się przed nim.
Protector One
1
Jest to bardzo przydatne, jeśli możesz zagwarantować, że skrypty, które go używają, pojawią się przed włączeniem jQuery do twojego dokumentu. Żaden z nieefektywności lub scenariuszy wyścigowych z powyższych podejść pętlowych również.
RedYetiCo
4

Posługiwać się:

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

Sprawdź to, aby uzyskać więcej informacji: http://www.learningjquery.com/2006/09/introducing-document-ready

Uwaga: Powinno to działać, o ile import skryptu dla biblioteki JQuery jest powyżej tego wywołania.

Aktualizacja:

Jeśli z jakiegoś powodu twój kod nie ładuje się synchronicznie (co nigdy nie spotkałem, ale najwyraźniej jest to możliwe z poniższego komentarza nie powinno się zdarzyć), możesz zakodować go w następujący sposób.

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();
Briguy37
źródło
6
trwa to do momentu, gdy dom będzie gotowy, a nie do załadowania jquery. prawdopodobnie ma 2 pliki skryptów, 1 z CDN (jquery z google i wtyczka lokalnie), gdzie 1 jest ładowany wcześniej niż drugi .... Twój kod nie rozwiązuje tego problemu, jeśli wtyczka jest ładowana szybciej niż sama jquery
Sander
2
nie zgadzam się z tobą @Sander to powinno działać, ponieważ ładowanie jest synchroniczne
malko
masz rację, moje założenie, że są ładowane asynchronicznie, jeśli pochodzą z innej domeny, jest całkowicie błędne! przepraszam za to
Sander
Spowoduje to zgłoszenie wyjątku, jeśli jquery nie jest dostępne, co powoduje, że nigdy nie przechodzi do instrukcji else, jak się wydaje. Chyba powinna to być próba złapania czy coś.
Sam Stoelinga
@SamStoelinga: Dzięki, teraz powinno to zostać naprawione.
Briguy37,
3

Nie przepadam za rzeczami z przerwami. Kiedy chcę odroczyć jquery, a właściwie cokolwiek innego, zwykle wygląda to tak.

Zacząć od:

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

Następnie:

 <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>

Wreszcie:

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

Lub mniej zadziwiająca wersja:

<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>

A w przypadku async możesz wykonywać wypychane funkcje na jquery onload.

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

Alternatywnie:

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]();}});
Szymon
źródło
3

Rozwińmy defer()od Dario, aby było bardziej wielokrotnego użytku.

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

Który jest następnie uruchamiany:

function waitFor() {
    defer('jQuery', () => {console.log('jq done')});
    defer('utag', () => {console.log('utag done')});
}
mewc
źródło
2

Nie sądzę, że to twój problem. Ładowanie skryptu jest domyślnie synchroniczne, więc jeśli nie używasz deferatrybutu lub nie ładujesz samego jQuery za pośrednictwem innego żądania AJAX, Twój problem jest prawdopodobnie czymś w rodzaju błędu 404. Czy możesz pokazać swoje znaczniki i daj nam znać, jeśli zobaczysz coś podejrzanego firebug czy inspektor sieci?

jmar777
źródło
1

Sprawdź to:

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

Stworzy on fałszywy obiekt jQuery, który pozwoli ci użyć metod onload jquery, i zostaną one wykonane zaraz po załadowaniu jquery.

To nie jest idealne.

// 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);

Arnold Roa
źródło
jest to bezproblemowe rozwiązanie do rejestracji funkcji gotowych do dokumentów dzięki
zanedev
0

Styczna uwaga na temat podejść tutaj, które używają obciążenia setTimeoutlub setInterval. W takich przypadkach możliwe jest, że po ponownym uruchomieniu sprawdzenia DOM zostanie już załadowany, a DOMContentLoadedzdarzenie przeglądarki zostanie uruchomione , więc nie można wiarygodnie wykryć tego zdarzenia przy użyciu tych podejść. Odkryłem jednak, że jQuery readynadal działa, więc możesz osadzić swój zwykły plik

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

wewnątrz twojego setTimeoutlub setIntervali wszystko powinno działać normalnie.

Richard J.
źródło