Использование .ready() перед включением jQuery

В этой заметке предлагается метод, который позволяет использовать метод jQuery.ready() в любом месте документа, даже если сам jQuery подключается в самом низу.

Идея в том, чтобы переместить JavaScript код в конец HTML-документа, используя для этого сам JavaScript. Идея была взята от сюда Stop paying your jQuery tax.

Метод заключается в следующем:

  1. В head включается сценарий, который:

    • Создает массив.
    • Создает поддельную функцию $, которая перемещает аргументы вызова в этот массив.
  2. В body, после включения jQuery, ставим скрипт, который:
    • Использует jQuery для перебора содержимого нашего массива.
    • ...и вызывает настоящую функцию $, передавая сохраненные аргументы (функции).

Реализация для WordPress

Для WordPress эта идея реализовывается таким PHP кодом в файле functions.php:

// Ability to use jQuery.ready() before jQuery is enabled
// 0 priority is important in some cases
add_action( 'wp_head', 'jquery_ready_catch', 1 );
function safe_jquery_ready(){

	add_action( 'wp_footer', __FUNCTION__, PHP_INT_MAX );

	echo '<script>' .
		 (
		 doing_action( 'wp_head' )
			 ? '(function(w,d,u){w.readyQ=[];w.bindReadyQ=[];function p(x,y){if(x=="ready"){w.bindReadyQ.push(y);}'.
			   'else{w.readyQ.push(x);}};var a={ready:p,bind:p};w.$=w.jQuery=function(f){if(f===d||f===u){return a}'.
			   'else{p(f)}}})(window,document)'
			 : '(function($,d){$.each(readyQ,function(i,f){$(f)});$.each(bindReadyQ,function(i,f){$(d).bind("ready",f)})})(jQuery,document)'
		 )
		 . "</script>\n";
}

Пояснения

Рассмотрим минифицированный JS код выше в нормальном виде:

(function (w, d, u) {

	// Define two queues for handlers
	w.readyQ = [];
	w.bindReadyQ = [];

	// Push a handler into the correct queue
	function pushToQ(x, y) {
		if (x == "ready") {
			w.bindReadyQ.push(y);
		} else {
			w.readyQ.push(x);
		}
	}

	// Define an alias object (for use later)
	var alias = {
		ready: pushToQ,
		bind: pushToQ
	}

	// Define the fake jQuery function to capture handlers
	w.$ = w.jQuery = function (handler) {
		if (handler === d || handler === u) {
			// Queue $(document).ready(handler), $().ready(handler)
			// and $(document).bind("ready", handler) by returning
			// an object with alias methods for pushToQ
			return alias;
		} else {
			// Queue $(handler)
			pushToQ(handler);
		}
	}

})(window, document);

Если вы посмотрите документацию по методу jQuery.ready(), там объясняется, что если обработчики привязаны к DOM ready с помощью функции .bind(), то на самом деле они срабатывают после срабатывания всех других обработчиков. Именно поэтому у нас есть две очереди - для того, чтобы соблюсти это поведение.

Развернув сценарий body (сразу после jQuery), мы получаем:

(function ($, doc) {
	$.each(readyQ, function (index, handler) {
		$(handler);
	});
	$.each(bindReadyQ, function (index, handler) {
		$(doc).bind("ready", handler);
	});
})(jQuery, document);

Точно так же, как в примере Сэма, мы используем метод jQuery.each(), чтобы правильно связать все наши обработчики очереди с DOM ready, но поскольку $(document).bind("ready", handler) мог быть вызван раньше, мы свяжем их тоже правильным образом.

Пример

<!DOCTYPE html>
<html>
	<head>
		<title>Example</title>
		<script>
		(function(w,d,u){w.readyQ=[];w.bindReadyQ=[];
		function p(x,y){if(x=="ready"){w.bindReadyQ.push(y);}else{w.readyQ.push(x);}};
		var a={ready:p,bind:p};w.$=w.jQuery=function(f){if(f===d||f===u){
			return a}else{p(f)}}}
		)(window,document)
		</script>
	</head>
	<body>
		<script>
			$(document).bind("ready", function () {
				console.log("Example D: $(document).bind(\"ready\", handler)");
			});
			$(document).ready(function () {
				console.log("Example A: $(document).ready(handler)");
			});
			$().ready(function () {
				console.log("Example B: $().ready(handler)");
			});
			$(function(){
				console.log("Example C: $(handler)");
			});
		</script>

		<!-- HTML КОД СТРАНИЦЫ -->

		<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
		<script>
		(function($,d){$.each(readyQ,function(i,f){$(f)});
		$.each(bindReadyQ,function(i,f){$(d).bind("ready",f)})})(jQuery,document)
		</script>
	</body>
</html>

Вывод:

Example A: $(document).ready(handler)
Example B: $().ready(handler)
Example C: $(handler)
Example D: $(document).bind("ready", handler)

Обратите внимание, что хотя Example D первый, в нем используется $(document).bind("ready", handler), поэтому он ставится в очередь и выполняется после трех других примеров. Он ведет себя именно так, как задумано jQuery.

--

Оригинальная статья