Javascript включает в себя не загрузку позже на странице
У нас есть приложение Rails, в котором мы включаем зависимости приложений в html-заголовке в application.js
:
//= require jquery
//= require analytics
// other stuff...
Затем на отдельных страницах в нижней части страницы есть тег script для analytics
:
<script>
analytics.track('on that awesome page');
</script>
Это нормально работает, но очень редко мы видим ошибку analytics is not defined
, совсем недавно в Chrome 43. Поскольку все должно быть загружено синхронно, похоже, что это должно работать из коробки, но я изменил script to:
<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'
}
});
Update:
Мы все еще видим это на производстве изредка. Мы также видели это в случае, когда мы загружаем script до application.js
, а затем ссылаемся на него:
javascript_include_tag 'mathjs'
javascript_include_tag 'application'
Каждый так часто мы видим ошибку math is not defined
. Мне интересно, произошла ли ошибка при загрузке mathjs
или других скриптов, которые не позволяют загружать ее, но тот факт, что это происходит во многих разных библиотеках и поэтому редко, кажется менее вероятным. Мы проверили некоторые отладочные проверки, чтобы узнать, полностью ли загружена наша application.js
, и она часто не кажется, даже если вы получаете доступ к чему-то вроде JQuery позже на странице.
Одна из причин заключалась в том, чтобы избежать уведомления старых браузеров о запущенных сценариях слишком долго, но мы можем просто отказаться от них и потянуть все это на application.js
, чтобы избежать ошибок.
Ответы
Ответ 1
Мы по-прежнему видим эту ошибку несколько раз в день и не получили окончательного ответа на то, что вызывает ее. Мое лучшее предположение заключается в том, что либо предыдущие сценарии тайм-аут загрузки, либо пользователь быстро перемещается между ссылками, что предотвращает загрузку страницы полностью. Они также могут просто использовать кнопку "Назад", вызывающую странное поведение в сценариях на странице.
Ответ 2
Это может произойти, если вы не дождитесь загрузки script, который определяет analytics
, или если вы не определяете порядок загрузки файлов javascript. Убедитесь, что script, который определяет analytics
, всегда загружается, прежде чем пытаться вызвать его метод track
. В зависимости от вашей установки скрипты могут загружаться в случайном порядке, что приводит к непредсказуемому поведению.
Вы пытались убедиться, что все загружено, но слушатель $(document).ready(function () {});
просто гарантирует, что DOM готов, а не доступна аналитика. И здесь у вас такая же проблема. $
- это просто jQuery, поэтому $ is not defined
означает, что jQuery еще не загружен. Вероятно, ваш script появился до загрузки jQuery и попытался вызвать то, что еще не определено.
Ответ 3
Основа вашей проблемы, вероятно, заключается в вашем предположении:
все должно быть загружено синхронно
Все решительно не загружается синхронно. Протокол HTTP 1.1 поддерживает piplining и из-за закодированное кодирование передачи ваши объекты, на которые ссылаются, могут или не могут выполнить загрузку до того, как ваша основная веб-страница завершит загрузку.
Все это происходит асинхронно, и вы не можете гарантировать порядок их загрузки. Вот почему браузеры делают несколько параллельных подключений к вашему веб-серверу при загрузке одной веб-страницы. Поскольку javascript и jQuery управляются событиями, они по своей природе асинхронны которые могут запутаться, если вы не хорошо понимаете это поведение.
Сочетание вашей проблемы заключается в том, что документ загружает JavaScript-событие (помните, jQuery только расширяет JavaScript) вызывается, когда DOM готов, который может быть до изображений и другого внешнего содержимого. И да, этот внешний контент может включать вашу ссылку на jquery.js script. Если это загрузится после DOM, вы увидите ошибку" $не определена". Поскольку связанный script еще не загружен, селектор jQuery undefined. Аналогично, с вашими другими связанными библиотеками.
Попробуйте вместо этого использовать $(window).load(). Это должно работать, когда все ссылочные объекты и загружены DOM.
Ответ 4
Поскольку сценарии имеют тенденцию загружать произвольные заказы, вы можете заставить свою аналитику script загрузить после того, как все будет готово.
Существуют различные подходы к этой задаче.
Скалы HTML5 дали довольно хороший фрагмент для такого рода вещей.
Кроме того, в зависимости от ваших потребностей вы можете использовать загрузчик модуля, например require.js. Использование загрузки promises и Require.js довольно сладкое тоже. Как правило, вы используете много JavaScript, загрузчик модулей поможет вам в этом беспорядке.
Я сохранил самый уродливый и наименее эффективный, еще один надежный подход к концу.
Ожидание загрузки внешней библиотеки может создавать огромные узкие места, и их следует рассматривать и тщательно обрабатывать. Вывод аналитики script составляет 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);
}
Взгляните на этот пример, когда я загружаю jQuery динамически с помощью функции обещания при загрузке script: 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");
}
Ответ 5
Этот код загрузит каждый URL script, помещенный в 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=""
.
Большинство проблем исходят из них, потому что они говорят браузеру выполнить script позже.
Они также могут быть в неправильном порядке.
Ответ 6
Как описывали другие, вы загружаете скрипты, а затем пытаетесь выполнить код до того, как ваши скрипты завершили загрузку. Поскольку это временная прерывистая ошибка вокруг относительно небольшого количества кода, расположенного в вашем 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
лучше подходят для этого. Проблема в том, что эти события могут быть уже запущены к моменту выполнения script, и вам придется написать резервный код для этих сценариев. Чтобы не писать 10 строк кода для выполнения 1 строки, я предложил простой, неинвазивный, быстрый и грязный, обязательно работая над каждым решением опроса браузера. Но чтобы не получить больше downvotes, здесь более правильное утомительное и сложное решение, если у вас есть говядина с опросом:
<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>
Ответ 7
У этого есть классное решение. Правильно загрузите 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.
Надеюсь, это решит вашу проблему.