Como fazer a execução do script esperar até que o jquery seja carregado

106

Estou tendo um problema de carregamento da página tão rápido que o jquery não terminou de carregar antes de ser chamado por um script subsequente. Existe uma maneira de verificar a existência de jquery e se ele não existir, aguarde um momento e tente novamente?


Em resposta às respostas / comentários abaixo, estou postando algumas das marcações.

A situação ... masterpage asp.net e childpage.

Na masterpage, tenho uma referência a jquery. Então, na página de conteúdo, tenho uma referência ao script específico da página. Quando o script específico da página está sendo carregado, ele reclama que "$ é indefinido".

Coloquei alertas em vários pontos da marcação para ver a ordem em que as coisas estavam disparando e confirmei que ele dispara nesta ordem:

  1. Cabeçalho da página mestra.
  2. Bloco 1 de conteúdo da página filha (localizado dentro do cabeçalho da página-mestre, mas depois que os scripts da página-mestre são chamados).
  3. Bloco 2 do conteúdo da página filha.

Aqui está a marcação no topo da página-mestre:

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

Então, no corpo da página-mestre, há um ContentPlaceHolder adicional:

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

Na página filha, é assim:

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

Aqui está o conteúdo do arquivo "../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
fonte
4
você está usando $(document).ready(function(){...});?
rownage de
Se você estiver carregando scripts com <script src="...">tags simples , isso não pode acontecer. Você está usando algo como RequireJS ou LABjs?
Pointy de
você está executando um script subsequente no $(document).ready()ou $(window).load()? Podemos ver um exemplo?
John Hartsock de
1
@AmandaMyer agora, ignore todas as sugestões sobre como usar o documento pronto, uma vez que você já está fazendo isso, mas aposto que é o tipo MIME que está fazendo com que eles não carreguem sincronizadamente
Sander
2
Tente mudar type="text/Scripts"para type="text/javascript", ou livre-se de tudo junto, não é mais necessário, e livre-se também do language="javascript. É melhor começar a tentar coisas.
Jack de

Respostas:

11

editar

Você poderia tentar o tipo correto para suas tags de script? Vejo que você usa text/Scripts, que não é o tipo MIME correto para javascript.

Usa isto:

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

fim da edição

ou você pode dar uma olhada em require.js que é um carregador para seu código javascript.

dependendo do seu projeto, isso pode ser um pouco exagero

Sander
fonte
204

Atrasado para a festa, e semelhante à pergunta de Briguy37, mas para referência futura, uso o seguinte método e passo as funções que desejo adiar até que o jQuery seja carregado:

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

Ele irá chamar recursivamente o método defer a cada 50 ms até que window.jQueryexista, momento em que ele sai e chamamethod()

Um exemplo com uma função anônima:

defer(function () {
    alert("jQuery is now loaded");
});
Darbio
fonte
2
Isto não funcionou para mim. Dentro do método, $ não foi definido ... ainda não sei por quê.
Aaron Lifshin
Esta foi uma boa solução para obter algum código personalizado funcionando em um site que foi projetado por outra pessoa no Adobe Muse.
Buggabill
@gidim Não vai, porque é um cronômetro e não um loop. Mas ele continuará em execução enquanto o usuário permanecer na página.
Micros
Seria melhor investigar a ordem de carregamento dos scripts. Descobri que se a scripttag que carrega jQuery tivesse um deferatributo, isso causaria o problema por não carregar até mais tarde, apesar da ordem dos scripts definida pelo código.
ADTC de
19

você pode usar o atributo defer para carregar o script no final.

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

mas normalmente carregar seu script na ordem correta deve resolver, então certifique-se de colocar a inclusão jquery antes de seu próprio script

Se seu código estiver na página e não em um arquivo js separado, você deve executar seu script somente depois que o documento estiver pronto e encapsular seu código desta forma também deve funcionar:

$(function(){
//here goes your code
});
Malko
fonte
Tudo bem, desde que você tenha uma única dependência
Guillaume Massé
16

Mais uma maneira de fazer isso, embora o método de adiamento de Darbio seja mais flexível.

(function() {
  var nTimer = setInterval(function() {
    if (window.jQuery) {
      // Do something with jQuery
      clearInterval(nTimer);
    }
  }, 100);
})();
thdoan
fonte
16

a maneira mais fácil e segura é usar algo assim:

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

        // place your code here.

        clearInterval(waitForJQuery);
    }
}, 10);
Moisrex
fonte
2
Solução genial. Obrigado
Diego Fortes,
13

Você pode tentar o evento onload. Ele é gerado quando todos os scripts são carregados:

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

Mas tenha em mente que você pode sobrescrever outros scripts onde window.onload usando.

Nigrimmist
fonte
isso funciona, mas você provavelmente verá um flash de conteúdo sem estilo se seu jquery mudar a aparência da página
Matthew Lock
1
Você pode adicionar um ouvinte de evento para evitar a substituição de outros scripts: stackoverflow.com/a/15564394/32453 FWIW '
rogerdpack
@rogerdpack concorda, é a melhor solução.
Nigrimmist
10

Descobri que a solução sugerida só funciona considerando o código assíncrono. Aqui está a versão que funcionaria em ambos os casos:

document.addEventListener('DOMContentLoaded', function load() {
    if (!window.jQuery) return setTimeout(load, 50);
    //your synchronous or asynchronous jQuery-related code
}, false);
Annarfych
fonte
Obrigado, verificar uma vez no carregamento e apenas quando o fallback começa a fazer um loop a cada 50 ms é muito melhor do que apenas deixá-lo fazer um loop com setInterval como a resposta mais votada. No melhor caso, você tem 0 ms de atraso, em vez do caso médio de 25 ms da resposta principal.
Luc
Agradável e elegante - gosto de como você passa a loadfunção, mas também a reutiliza setTimeout.
Nemesarial
6

