Javascript 包括不在页面稍后加载
Javascript includes not loading for later in the page
我们有一个 Rails 应用程序,我们在 application.js
:
的 html 头中包含我们的应用程序依赖项
//= 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
中出现了问题。还有其他想法为什么它可能会破裂吗?您可以查看示例页面 here.
完整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
以避免错误。
如果您不等待定义 analytics
的脚本加载,或者如果您没有定义 javascript 文件的加载顺序,就会发生这种情况。在尝试调用其方法 track
之前,请确保始终加载定义 analytics
的脚本。根据您的设置,脚本可能会以随机顺序加载,从而导致这种不可预测的行为。
您试图确保所有内容都已加载,但侦听器 $(document).ready(function () {});
只是确保 DOM 已准备就绪,而不是分析可用。在这里你有同样的问题。 $
只是 jQuery 所以 $ is not defined
意味着 jQuery 还没有被加载。因此,您的脚本可能在 jQuery 加载之前出现并试图调用尚未定义的内容。
由于脚本往往以随机顺序加载,您可以在一切准备就绪后强制加载分析脚本。
这个任务有多种方法。
HTML5 rocks 对这类东西给出了很好的 snippet。
此外,根据您的需要,您可以使用像 require.js. Using loading promises and Require.js is pretty sweet too 这样的模块加载器。通常,你使用很多 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");
}
它有一个很酷的解决方案。为您的应用程序正确加载 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 文件。
希望这会解决您的问题。
你的问题的基础可能在于你的假设:
everything should be loaded synchronously
一切都绝对不是同步加载。 HTTP 1.1 协议支持 piplining and due to chunked transfer encoding,您的引用对象可能会或可能不会在您的主网页完成加载之前完成加载。
所有这些都是异步发生的,您无法保证它们的加载顺序。这就是为什么 browsers make multiple parallel connections to your web server when loading a single webpage. 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 只是扩展了 JavaScript)"is called when the DOM is ready which can be prior to images and other external content is loaded." 是的,这个外部内容可以包括您的link 到 jquery.js 脚本。如果在 DOM 之后加载,您将看到错误“$ is not defined”。因为尚未加载 linked 脚本,所以 jquery 选择器未定义。同样,与您的其他 linked 图书馆。
请尝试使用 $(window).load()。当所有引用的对象 and DOM 已加载时,这应该有效。
此代码将按准确顺序加载 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=""
属性。
大多数问题都来自这些,因为它们告诉浏览器稍后执行脚本。
它们也可能只是顺序错误。
正如其他人所描述的,您正在加载脚本,然后在脚本完成加载之前尝试执行代码。由于这是一个 偶尔间歇性 错误,围绕着您 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>
我们每天仍会多次看到此错误,但尚未就导致此错误的原因得出任何结论性的答案。我最好的猜测是早期的脚本下载超时,或者用户在链接之间快速导航,导致页面无法完全加载。他们也可能只是使用后退按钮导致页面上的脚本出现奇怪的行为。
我们有一个 Rails 应用程序,我们在 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
中出现了问题。还有其他想法为什么它可能会破裂吗?您可以查看示例页面 here.
完整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
以避免错误。
如果您不等待定义 analytics
的脚本加载,或者如果您没有定义 javascript 文件的加载顺序,就会发生这种情况。在尝试调用其方法 track
之前,请确保始终加载定义 analytics
的脚本。根据您的设置,脚本可能会以随机顺序加载,从而导致这种不可预测的行为。
您试图确保所有内容都已加载,但侦听器 $(document).ready(function () {});
只是确保 DOM 已准备就绪,而不是分析可用。在这里你有同样的问题。 $
只是 jQuery 所以 $ is not defined
意味着 jQuery 还没有被加载。因此,您的脚本可能在 jQuery 加载之前出现并试图调用尚未定义的内容。
由于脚本往往以随机顺序加载,您可以在一切准备就绪后强制加载分析脚本。
这个任务有多种方法。
HTML5 rocks 对这类东西给出了很好的 snippet。 此外,根据您的需要,您可以使用像 require.js. Using loading promises and Require.js is pretty sweet too 这样的模块加载器。通常,你使用很多 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");
}
它有一个很酷的解决方案。为您的应用程序正确加载 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 文件。 希望这会解决您的问题。
你的问题的基础可能在于你的假设:
everything should be loaded synchronously
一切都绝对不是同步加载。 HTTP 1.1 协议支持 piplining and due to chunked transfer encoding,您的引用对象可能会或可能不会在您的主网页完成加载之前完成加载。
所有这些都是异步发生的,您无法保证它们的加载顺序。这就是为什么 browsers make multiple parallel connections to your web server when loading a single webpage. 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 只是扩展了 JavaScript)"is called when the DOM is ready which can be prior to images and other external content is loaded." 是的,这个外部内容可以包括您的link 到 jquery.js 脚本。如果在 DOM 之后加载,您将看到错误“$ is not defined”。因为尚未加载 linked 脚本,所以 jquery 选择器未定义。同样,与您的其他 linked 图书馆。
请尝试使用 $(window).load()。当所有引用的对象 and DOM 已加载时,这应该有效。
此代码将按准确顺序加载 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=""
属性。
大多数问题都来自这些,因为它们告诉浏览器稍后执行脚本。
它们也可能只是顺序错误。
正如其他人所描述的,您正在加载脚本,然后在脚本完成加载之前尝试执行代码。由于这是一个 偶尔间歇性 错误,围绕着您 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>
我们每天仍会多次看到此错误,但尚未就导致此错误的原因得出任何结论性的答案。我最好的猜测是早期的脚本下载超时,或者用户在链接之间快速导航,导致页面无法完全加载。他们也可能只是使用后退按钮导致页面上的脚本出现奇怪的行为。