简体   繁体   English

Javascript包含以后不在页面中加载

[英]Javascript includes not loading for later in the page

We have a Rails application where we are including our application dependencies in the html head within application.js : 我们有一个Rails应用程序,我们都包括内部的HTML头我们的应用程序相关application.js

//= require jquery
//= require analytics
// other stuff...

Then on individual pages we have a script tag at the bottom of the page for analytics : 然后在单个页面上,我们在页面底部有一个脚本标记用于analytics

<script>
  analytics.track('on that awesome page');
</script>

This normally works fine, but very occasionally we see the error analytics is not defined , most recently on Chrome 43. Because everything should be loaded synchronously, this seems like it ought to work out of the box, but I changed the script to: 这通常很好,但偶尔我们会看到错误analytics is not defined ,最近才在Chrome 43上analytics is not defined 。因为所有内容都应该同步加载,这似乎应该是开箱即用的,但我将脚本更改为:

<script>
  $(document).ready(function () {
    analytics.track('on that awesome page');
  });
</script>

And now instead every once in a while we see $ is not defined instead. 而现在我们偶尔会看到$ is not defined We don't see any other errors from the same IP, otherwise I would suspect something went wrong in application.js . 我们没有看到来自同一IP的任何其他错误,否则我会怀疑application.js Any other ideas why it might break? 任何其他想法为什么它可能会破裂? You can see an example page here . 您可以在此处查看示例页面。

The full application.js : 完整的application.js

// Polyfills
//= require es5-shim/es5-shim
//= require es5-shim/es5-sham
//= require polyfills
//
// Third party plugins
//= require isMobile/isMobile
//= require jquery
//
//= require jquery.ui.autocomplete
//= require jquery.ui.dialog
//= require jquery.ui.draggable
//= require jquery.ui.droppable
//= require jquery.ui.effect-fade
//= require jquery.ui.effect-slide
//= require jquery.ui.resizable
//= require jquery.ui.tooltip
//
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backbone-sortable-collection
//= require bootstrap
//= require load-image
//= require react
//= require react_ujs
//= require classnames
//= require routie
//= require mathjs
//= require moment
//= require stink-bomb
//= require analytics
//
// Our code
//= require_self
//= require extensions
//= require extend
//= require models
//= require collections
//= require constants
//= require templates
//= require mixins
//= require helpers
//= require singletons
//= require actions
//
//= require object
//= require components
//= require form_filler
//= require campaigns
//= require form_requests
//= require group_wizard
//= require step_adder

Chalk = {};
underscore = _;

_.templateSettings = {
  evaluate:    /\{\{(.+?)\}\}/g,
  interpolate: /\{\{=(.+?)\}\}/g,
  escape:      /\{\{-(.+?)\}\}/g
};

moment.locale('en', {
  calendar: {
    lastDay: '[Yesterday at] LT',
    sameDay: '[Today at] LT',
    nextDay: '[Tomorrow at] LT',
    lastWeek: 'dddd [at] LT',
    nextWeek: '[Next] dddd [at] LT',
    sameElse: 'L LT'
  }
});

Update: 更新:

We're still seeing this on production occasionally. 我们偶尔会看到这种情况。 We've also seen it in a case where we load a script before application.js and then reference it within: 我们也看到过在我们在application.js之前加载脚本然后在其中引用它的情况:

javascript_include_tag 'mathjs'
javascript_include_tag 'application'

Every so often we see a math is not defined error. 我们经常看到math is not defined错误。 I'm wondering if an error happens during the loading of mathjs or other scripts preventing it from being loaded, but the fact that it happens on so many different libraries, and so infrequently, makes it seem less likely. 我想知道在加载mathjs或其他脚本期间是否发生错误以防止它被加载,但事实上它发生在很多不同的库上,并且很少发生,这使得它看起来不太可能。 We did put in some debug checks to see whether our application.js is fully loaded and it often doesn't seem to be, even if accessing something like Jquery later in the page. 我们确实进行了一些调试检查,以查看我们的application.js是否已完全加载,而且通常似乎不是,即使稍后在页面中访问类似Jquery的东西。

One motivation in this was to avoid old browser notifications about scripts running too long, but we may just give up and pull it all into application.js to avoid the errors. 这样做的一个动机是避免关于脚本运行时间过长的旧浏览器通知,但我们可能会放弃并将其全部放入application.js以避免错误。

This can happen if you don't wait for the script to load that defines analytics or if you do not define the order in which the javascript files are loaded. 如果您不等待加载定义analytics的脚本,或者您没有定义加载javascript文件的顺序,则会发生这种情况。 Make sure that the script that defines analytics is always loaded before you try to call its method track . 在尝试调用其方法track之前,请确保始终加载定义analytics的脚本。 Depending on your setup the scripts could load in random order, leading to this unpredictable behavior. 根据您的设置,脚本可能以随机顺序加载,从而导致这种不可预测的行为。

