
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 ещё глубже, или поизучать другие фреймворки.
