在jQuery加载之前处理依赖于jQuery的代码

43
我希望遵循将所有JavaScript放在页面最底部的一般准则,以加快加载时间并解决一些令人讨厌的jQuery版本冲突问题(在Django中)。
然而,偶尔会有一些代码依赖于jQuery,但必须进一步上移(基本上该代码不能移动到底部)。
我想知道是否有一种简单的方式来编写代码,即使在定义jQuery之前,代码也能正常工作。
以下内容似乎过于复杂,但我不知道还有其他方法:
function run_my_code($) {
    // jquery-dependent code here
    $("#foo").data('bar', true);
}
var t = null;
function jquery_ready() {
    if (window.jQuery && window.jQuery.ui) {
        run_my_code(window.jQuery);
    } else {
        t = window.setTimeout(jquery_ready, 100);
    }
}
t = window.setTimeout(jquery_ready, 100);

实际上,我可能需要在页面中多次使用代码,这些代码不知道其他代码的存在,所以即使我把每个jquery_ready重命名为像jquery_ready_guid、jquery_ready_otherguid这样的东西,这也可能行不通。

澄清

只是为了明确,我将JavaScript的包含文件(<script type="text/javascript" src="jquery.min.js" />)放在页面的最底部,就在</body>之前。因此我不能使用$

11个回答

42

使用纯JavaScript实现$(document).ready();的简单方法:

document.addEventListener("DOMContentLoaded", function(event) { 
    //you can use jQuery there
});

这正是我在寻找的。我更喜欢原始的代码,但当代码不是你自己的时候,你必须适应它。 - Cleiton Oliveira

20

我所知道的只有你这种方式,不过我会确保作用域更加严格:

(function() {
  var runMyCode = function($) {
    // jquery-dependent code here
    $("#foo").data('bar', true);
  };

  var timer = function() {
    if (window.jQuery && window.jQuery.ui) {
      runMyCode(window.jQuery);
    } else {
      window.setTimeout(timer, 100);
    }
  };
  timer();
})();

更新

这是我拼凑出来的一个小延迟加载器:

var Namespace = Namespace || { };
Namespace.Deferred = function () {
  var functions = [];
  var timer = function() {
    if (window.jQuery && window.jQuery.ui) {
        while (functions.length) {
            functions.shift()(window.jQuery);
        }
    } else {
        window.setTimeout(timer, 250);
    }
  };
  timer();
  return {
    execute: function(onJQueryReady) {
        if (window.jQuery && window.jQuery.ui) {
            onJQueryReady(window.jQuery);
        } else {
            functions.push(onJQueryReady);
        }
    }
  };
}();

然后可以像这样使用:

Namespace.Deferred.execute(runMyCode);

这句话应该是typeof(jQuery) !== "undefined",而不是window.jQuery,对吗? - Ky -
在实践中,我回答中发布的内容和你的片段将是相同的(出于个人偏好),我喜欢在编写代码时使用JS的真/假性质。 - Rich O'Kelly

19
我发现最好的方法是把代码写在一个函数中,在jquery加载后调用该函数:
function RunAfterjQ(){
// Codes that uses jQuery
}

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
    RunAfterjQ();
</script>

更新:对于母版页,您可以定义一个数组,在母版页的head部分中添加函数:

var afterJQ = [];

然后在母版页的底部运行此数组中推入的所有函数:

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
    for(var i = 0; i < afterJQ.length; i++) afterJQ[i]();
</script>

无论何时需要使用依赖于 jQuery 的 JavaScript 代码,并且在定义 jQuery 之前,请将其推入此数组中:

afterJQ.push( function() { 
    // this code will execute after jQuery is loaded.
 });

4
很棒的答案!最简单、最快速且完美地发挥作用! - obreezy

10

这里有一种编写注入代码的方法,只会在jQuery加载后才运行(无论是同步还是异步)。

<script>
if ( ! window.deferAfterjQueryLoaded ) {
    window.deferAfterjQueryLoaded = [];
    Object.defineProperty(window, "$", {
        set: function(value) {
            window.setTimeout(function() {
                $.each(window.deferAfterjQueryLoaded, function(index, fn) {
                    fn();
                });
            }, 0);
            Object.defineProperty(window, "$", { value: value });
        },

        configurable: true
    });
}

window.deferAfterjQueryLoaded.push(function() {
    //... some code that needs to be run
});
</script>

它的作用是:

  1. 懒加载定义deferAfterjQueryLoaded,因此您不需要将其注入到中。
  2. 定义一个window.$setter。 当jQuery加载时,它最后执行的一件事情之一就是分配给全局变量$。 这允许您在发生这种情况时触发函数。
  3. 安排延迟的函数在jQuery脚本完成后尽快运行(setTimeout(..., 0);)。
  4. 使setter自己删除。

要完全清除,您可以使定时函数同样删除deferAfterjQueryLoaded


糟糕的解决方案。当jQuery在上面被定义时,它不会检查属性是否已经定义并覆盖它,从而使jQuery失效。代码应该检查jQuery是否就位,在这种情况下不重新定义属性,而是立即安排函数执行。 - mdzieg
@mdzieg 问题明确说明jQuery的<script>标签将是页面上的最后一条语句。请参见问题底部的“澄清”。 - radiaph

8
如何看待以下内容:
<script>
    window.deferAfterjQueryLoaded = [];
    window.deferAfterjQueryLoaded.push(function() {
        //... some code that needs to be run
    });

    // ... further down in the page

    window.deferAfterjQueryLoaded.push(function() {
        //... some other code to run
    });
