Когда я начинал учить веб-программирование, встретил лучший из всех, по моему мнению, фреймворков — JQuery. В то далёкое время нельзя было представить нормальное программирование без него, так как он мог делать одной строкой то, что делал JavaScript за 95.
В сегодняшнее время, JavaScript очень изменился. В него добавили большой функционал, который сокращает количество кода в разы и делает программирование более удобным. Но даже с этими обновлениями он не может воспроизвести некоторые, даже самые простые, функции из JQuery, и когда мы решаем отказаться от этого фремворка, то чувствует некую трудность из за этого.
Так вот, в этой статье я хочу рассказать о реализациях некоторых функций из JQuery на чистом JavaScript.
Пока всё. Через время я буду опубликовывать статьи про функции JQuery более конкретно
В сегодняшнее время, JavaScript очень изменился. В него добавили большой функционал, который сокращает количество кода в разы и делает программирование более удобным. Но даже с этими обновлениями он не может воспроизвести некоторые, даже самые простые, функции из JQuery, и когда мы решаем отказаться от этого фремворка, то чувствует некую трудность из за этого.
Так вот, в этой статье я хочу рассказать о реализациях некоторых функций из JQuery на чистом JavaScript.
$(document).ready( Function ); или $( Function );
Для тех кто не знает, это функция готовности DOM дерева. Т.е. эта функция запускается, когда DOM страницы был полностью загружен.
Начиная с IE9+ эту функцию можно заменить с помощью событияDOMContentLoaded
повешенного наdocument
.
Пример:
document.addEventListener('DOMContentLoaded', function() { // Ваш скрипт }, false);
Если вам нужна поддержка начиная с IE4+, то можно воспользоваться более старым методом — с помощью событияreadystatechange
повешенного наdocument
и проверкойreadyState
.
Пример:
document.onreadystatechange = function(){ if(document.readyState === 'complete'){ // Ваш скрипт } };
Если же мы посмотрим в исходники JQuery, то выйдет следующая функция:
Пример:
var ready = (function() { var readyList, DOMContentLoaded, class2type = {}; class2type["[object Boolean]"] = "boolean"; class2type["[object Number]"] = "number"; class2type["[object String]"] = "string"; class2type["[object Function]"] = "function"; class2type["[object Array]"] = "array"; class2type["[object Date]"] = "date"; class2type["[object RegExp]"] = "regexp"; class2type["[object Object]"] = "object"; var ReadyObj = { // Является ли DOM готовым к использованию? Установите значение true, как только оно произойдет. isReady: false, // Счетчик, чтобы отслеживать количество элементов, ожидающих до начала события. См. #6781 readyWait: 1, // Удерживать (или отпускать) готовое событие holdReady: function(hold) { if (hold) { ReadyObj.readyWait++; } else { ReadyObj.ready(true); } }, // Обрабатывать, когда DOM готов ready: function(wait) { // Либо трюк не работает, либо событие DOMready/load и еще не готовы if ((wait === true && !--ReadyObj.readyWait) || (wait !== true && !ReadyObj.isReady)) { // Убедитесь, что тело существует, по крайней мере, в случае, если IE наложает (ticket #5443). if (!document.body) { return setTimeout(ReadyObj.ready, 1); } // Запоминаем что DOM готов ReadyObj.isReady = true; // Если обычное событие DOM Ready запускается, уменьшается и ожидает, если потребуется, if (wait !== true && --ReadyObj.readyWait > 0) { return; } // Если функции связаны, выполнить readyList.resolveWith(document, [ReadyObj]); // Запуск любых связанных событий //if ( ReadyObj.fn.trigger ) { // ReadyObj( document ).trigger( "ready" ).unbind( "ready" ); //} } }, bindReady: function() { if (readyList) { return; } readyList = ReadyObj._Deferred(); // Поймать случаи, когда $(document).ready() вызывается после // события браузера, которое уже произошло. if (document.readyState === "complete") { // Обращайтесь к нему асинхронно, чтобы позволить скриптам возможность задержать готовность return setTimeout(ReadyObj.ready, 1); } // Mozilla, Opera и webkit nightlies в настоящее время поддерживают это событие if (document.addEventListener) { // Используем удобный callback события document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); // Откат к window.onload, который всегда будет работать window.addEventListener("load", ReadyObj.ready, false); // Если используется тип событий IE } else if (document.attachEvent) { // Обеспечить запуск перед загрузкой, // Возможно, поздно, но безопасно также для iframes document.attachEvent("onreadystatechange", DOMContentLoaded); // Откат к window.onload, который всегда будет работать window.attachEvent("onload", ReadyObj.ready); // Если IE, а не frame // Постоянно проверяем, готов ли документ var toplevel = false; try { toplevel = window.frameElement == null; } catch (e) {} if (document.documentElement.doScroll && toplevel) { doScrollCheck(); } } }, _Deferred: function() { var // список callback callbacks = [], // stored [ context , args ] fired, // Чтобы избежать запуска, когда это уже сделано firing, // Чтобы узнать, отменена ли отсрочка cancelled, // Отложенный deferred = { // done( f1, f2, ...) done: function() { if (!cancelled) { var args = arguments, i, length, elem, type, _fired; if (fired) { _fired = fired; fired = 0; } for (i = 0, length = args.length; i < length; i++) { elem = args[i]; type = ReadyObj.type(elem); if (type === "array") { deferred.done.apply(deferred, elem); } else if (type === "function") { callbacks.push(elem); } } if (_fired) { deferred.resolveWith(_fired[0], _fired[1]); } } return this; }, // Разрешить с заданным контекстом и аргументами resolveWith: function(context, args) { if (!cancelled && !fired && !firing) { // Убедитесь, что имеются аргументы (#8421) args = args || []; firing = 1; try { while (callbacks[0]) { callbacks.shift().apply(context, args); //shifts a callback, and applies it to document } } finally { fired = [context, args]; firing = 0; } } return this; }, // решить с этим в качестве контекста и приведенных аргументов resolve: function() { deferred.resolveWith(this, arguments); return this; }, // Отложено ли это решение? isResolved: function() { return !!(firing || fired); }, // Отмена cancel: function() { cancelled = 1; callbacks = []; return this; } }; return deferred; }, type: function(obj) { return obj == null ? String(obj) : class2type[Object.prototype.toString.call(obj)] || "object"; } } // Проверка готовности DOM для Internet Explorer function doScrollCheck() { if (ReadyObj.isReady) { return; } try { // Если используется IE, то используйте трюк Диего Перини // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch (e) { setTimeout(doScrollCheck, 1); return; } // И выполнить функцию ожидания ReadyObj.ready(); } // Функция очистки для document ready if (document.addEventListener) { DOMContentLoaded = function() { document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); ReadyObj.ready(); }; } else if (document.attachEvent) { DOMContentLoaded = function() { // Убедимся, что тело существует, по крайней мере, в случае, если IE наложает (ticket #5443). if (document.readyState === "complete") { document.detachEvent("onreadystatechange", DOMContentLoaded); ReadyObj.ready(); } }; } function ready(fn) { // Прикрепление слушателя ReadyObj.bindReady(); var type = ReadyObj.type(fn); // Добавление callback'а readyList.done(fn); // ReadyList является результатом _Deferred() } return ready; })();
Запуск функции происходить таким образом:
ready(function() { // Ваш скрипт });
$.ajax( Object );
Для тех кто не знает, эта функция выполняет асинхронный HTTP (Ajax) запрос.
Как бы это ни было банально, но альтернативой для Jquery.ajax() являетсяXMLHttpRequest
Немного об использовании:
Для начала, нам бы следовало создать кроссбраузерную функцию для отправки, т.к. в разных браузерах функция отправки может быть разной. Она создаётся таким образом:
function getXmlHttp(){ var xmlhttp; try { xmlhttp = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xmlhttp = new ActiveXObject('Microsoft.XMLHTTP'); } catch (E) { xmlhttp = false; } } if (!xmlhttp && typeof XMLHttpRequest !== 'undefined') { xmlhttp = new XMLHttpRequest(); } return xmlhttp; }
А вот сам пример стандартного POST запроса с обработкой ошибок:
var xmlhttp = getXmlHttp(); // Получаем нашу функцию xmlhttp.open('POST', '/someurl', true); // Отправляем POST запрос на адрес "/someurl" // Вызываем функцию при изменении статуса запроса xmlhttp.onreadystatechange = function(){ if (xmlhttp.readyState !== 4) return; // Если запрос не завершён, то ничего не делаем // Немного о статусах: // Статус 0 — Объект XMLHttpRequest был создан, но метод open() ещё не вызывался. // Статус 1 — Был вызван метод open(). На этом этапе методом setRequestHeader() могут быть установлены заголовки запроса (request headers), после чего, для начала выполнения запроса, может быть вызван метод send() . // Статус 2 — Был вызван метод send() и получены заголовки ответа (response headers) . // Статус 3 — Получена часть ответа. Если responseType это пустая строка или имеет значение "text", responseText будет содержать загруженную порцию текста ответа. // Статус 4 — Операция доставки данных завершена. Это может означать как то, что передача данных полностью завершена успешно, так и то, что произошла ошибка. clearTimeout(timeout); // Удаляем Timeout, если запрос завершён if (xmlhttp.status == 200) { // Если запрос был отправлен успешно и мы получили ответ, то обрабатываем информацию console.log(xmlhttp.responseText); } else { // Если же у нас ошибка, то отправляем её в обработчик handleError(xmlhttp.statusText); } } // Указываем данные, которые нам нужно отправить xmlhttp.send("a=5&b=4"); // Создаём таймер на 10 секунд. Он нужен для того, чтобы, когда ответ от сервера не приходит, выдать ошибку var timeout = setTimeout( function(){ xmlhttp.abort(); handleError('Time over') }, 10000); // Создаём функцию обработки ошибок function handleError(message) { // Тут мы принимает текст ошибки и распологаем ним как хотим console.log('Ошибка: ' + message); }
По такой же технологией сделана функция JQuery AJAX.
Глобальная функция $(...);
Если кто не знает, этой функцией создаётся глобальный JQuery объект.
Тут я не буду расписывать полный функционал этой функции, так как это займёт у меня минимум неделю, а просто напишу, как создаётся подобная вещь по примеру JQuery.
Для начала создадим обычную функцию, к примеру,Library
с аргументами (селектор и контекст).
var Library = function (selector, context) {};
Далее пишем общую функцию. В JQuery значения, которые попадают в функцию перебираются с помощью условий и выводится результат
var init = function (selector, context) { // Для начала мы создадим массив, // в который будем скидывать элементы var array = []; /** Сначала мы проработаем вариан, * когда кодер указал селектор * или HTML код в первом аргументе */ if (typeof selector === 'string' ) { /** Нам нужно попарсить HTML код * чтобы узнать селектор это или код. * Для парсинка в JQuery используется * следующее регулярное выражение: * /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/ */ if (/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/.exec(selector)) { // Сначала я распарсю код var DOM = new DOMParser().parseFromString(selector, 'text/html'); var DOMList = DOM.body.childNodes; // Тут ещё нужно вспомнить про context и проверить его на значения if (!context || {}.toString.call(context) !== '[object Object]') { context = null; }; // Далее добавляем элементы новый в массив for (var i = 0; i < DOMList.length; i++) { if (context) { for (var attr in context) { DOMList[i].setAttribute(attr, context + ''); }; }; array[array.length] = DOMList[i]; }; return array; } else { // Тут нужно проверить // является ли свойство // context элементом, // в котором нужно искать // объект var DOMList = {}.toString.call(context) === '[object HTMLElement]' ? context.querySelectorAll(selector) : document.querySelectorAll(selector); // Теперь перекидываем все элементы в массив // и выводим for (var i = 0; i < DOMList.length; i++) { array[array.length] = DOMList[i]; }; return array; }; // Тут мы проверим, является ли первый аргумент массивом } else if ({}.toString.call(selector) === '[object Array]') { // Перекидываем значения и выводим for (var i = 0; i < selector.length; i++) { array[array.length] = selector[i]; }; return array; // Далее я проверю, может это объект или один элемент } else if ({}.toString.call(selector) === '[object Object]' || {}.toString.call(selector) === '[object HTMLElement]') { // Запихиваем объект в массив и выводим array[0] = selector; return array; // Теперь проверяем, может это живой массив элементов? } else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') { // Перекидываем значения и выводим for (var i = 0; i < selector.length; i++) { array[array.length] = selector[i]; }; return array; // Если ничего не подходит, то выводим пустой массив } else { return array; } };
Теперь мы можем добавить запуск этой функции через основную
var Library = function (selector, context) { // Получаем массив из основной функции var array = new init(selector, context); /** Тут мы создаём объект. * К его proto присваиваем * прототип главной функции, * чтобы потом можно было * создавать дополнительный * функционал */ var object = { __proto__: Library.prototype }; // Далее мы перекидываем элементы // Из массива в объект и создаём // параметр length, чтобы потом можно // было узнать, сколько элементов // в объекте for (var i = 0; i < array.length; i++) { object[i] = array[i]; }; object.length = array.length; return object; };
Вот и готово. Теперь мы можем получить массив элементов через функциюLibrary(...);
и создавать дополнительный функционал через такую конструкциюLibrary.prototype.myFunction = function () {...};
Пока всё. Через время я буду опубликовывать статьи про функции JQuery более конкретно