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


我们有一个Rails应用程序,我们将应用程序依赖项包含在html头中 application.js

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

然后在单个页面上,我们在页面底部有一个脚本标记 analytics

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

这通常很好,但偶尔我们会看到错误 analytics is not defined,最近在Chrome 43上。由于所有内容都应该同步加载,这似乎应该是开箱即用,但我将脚本更改为:

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

而现在我们偶尔会看到 $ is not defined 代替。我们没有看到来自同一IP的任何其他错误,否则我会怀疑出现了问题 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'
  }
});

更新:

我们偶尔会看到这种情况。在我们之前加载脚本的情况下,我们也看到了它 application.js 然后在以下内容中引用它:

javascript_include_tag 'mathjs'
javascript_include_tag 'application'

我们经常看到一个 math is not defined 错误。我想知道在加载过程中是否发生错误 mathjs 或者其他脚本阻止它被加载,但事实上它发生在很多不同的库上,并且很少发生,这使它看起来不太可能。我们确实进行了一些调试检查以确定是否我们的 application.js 是完全加载的,它通常似乎不是,即使在页面后面访问像Jquery这样的东西。

这样做的一个动机是避免关于脚本运行时间过长的旧浏览器通知,但我们可能只是放弃并将其全部拉入 application.js 避免错误。


12285
2018-06-23 19:14


起源

我从未遇到过这样的问题,但我能想到的最简单的解决方案是添加一个do {} while;在$(文件).ready之前。根据那个小提琴: jsfiddle.net/295v5p8j (现在只测试它)在jQuery被定义之前,这将什么也不做,也许以这种方式可以防止它,尽管它应该同步加载所有内容? - briosheje
您是否拥有加载脚本的非缩小源 analytics? - light
@briosheje问题是如果此时没有加载jquery / analytics,它可能永远不会加载。然后我们的用户将无法在页面上执行任何操作。 - lobati
@light执行所有需求的脚本是一个rails application.js。我会在问题中添加完整的内容。 - lobati
当'$ is undefined'时,加载脚本标记时不会加载jQuery。这似乎很清楚。那么,您是否看过应用程序所服务的JavaScript的状态?是否包含jQuery?这是在生产中编译资产还是在开发中使用未编译资产? - hightempo


答案:


如果您不等待脚本加载该定义,则会发生这种情况 analytics 或者如果您没有定义加载javascript文件的顺序。确保定义的脚本 analytics 在尝试调用其方法之前始终加载 track。根据您的设置,脚本可能会以随机顺序加载,从而导致这种不可预测的行为。

你试图确保一切都已加载,但是听众 $(document).ready(function () {}); 只是确保DOM准备就绪,而不是分析可用。在这里你有同样的问题。 $ 只是jQuery所以 $ is not defined 意味着尚未加载jQuery。所以可能你的脚本在加载jQuery之前出现并试图调用尚未定义的内容。


7
2018-06-23 19:22



但我们正在加载 analytics/jQuery 在html头中同步依赖,所以在点击这个脚本标记之前不应该完全加载它们吗? - lobati
这取决于你如何加载它。但是没有理由没有任何事情发生,而且有时候没有定义$表示$尚未加载。我只能推测原因可能是什么,这取决于您的设置和代码。 - Ole Spaarmann
你能告诉我有关我的具体设置吗?是否有我没有给你的细节会有所帮助? - lobati
这个问题就此而言 可能会帮助你。您必须查看RoR文档,尤其是require_tree。这可能会解决您的问题。如果在发生错误之前没有加载页面,然后检查包含js文件的订单的HTML源代码,如果所有文件都加载没有任何错误,还要检查Chrome开发工具中的网络选项卡。 - Ole Spaarmann
我们没有 require_tree . 在我们的 application.js,即使我们这样做了,它仍然应该在文档正文中的脚本标记之前加载。 - lobati


你的问题的基础可能在于你的假设:

一切都应该同步加载

一切都是最明确的  同步加载。 HTTP 1.1协议支持 piplining 由于 分块传输编码,您的引用对象可能会或可能不会在主网页加载完成之前完成加载。

所有这一切都发生了 异步 而且您不能保证它们的加载顺序。这就是为什么 浏览器与您的Web服务器建立多个并行连接 加载单个网页时。因为javascript和jQuery是事件驱动的,所以它们本质上是异步的 如果你不太了解这种行为,这可能会让人感到困惑

复杂你的问题是文件onload JavaScript事件(记住,jQuery只是扩展JavaScript) “当DOM准备好时,可以在图像和其他外部内容加载之前调用”。 是的,这个外部内容可以包含您到jquery.js脚本的链接。如果在DOM之后加载,那么您将看到错误“$ is not defined”。由于链接脚本尚未加载,因此未定义jquery选择器。同样,与您的其他链接库。

尝试使用 $(窗口).load() 代替。这应该适用于所有引用的对象  DOM已加载。


2
2018-06-13 20:06