You tried to make sure everything was loaded, but the listener $(document).ready(function () {}); 你试图确保所有内容$(document).ready(function () {});加载,但是监听器$(document).ready(function () {}); just makes sure that the DOM is ready, not that analytics is available. 只是确保DOM准备就绪,而不是分析可用。 And here you have the same problem. 在这里你有同样的问题。 $ is just jQuery so $ is not defined means jQuery hasn't been loaded yet. $只是jQuery所以$ is not defined意味着jQuery尚未加载。 So probably your script came before jQuery was loaded and tried to call what wasn't defined yet. 所以可能你的脚本在jQuery加载之前出现并试图调用尚未定义的内容。

The basis of your problem probably lies in your assumption: 你的问题的基础可能在于你的假设:

everything should be loaded synchronously 一切都应该同步加载

Everything is most decidedly not loaded synchronously. 一切都绝对不是同步加载的。 The HTTP 1.1 protocol supports piplining and due to chunked transfer encoding , your referenced objects may or may not complete loading before your main webpage has finished loading. HTTP 1.1协议支持piplining,并且由于分块传输编码 ,在您的主网页加载完成之前,您引用的对象可能会也可能不会完成加载。

All of this happens asynchronously and you can't guarantee the order in which they are loaded. 所有这些都是异步发生的,您不能保证它们的加载顺序。 This is why browsers make multiple parallel connections to your web server when loading a single webpage. 这就是为什么浏览器在加载单个网页时与Web服务器建立多个并行连接的原因。 Because javascript and jQuery are event driven, they are by nature asynchronous which can become confusing if you don't understand that behavior well . 因为javascript和jQuery是事件驱动的,所以它们本质上是异步的,如果你不能很好地理解这种行为,它会变得混乱

Compounding your problem is the fact that document onload JavaScript event (remember, jQuery just extends JavaScript) "is called when the DOM is ready which can be prior to images and other external content is loaded." 使问题更加复杂的是,文件onload JavaScript事件(请记住,jQuery只是扩展JavaScript) “在DOM准备就绪时调用,可以在图像和其他外部内容加载之前调用。” And yes, this external content can include your link to the jquery.js script. 是的,这个外部内容可以包含您到jquery.js脚本的链接。 If that loads after the DOM, then you will see the error "$ is not defined". 如果在DOM之后加载,那么您将看到错误“$ is not defined”。 Because the linked script has not yet loaded, the jquery selector is undefined. 由于链接脚本尚未加载,因此未定义jquery选择器。 Likewise, with your other linked libraries. 同样,与您的其他链接库。

Try using $(window).load() instead. 尝试使用$(window).load()代替。 This should work when all the referenced objects and the DOM has loaded. 这应该在所有引用的对象 DOM都已加载时起作用。

Since scripts tend to load at random orders you may force your analytics script to load after everything is up and ready. 由于脚本倾向于以随机顺序加载,因此您可以强制分析脚本在一切准备就绪后加载。

There are various approaches for this task. 这项任务有各种方法。

HTML5 rocks has given a quite nice snippet for this kind of stuff. HTML5 rock为这类东西提供了一个非常好的片段 Moreover, depending on your needs you may use a module loader like require.js . 此外,根据您的需要,您可以使用像require.js这样的模块加载器。 Using loading promises and Require.js is pretty sweet too . 使用加载promises和Require.js也很可爱 Generally, you use a lot of JavaScript a module loader will help you with this mess. 通常,您使用大量的JavaScript模块加载器将帮助您解决这个问题。

I kept the ugliest and least efficient, still one solid approach for the end. 我保持了最丑陋,效率最低,最终仍然坚实的方法。 Waiting for an external library to load may create huge bottlenecks and should be considered and also handled really carefully. 等待外部库加载可能会产生巨大的瓶颈,应该考虑并且也要非常小心地处理。 Analytics fetching script is async and really hard to handle. 分析抓取脚本是async并且很难处理。 On these cases I prefer 在这些情况下,我更喜欢

Loading external deferred scripts is somehow easy: 加载外部延迟脚本在某种程度上很简单:

function loadExtScript(src, test, callback) {
  var s = document.createElement('script');
  s.src = src;
  document.body.appendChild(s);

  var callbackTimer = setInterval(function() {
    var call = false;
    try {
      call = test.call();
    } catch (e) {}

    if (call) {
      clearInterval(callbackTimer);
      callback.call();
    }
  }, 100);
}

Take a look at this example where I am loading jQuery dynamically with a promise function when the script is loaded: http://jsfiddle.net/4mtyu/487/ 看一下这个例子,我在加载脚本时用promise函数动态加载jQuery: http//jsfiddle.net/4mtyu/487/

Here is a chaining demo loading Angular and jQuery in order : http://jsfiddle.net/4mtyu/488/ 这是一个按顺序加载Angular和jQuery的链接演示: http//jsfiddle.net/4mtyu/488/

Considering the example above you may load your analytics as: 考虑上面的示例,您可以将分析加载为:

loadExtScript('https://code.jquery.com/jquery-2.1.4.js', function () {
    return (typeof jQuery == 'function');
}, jQueryLoaded);

function jQueryLoaded() {
    loadExtScript('https://analytics-lib.com/script.js', function () {
        return (typeof jQuery == 'function');
    }, analyticsLoadedToo);
}

