jQuery однозначно стал стандартом в индустрии веб-дева. Есть много отличных js-фреймворков, которые заслуживают внимания, но jQuery поразил всех своей лёгкостью, изящностью, магией. Люди пишут с использованием jQuery, люди пишут плагины для jQuery, люди даже пишут статьи про jQuery, но мало кто знает (особенно из новичков), КАК устроен jQuery.
В этой статье проведем небольшой экскурс во внутренности этого фреймворка и разберем, что внутри.
Статья рассчитана на базовые знания Javascript. Задумайтесь и, если вы знаете, как написать клон jQuery, то, скорее всего, вы тут не найдёте ничего нового. Остальным — добро пожаловать под кат
Общие сведения
jQuery — это Javascript-библиотека.
Официальный сайт — jquery.com, автор — John Resig, aka jeresig, известный гуру и бывший евангелист Javascript в Mozilla Corporation. У него есть свой блог — ejohn.org, где он написал кучу крутых статей и либа для работы с Canvas — processing.js, а также книга «JavaScript. Профессиональные приёмы программирования». Находится в Зале Славы RIT
Основной jQuery-репозиторий располагается на GitHub, где лежат исходники, unit-тесты, сборщик, js-lint проверялка и т.д.
В этот момент я хотел бы сделать отступление и обратить внимание на GitHub. Огромное количество OpenSource Javascript либ — prototype.js, MooTools, node.js, jQuery, Raphael, LibCanvas, YUI а также значительная часть Javascript (и не только Javascript) сообщества нашли приют там, потому, если вы хотите выложить свой javascript-проект, GitHub — лучшее место.
В директории /src находятся исходники, разбитые на множество файлов. Если вы смотрели на файл code.jquery.com/jquery-*.js и ужасались, как там можно не запутаться, то знайте — всё структурировано и не так ужасно. Собираются при помощи билдера на node.js. В нём строки "@VERSION" и "@DATE" исходника заменяются на соответсвующие значения.
Углубляемся в исходники
Coding styles весьма привычные и обычные. Порадую или огорчу вас. Используются табы и египетские скобки. Отбиваются только # indentation, alignment не используется нигде.
Есть два файла — intro.js и outro.js, которые ставятся в начало и конец собранного исходника соответственно.
(function( window, undefined ) {
var document = window.document,
navigator = window.navigator,
location = window.location;
[...] // Основные исходники тут
window.jQuery = window.$ = jQuery;
})(window);
Core
Основной интерес для нас представляет файл core.js, в котором и находится всё «мясо».
Исходник выглядит так. Мы видим, что код опустился ещё на один уровень вложенности, что позволяет легче контролировать область видимости переменных:
var jQuery = (function () {
var jQuery = function ( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
};
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$,
// A central reference to the root jQuery(document)
rootjQuery,
[...]
rootjQuery = jQuery(document);
[...]
return jQuery;
})();
В скопированном участке можно увидеть конструктор jQuery-объекта, сохранённые текущие значения jQuery и $ (понадобятся далее для того, чтобы реализовать
jQuery.noConflict()
) а также некий rootjQuery — объект jQuery с ссылкой на document ( кеш часто встречаемого $(document)
, оптимизация )Чуть ниже — серия RegExp'ов, которые необходимы для реализации jQuery.browser, jQuery.trim, парсинга json и т.п. Современные браузеры подерживают методы
''.trim
и [].indexOf
, потому jQuery сохранило ссылки на них и использует нативные реализации в своих jQuery.trim
и jQuery.inArray
.trim = String.prototype.trim,
indexOf = Array.prototype.indexOf,
Конструирование объекта
Подбираемся к «святая-святых» jQuery — $-функции. Эта часть — самый тяжелый для непривыкшего человека кусок, потому подходим к ней со свежей головой ;) Тут скрыта магия прототипов jQuery, я не буду вдаваться в подробности, почему оно так работает, расскажу только КАК оно работает.
Мы уже видели выше код конструктора jQuery:
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
То есть, при вызове функции jQuery создается и возвращается сущность "
jQuery.fn.init
". В этом месте используется магия Javascript. Чуть ниже по коду мы можем обнаружить приблизительно следующее:jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
[...]
}
[...]
}
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
Отныне мы знаем, что
jQuery.fn
— это ничто иное, как прототип jQuery
и это знание поможет нам разобраться кое-с-чем ниже. Также, jQuery.fn.init.prototype
указывает на прототип jQuery
, и конструктор jQuery.fn.init.prototype
указывает на jQuery
. Такой подход даёт нам очень интересный результат. Откроем jQuery, консоль Chrome и введем:$(document) instanceof jQuery; // true
$(document) instanceof jQuery.fn.init; // true
Чтобы вы поняли суть такого поведения, я приведу вам другой пример:
var Init = function () {
console.log('[Init]');
};
var jQuery = function () {
console.log('[jQuery]');
return new Init();
};
Init.prototype = jQuery.prototype = {
constructor: jQuery
};
var $elem = jQuery(); // [jQuery] , [Init]
console.log( $elem instanceof jQuery ); // true
console.log( $elem instanceof Init ); // true
Таким образом, всё конструирование находится в функции-объекте
jQuery.fn.init
, а jQuery
— это фабрика объектов jQuery.fn.init
Парсим аргументы
Есть куча вариантов использования функции jQuery:
$(function () { alert('READY!') }); // Функция, которая выполнится только при загрузке DOM
$(document.getElementById('test')); // Ссылка на элемент
$('<div />'); // Создать новый элемент
$('<div />', { title: 'test' }); // Создать новый элемент с атрибутами
// Поддерживает все самые мыслимые и немыслимые css-селекторы:
$('#element'); // Елемент с айди "element"
$('.element', $previous ); // Найти все элементы с классом element в $previous
$("div[name=city]:visible:has(p)"); // И всё, что вы можете подумать
Для детального описания селекторов — читайте статью AntonShevchuk "jQuery для начинающих. Часть 4. Селекторы"
Залезем в конструктор, который, как мы уже знаем
jQuery.fn.init
. Я приведу здесь псевдокод:init: function( selector, context, rootjQuery ) {
if ( !selector ) return this;
// Handle $(DOMElement)
if ( selector.nodeType ) return this = selector;
// The body element only exists once, optimize finding it
if ( selector === "body" && !context ) return this = document.body;
if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
// Handle HTML strings
if ( typeof selector === "string" ) {
// Verify a match, and that no context was specified for #id
if ( selector.match(quickExpr) ) {
if ( match[1] ) {
return createNewDomElement( match[1] );
} else {
return getById( match[2] )
}
} else {
return jQuery( context ).find( selector );
}
}
},
Первые четыре куска вполне понятны — идет обработка случаев, когда передали пустой селектор, DOM-элемент в качестве селектора или строку 'body' — для более быстрого получения тела документа, а также обработка функции для DomReady.
Интересный момент с случаем, когда мы передаем строку. В первую очередь оно парсит её «быстрым регулярным выражением». В нём левая часть отвечает за нахождение тегов в строке, а вторая — за поиск по айди элемента:
quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
И только если запрос более сложный, то вызывается метод
find
у текущего контекста, который ищет элемент при помощи поискового движка (тоже авторства JResig) Sizzle
(права принадлежат The Dojo Foundation).Разработка плагинов
Многие профессионалы Javascript знают о том, что класс, созданный при помощи прототипов можно очень легко расширять.
var MyClass = function () {
// constructor
};
MyClass.prototype = {
// prototype
};
var instance = new MyClass();
// Мы можем расширить прототип класса и новые возможности добавятся во все сущности, даже уже созданные
MyClass.prototype.plugin = function () {
console.log("He's alive!");
};
instance.plugin(); // He's alive!
Таким же образом мы можем расширять стандартный прототип jQuery:
jQuery.prototype.plugin = function () {
// Here is my plugin
};
Но, как мы уже заметили выше,
fn
— это короткая ссылка на jQuery.prototype
, потому можно писать короче:jQuery.fn.plugin = function () {
// Here is my plugin
// this здесь ссылается на jquery-объект, от которого вызван метод
};
И данный плагин появится во всех уже созданных и тех, что создадутся сущностях. Добавляя свойства напрямую в объект мы реализуем статические свойства:
jQuery.plugin = function () {
// Here is my plugin
};
Таким образом, наилучший шаблон для небольших плагинов:
new function (document, $, undefined) {
var privateMethod = function () {
// private method, used for plugin
};
$.fn.myPlugin = function () {
};
// и, если нужен метод, не привязанный к dom-элементам:
$.myPlugin = function () {
};
}(document, jQuery);
Именно такой подход можно заметить у большинства плагинов для jQuery, например, DatePicker.
Заключение
На мой взгляд причиной популярности jQuery стала внешняя простота и лёгкость, а также краткость названий:
css
против setStyles
, attr
против setAttributes
и т.п. Идея была просто прекрасной и покорила умы многих. Очень часто встречаются клоны jQuery или переносятся идеи в другие языки. Но простота обманчива. И не всегда она хороша, так что всегда трижды подумайте, прежде чем сокращать понятное название своего метода, может оно вылезет вам боком ;)Надеюсь, вам понравилась эта статья и она не оказалась слишком заумной. Если будет желание — можно будет продолжить цикл и углубится в jQuery ещё глубже, или поизучать другие фреймворки.