我认为你将下载顺序与加载顺序混为一谈。是的,我可以相信浏览器可以异步下载资源,但实际上默认情况下异步执行js代码是没有意义的。它将无视单独的脚本标记。更多相关信息: stackoverflow.com/a/8996894/372479 同样,文档加载与窗口加载无关,因为这里的问题是 $ is not defined。无论哪种方式,我们都得到相同的错误。 - lobati
它是 比这复杂得多。 jQuery网站甚至说明了这一点 “在文档准备就绪之前,页面无法安全操作。”。他们还建议使用$(window).load()事件而不是$(document).ready(),如果你需要确保所有对象,而不仅仅是DOM被加载。具有单独的脚本标记,以便1)提供抽象,2)允许来自多个URL的脚本,而不仅仅是确保加载顺序。 - James Shewey
我更新了我的答案来解释为什么$未定义,但最终,无论理论如何,证据都在布丁中。 $(window).load()能解决这个问题吗? - James Shewey
它没有解决它。我们都试过了。打电话给 $ 立即执行任何一种方式。不管你是否正在做 $(window).load(callback) 要么 $(document).ready(callback), $() 函数立即运行。唯一的区别是什么时候 callback 跑了。我们没有从回调中收到错误。我们收到了错误 $ is not defined。 - lobati
“$未定义”肯定意味着您在加载jquery.js脚本的链接时遇到某种错误,但此时我不清楚它可能是什么。 - James Shewey


此代码将加载放入的每个脚本URL libs 按照确切的顺序,在添加下一个之前等待一个完全加载。 它不像让浏览器那样优化,但它允许您监视错误并强制加载顺序。

(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();
})();

但我会建议你检查每一个 <script> 您网站的标记,看看他们是否有 defer="" 要么 async="" 属性。 大多数问题都来自这些,因为它们告诉浏览器稍后执行脚本。 它们也可能只是错误的顺序。


1
2018-06-14 12:37





由于脚本倾向于以随机顺序加载,因此您可以强制分析脚本在一切准备就绪后加载。

这项任务有各种方法。

HTML5摇滚给了一个相当不错的 片段 对于这种东西。 此外,根据您的需要,您可以使用模块加载器 require.js。使用加载promises和Require.js是 太可爱了。通常,您使用大量的JavaScript,模块加载器将帮助您解决这个问题。

我保持最丑陋,效率最低,最终仍然是坚实的方法。 等待外部库加载可能会产生巨大的瓶颈,应该考虑并且也要非常小心地处理。分析抓取脚本是 async 而且真的很难处理。在这些情况下,我更喜欢

加载外部延迟脚本在某种程度上很简单:

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

看看这个示例,我在加载脚本时使用promise函数动态加载jQuery: http://jsfiddle.net/4mtyu/487/

这是一个按顺序加载Angular和jQuery的链接演示:http://jsfiddle.net/4mtyu/488/ 

考虑上面的示例,您可以将分析加载为:

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");
}

1
2017-07-03 22:25



问题是,这些脚本都不是从外部源加载或异步加载的。 jQuery与我们的服务器上的应用程序捆绑在一起,分析也是如此。 - lobati
setTimeout没有帮助,因为你不知道加载文件需要多少次。 - Kulvar
@Kulvar这是 setInterval() - vorillaz
不要改变太多。你在两种情况下都浪费时间。你有 load 和 error 事件,以了解何时检索脚本或无法检索脚本^ _ ^ - Kulvar
如果jquery在你的服务器上是否真的没关系,如果它不是页面/代码的内联,那么它独立于页面,因此是异步的。只要你有 <script src="... 这至少在某种程度上是异步的,并且将根据网络延迟/带宽/ QOS,文件加载的大小以及当前正在使用的连接/管道的数量以及待处理/排队的请求数量来完成。 - James Shewey


正如其他人所描述的那样,您正在加载脚本,然后在脚本加载完成之前尝试执行代码。因为这是一个 偶尔间歇性的 围绕位于HTML中的相对少量代码的错误,您可以通过一些简单的轮询来修复它:

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

...同样可以用于其他库,希望你看到模式:

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

编辑: 正如评论中指出的那样 load 事件会更适合这个。问题是这些事件可能在脚本执行时已经触发,您必须为这些场景编写回退代码。我不赞成不写10行代码来执行1行,我建议一个简单,非侵入性,快速和脏,确保在每个浏览器轮询解决方案上工作。但是为了不再获得任何投票,这里是一个 更合适 繁琐而复杂的解决方案,以防你有一些牛肉轮询:

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

0
2018-06-14 18:21



load 和 error 事件比 setTimeout 要么 setInterval 知道它是否成功或无法检索脚本。 - Kulvar
你是对的 - 有十几件比投票更好的事情。但如果一个 <script /> 从内部开始已经加载或错误的页面顶部 <script /> 在页面底部执行,内联脚本无法知道 - 事件已经通过。因此,轮询是下一个最佳选择。 FWIW - 我的公司构建并维护一个广泛使用的开源模块加载器 - 我很清楚 更好 选项。 - Ryan Wheale
或者您可以使用javascript创建脚本节点,然后使用事件监视其状态。使用Promise或其他东西,您也可以处理依赖性。 ^ _ ^ - Kulvar


我们仍然每天都会看到这个错误几次,并且没有得到任何结论性的答案。我最好的猜测是,早期的脚本超时下载,或者用户在链接之间快速导航,阻止页面完全加载。他们也可能只是使用后退按钮导致页面上的脚本出现奇怪的行为。


0
2018-03-06 04:43





它有一个很酷的解决方案。为您的应用程序正确加载js。在这里,我首先加载jQuery然后加载analytics.js。我希望这将解决您支持html5的浏览器的问题。 码:

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

此代码段将首先加载jQuery,然后加载analytics.js文件。 希望这将解决您的问题。


-1
2018-06-12 22:50



这似乎与仅在头部首先使用脚本标签有很大不同。默认情况下,脚本标签是同步的: developer.mozilla.org/en-US/docs/Web/HTML/Element/script - lobati