É um problema comum, imagine que você usa um mecanismo de modelagem de PHP legal, então você tem seu layout básico:

HEADER
BODY ==> dynamic CONTENT/PAGE
FOOTER

E, claro, você leu em algum lugar, é melhor carregar Javascript na parte inferior da página, para que seu conteúdo dinâmico não saiba quem é jQuery (ou o $).

Além disso, você leu em algum lugar que é bom incorporar Javascript pequeno, então imagine que você precisa do jQuery em uma página, baboom, $ não está definido (.. ainda ^^).

Eu amo a solução que o Facebook oferece

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

Portanto, como um programador preguiçoso (devo dizer um bom programador ^^), você pode usar um equivalente (dentro de sua página):

window.jqReady = function() {}

E adicione na parte inferior do seu layout, após jQuery incluir

if (window.hasOwnProperty('jqReady')) $(function() {window.jqReady();});
Thomas Decaux
fonte
Obrigado! Esse é exatamente o caso que estou enfrentando.
KumZ de
Ou mova o carregamento do jquery para o topo :) stackoverflow.com/a/11012073/32453
rogerdpack
5

Em vez de "esperar" (o que geralmente é feito usando setTimeout), você também pode usar a definição do objeto jQuery na própria janela como um gancho para executar seu código que depende dele. Isso é possível por meio de uma definição de propriedade, definida usando 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

    }
  });
})();
Protetor um
fonte
isso seria vagamente útil se jQuery realmente definisse uma propriedade de janela, mas não parece?
Jim
Na verdade, não define uma propriedade, mas define window.jQueryum valor, definindo a jQueryvariável no escopo global (ou seja window). Isso irá disparar a função configuradora de propriedade no objeto de janela definido no código.
Protetor um de
Isso não funciona se a propriedade jQuery for definida antes que este código seja executado; ou seja, se seu jquery.js adiado for carregado antes que este código seja carregado. Uma solução é colocar seu jquery.js adiado antes, em </body>vez de antes</head>
user36388
@ usuário: Simplesmente execute o código antes que o jQuery (adiado ou não) seja carregado. Não importa onde você o carrega, apenas que meu fragmento de código vem antes dele.
Protetor um de
1
Isso é muito útil se você pode garantir que os scripts que usam isso vêm antes da inclusão de jQuery em seu documento. Nenhuma das ineficiências ou cenários de corrida das abordagens de loop acima também.
RedYetiCo
4

Usar:

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

Verifique isso para obter mais informações: http://www.learningjquery.com/2006/09/introducing-document-ready

Nota: Isso deve funcionar contanto que a importação de script para sua biblioteca JQuery esteja acima desta chamada.

Atualizar:

Se por algum motivo seu código não estiver carregando de forma síncrona (o que eu nunca encontrei, mas aparentemente pode ser possível a partir do comentário abaixo não deve acontecer), você pode codificá-lo como o seguinte.

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
fonte
6
essa espera é até que o dom esteja pronto, não até que o jquery seja carregado. ele provavelmente tem 2 arquivos de script, 1 de um CDN (jquery do google e um plugin localmente) onde o 1 é carregado antes do outro .... seu código não resolve isso, se o plugin é carregado mais rápido que o próprio jquery
Sander de
2
discordo de você @Sander, isso deve funcionar porque o carregamento é síncrono
malko
você está certo, minha suposição sobre eles serem carregados de forma assíncrona se vierem de um domínio diferente está completamente errada! desculpe-me por isso
Sander de
Isso lançará uma exceção se jquery não estiver disponível, fazendo com que ele nunca entre na instrução else que parece. Acho que deve ser uma tentativa de pegar ou algo assim.
Sam Stoelinga
@SamStoelinga: Obrigado, isso agora deve ser corrigido.
Briguy37
3

Eu não gosto muito de coisas de intervalo. Quando eu quero adiar o jquery, ou qualquer coisa na verdade, geralmente é algo assim.

Começar com:

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

Então:

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

Então finalmente:

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

Ou a versão menos incompreensível:

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

E, no caso de assíncrono, você pode executar as funções enviadas em jquery onload.

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

Alternativamente:

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]();}});
Simon
fonte
2

Não acho que seja problema seu. O carregamento do script é síncrono por padrão, então, a menos que você esteja usando o deferatributo ou carregando o próprio jQuery por meio de outra solicitação AJAX, seu problema é provavelmente algo mais como 404. Você pode mostrar sua marcação e nos avise se encontrar algo suspeito em firebug ou inspetor da web?

jmar777
fonte
2

Vamos expandir defer()de Dario para ser mais reutilizável.

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

Que é então executado:

function waitFor() {
    defer('jQuery', () => {console.log('jq done')});
    defer('utag', () => {console.log('utag done')});
}
mewc
fonte
1

Verifique isto:

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

Ele criará um objeto jQuery falso, que permite que você use os métodos onload do jquery, e eles serão executados assim que o jquery for carregado.

Não é perfeito.

// 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
fonte
esta é uma solução fácil para registrar funções prontas para documentos, obrigado
zanedev
0

Uma nota tangencial sobre as abordagens aqui que usam setTimeoutou de carga setInterval. Nesses casos, é possível que, quando a verificação for executada novamente, o DOM já tenha sido carregado e o DOMContentLoadedevento do navegador tenha sido disparado, portanto, você não pode detectar esse evento de forma confiável usando essas abordagens. O que descobri é que o jQuery readyainda funciona, então você pode incorporar seu

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

dentro de seu setTimeoutou setIntervale tudo deve funcionar normalmente.

Richard J
fonte