- PVSM.RU - https://www.pvsm.ru -
Когда я начинал учить веб-программирование, встретил лучший из всех, по моему мнению, фреймворков — JQuery. В то далёкое время нельзя было представить нормальное программирование без него, так как он мог делать одной строкой то, что делал JavaScript за 95.
В сегодняшнее время, JavaScript очень изменился. В него добавили большой функционал, который сокращает количество кода в разы и делает программирование более удобным. Но даже с этими обновлениями он не может воспроизвести некоторые, даже самые простые, функции из JQuery, и когда мы решаем отказаться от этого фремворка, то чувствует некую трудность из за этого.
Так вот, в этой статье я хочу рассказать о реализациях некоторых функций из JQuery на чистом JavaScript.
Для тех кто не знает, это функция готовности DOM дерева. Т.е. эта функция запускается, когда DOM страницы был полностью загружен.
Начиная с IE9+ эту функцию можно заменить с помощью события DOMContentLoaded
[1] повешенного на document
.
Пример:
document.addEventListener('DOMContentLoaded', function() {
// Ваш скрипт
}, false);
Если вам нужна поддержка начиная с IE4+, то можно воспользоваться более старым методом — с помощью события readystatechange
[2] повешенного на 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() {
// Ваш скрипт
});
Для тех кто не знает, эта функция выполняет асинхронный HTTP (Ajax) запрос.
Как бы это ни было банально, но альтернативой для Jquery.ajax() является XMLHttpRequest
[3]
Немного об использовании:
Для начала, нам бы следовало создать кроссбраузерную функцию для отправки, т.к. в разных браузерах функция отправки может быть разной. Она создаётся таким образом:
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*(<[wW]+>)[^>]*|#([w-]+))$/
*/
if (/^(?:s*(<[wW]+>)[^>]*|#([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 более конкретно
Автор: Yuri Spivak
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/262426
Ссылки в тексте:
[1] DOMContentLoaded
: https://developer.mozilla.org/ru/docs/Web/Events/DOMContentLoaded
[2] readystatechange
: https://developer.mozilla.org/ru/docs/Web/Events/readystatechange
[3] XMLHttpRequest
: http://xmlhttprequest.ru/
[4] Источник: https://habrahabr.ru/post/335926/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.