</script>

<script src="jquery.js" />
<script>
    $.each(window.deferAfterjQueryLoaded, function(index, fn) {
        fn();
    });
</script>

这是因为这里的每个脚本都是完全阻塞的。这意味着首先创建deferAfterjQueryLoaded数组和所有函数,然后将它们推入该数组中。然后jQuery完全加载。然后你遍历该数组并执行每个函数。这也适用于不同文件中的脚本。
如果你还想要DOMReady触发,可以像这样在其中一个deferAfterjQueryLoaded函数中嵌套一个$(function() {})
window.deferAfterjQueryLoaded.push(function() {
    $(function() {
        console.log('jquery loaded and the DOM is ready');
    });
    console.log('jquery loaded');
});

最终,您应该重构代码,使所有内容实际上都降到底部,并拥有一个符合该模型的系统。这样可以更容易地理解所发生的一切,并且性能更好(特别是如果您有单独的脚本)。


这并不比其他方法有什么不同,但还是感谢您的回答。我的问题是我需要将一些依赖于jQuery的代码插入到一个我无法真正控制的页面上。也就是说,我可以添加一些JavaScript,但无法控制它放置的位置。更糟糕的是,jQuery是异步加载的,所以我不能简单地使用window.onload。现在我所做的是等待window.onload,然后每隔300毫秒左右检查一次jQuery对象...这是一个我想要消除的hack。如果它没有内置到jQuery中,也许这是唯一的方法。 - Brad

4

我也遇到了这个问题,但是有很多文件,我使用headjs来管理加载,它只有2kb,所以把它放在header中不会有问题。你的代码变成这样:

head.ready(function(){
  $...
});

在页面底部,

head.js('/jquery.min.js');

嗯...那可能行。但是我不太喜欢引入另一个JS依赖。 - Jordan Reiter

1
function jQueryGodot(code)
{
    if (window.jQuery)
    {
        code(window.jQuery);        
    }
    else
    {
        if (!window.$)
        {
            window.$ = { codes: [] };
            window.watch('$', function(p, defered, jQuery) {
                jQuery.each(defered.codes, function(i, code) {
                    code(jQuery);
                });
                return jQuery;
            });
        }

        window.$.codes.push(code);
    }
}

jQueryGodot(function($) {
    $('div').html('Will always work!');
})

JSFiddle上的工作示例

传递给jQueryGodot函数的代码将始终被执行,无论其是在jQuery加载前还是之后被调用。

该解决方案依赖于Object.watch,在大多数浏览器中需要此polyfill(660字节压缩):https://gist.github.com/adriengibrat/b0ee333dc1b058a22b66


很棒的解决方案。之前不知道 watch 这个命令。660字节似乎在页面顶部加载并不是什么大问题。 - Jordan Reiter
.watch() 仅适用于 Firefox:https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/watch - Frank
我在答案中提到了Object.watch polyfill。 - brother Filip

1

你应该能够在 documentready 事件中完成这个操作。


2
像这样的代码 document.addEventListener("load", function () { run_my_code(window.jQuery) }); - Jordan Reiter
看起来问题在于你只能附加一次load函数,因此我每页只能处理一个实例。可能会有多个。 - Jordan Reiter
那么为什么文档会这样说:“如果在同一EventTarget上使用相同参数注册多个相同的EventListeners,则重复实例将被丢弃。它们不会导致EventListener被调用两次,并且由于重复项已被丢弃,因此无需使用removeEventListener方法手动删除它们。” https://developer.mozilla.org/en/DOM/element.addEventListener#Multiple_identical_event_listeners - Jordan Reiter
2
第二个参数不同。 - Daniel A. White
使用on ready事件的问题在于$仍未映射到命名空间中。在Chrome中会出现JS错误。 - Lucas Holt

1
我已经编写了一个简单的 JavaScript 代码来处理这种情况:https://github.com/Yorkii/wait-for 然后你可以像这样使用它。
waitFor('jQuery', function () {
   //jQuery is loaded
   jQuery('body').addClass('done');
});

你甚至可以等待多个库

waitFor(['jQuery', 'MyAppClass'], function () {
   //Both libs are loaded
});

1
你可以像这样推迟所有调用:jQuery(function(){...}),无需使用setTimeout的循环: https://jsfiddle.net/rL1f451q/3/ 它会将每个jQuery(...)调用收集到一个数组中,直到jQuery未定义,然后第二段代码在jQuery可用时执行它们。
将此放在或的开头即可。
<!-- jQuery defer code body: deferring jQuery calls until jQuery is loaded -->
<script>
  window.jQueryQ = window.jQueryQ || [];
  window.$ = window.jQuery = function(){
    window.jQueryQ.push(arguments);
  }
</script>
<!-- end: jQuery defer code body -->

在整个文档的最后,jQuery脚本之后,放置此内容:
<!-- jQuery deferring code footer: add this to the end of body and after the jQuery code -->
<script>
  jQuery(function(){
    jQuery.each(window.jQueryQ||[],function(i,a){
      // to understand why setTimeout 0 is useful, see: https://www.youtube.com/watch?v=8aGhZQkoFbQ, tldr: having a lot of calls wont freeze the website
      setTimeout(function(){
        jQuery.apply(this,a);
      },0);
    });
  });
</script>
<!-- end: jQuery deferring code footer -->

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接