function analyticsLoadedToo() {
    //cool we are up and running
    console.log("ready");
}

This code will load each script URL put in libs in the exact order, waiting for one to fully load before adding the next one. 此代码将按照确切的顺序加载放在libs中的每个脚本URL,等待一个完全加载,然后再添加下一个。 It's not as optimised than letting the browser doing it, but it allow you to monitor the errors and force the order of the loading. 它不像让浏览器那样优化,但它允许您监视错误并强制加载顺序。

(function(){
    var libs = [
        "http://example.com/jquery.js",
        "http://example.com/tracker.js",
        "http://example.com/myscript.js"
    ];
    var handle_error = function() {
        console.log("unable to load:", this.src);
    };
    var load_next_lib = function() {
        if(libs.length) {
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.src = libs.shift();
            script.addEventListener("load", load_next_lib);
            script.addEventListener("error", handle_error);
            document.body.appendChild(script);
        }
    };
    load_next_lib();
})();

But I would advise you to check every <script> tag of your website and see if they have a defer="" or async="" attribute. 但我会建议您检查您网站的每个<script>标记,看看它们是否具有defer=""async=""属性。 Most issues come from these because they tell the browser to execute the script later. 大多数问题都来自这些,因为它们告诉浏览器稍后执行脚本。 They may also just be in the wrong order. 他们也可能只是错误的顺序。

As others have described, you are loading scripts and then trying to execute code before your scripts have finished loading. 正如其他人所描述的那样,您正在加载脚本,然后在脚本加载完成之前尝试执行代码。 Since this is an occasionally intermittent error around a relatively small amount of code located in your HTML, you can fix it with some simple polling: 由于这是围绕HTML中相对少量代码的偶尔间歇性错误,您可以通过一些简单的轮询来修复它:

(function runAnalytics () {
  if (typeof analytics === 'undefined') {
    return setTimeout(runAnalytics, 100);
  }
  analytics.track('on that awesome page');
}());

... the same can be used for other libraries, hopefully you see the pattern: ...同样可以用于其他库,希望你看到模式:

(function runJQuery () {
  if (typeof $ === 'undefined') {
    return setTimeout(runJQuery, 100);
  }
  $(document).ready(...);
}());

Edit: As pointed out in the comments, the load events would be better suited for this. 编辑:正如评论中所指出的, load事件将更适合于此。 The problem is that those events might have already fired by the time the script executes and you have to write fallback code for those scenarios. 问题是这些事件可能在脚本执行时已经触发,您必须为这些场景编写回退代码。 In favor of not writing 10+ lines of code to execute 1 line, I suggested a simple, non-invasive, quick and dirty, sure to work on every browser polling solution. 为了不写10行以上的代码来执行1行,我提出了一个简单的,非侵入性的,快速和脏的,肯定会在每个浏览器轮询解决方案上工作。 But in order to not get any more downvotes, here's a more proper tedious and over complicated solution in case you have some beef with polling: 但是为了不再获得任何投票,这里有一个更合适的繁琐和过于复杂的解决方案,以防你有一些牛肉投票:

<script>
// you can reuse this function
function waitForScript (src, callback) {
  var scripts = document.getElementsByTagName('script');
  for(var i = 0, l = scripts.length; i < l; i++) {
    if (scripts[i].src.indexOf(src) > -1) {
      break;
    }
  }
  if (i < l) {
    scripts[i].onload = callback;
  }
}

function runAnalytics () {
  analytics.track('on that awesome page');
}

if (typeof analytics === 'undefined') {
  waitForScript('analytics.js', runAnalytics);
} else {
  runAnalytics();
}

function runJQuery () {
  $(document).ready(...);
}

if (typeof $ === 'undefined') {
  waitForScript('jquery.min.js', runJQuery);
} else {
  runJQuery();
}
</script>

We're still seeing this error a few times a day and haven't reached any conclusive answer to what is causing it. 我们仍然每天都会看到这个错误几次,并且没有得到任何结论性的答案。 My best guess is that either the earlier scripts time out downloading, or a user is quickly navigating between links, preventing the page from loading completely. 我最好的猜测是,较早的脚本超时下载,或者用户在链接之间快速导航,阻止页面完全加载。 They might also just be using the back button causing weird behavior in scripts on the page. 他们也可能只是使用后退按钮在页面上的脚本中导致奇怪的行为。

It has a cool solution. 它有一个很酷的解决方案。 Make js loading correctly for your application. 为您的应用程序正确加载js。 Here, I am loading jQuery first then loading analytics.js . 在这里,我首先加载jQuery然后加载analytics.js。 I hope this will solve your problem for html5 supported browsers. 我希望这将解决您支持html5的浏览器的问题。 Code: 码:

    var fileList =[
                    'your_file_path/jQuery.js',
                    'your_file_path/analytics.js'
                  ];

    fileList.forEach(function(src) {
    var script = document.createElement('script');
    script.src = src;
    script.async = false;
    document.head.appendChild(script);
 });

This code snippet will load jQuery first and then load analytics.js file. 此代码段将首先加载jQuery,然后加载analytics.js文件。 Hopefully, this will fix your issue. 希望这能解决您的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM