- PVSM.RU - https://www.pvsm.ru -
Когда-то библиотека jQuery была хорошим помощником для программистов. Так как она позволяла в разы облегчить создание функционала, который в то время с помощью одного JavaScript было почти нереально написать, и предоставляла очень хорошую кроссбраузерность.
Сейчас же JavaScript получил множество больших обновлений, функции в которых получили большое количество полифиллов и могут заменить функции jQuery. Но, к сожалению, JS до сих пор не научился делать большинство функций, которые присутствуют в jQuery.
В этой статье я расскажу про самую главную функцию jQuery — jQuery();
.
Функция JQuery();
состоит из двух функций — основной и так называемой обложки. Вы запускаете обложку, обложка запускает основную функцию и возвращает из неё результат.
Обложка выглядит следующим образом:
var jQuery = function (selector, context) {
return new jQuery.prototype.init(selector, context);
};
Как видно из кода, когда мы запускаем функцию jQuery();
она запускает функцию init
с теми же параметрами из своего прототипа. Именно функция init
является основной.
Далее нам следует создать прототип для нашей функции, в конструкторе которой мы будем ссылаться на функцию jQuery. Заметка: в старых версиях jQuery, параметр constructor
не указывается.
jQuery.prototype = {
constructor: jQuery
};
jQuery.prototype
и jQuery.fn
, тем самым задав для двух параметров один объект, чтобы иметь возможность более кратко добавлять новые функции для объектов.
jQuery.fn = jQuery.prototype = {
constructor: jQuery
};
Теперь начнём создавать основную функцию. Я не буду расписывать полную структуру функции, т.к. это займёт у меня минимум неделю, а напишу как реализуется более краткий вариант по примеру jQuery.
У jQuery вся функция состоит из условий, которые перебирают данные полученные функцией, и массива, в который скидываются нужные элементы.
Начальный вид функции выглядит так:
var init = jQuery.prototype.init = function (selector, context) {
/** Для начала создаём массив, в который будем скидывать нужные элементы */
var array = [];
/** Сначала мы проработаем вариан,
* когда кодер указал селектор
* или HTML код в первом аргументе
*/
if (typeof selector === 'string') {
/** Далее проверяем, не является ли первый аргумент DOM элементом */
} else if (selector.nodeType) {
/**
* Следующая проверка будет на ситуацию
* если первый аргумент является массивом
*/
} else if ({}.toString.call(selector) === '[object Array]') {
/**
* Следующей проверкой будет вариант,
* когда первый аргумент является объектом
*/
} else if ({}.toString.call(selector) === '[object Object]') {
/** Теперь проверяем, может это живой массив элементов? */
} else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') {
/** Если первый аргумент — это функция */
} else if ({}.toString.call(selector) === '[object Function]') {
/** Если же ни однин тип не подошёл, то выводим пустой массив */
} else {
return array;
}
};
Рассмотрим первое условие — если селектор является строкой. Если это строка, то нам следует проверить: это селектор или HTML код (ведь jQuery таким образом может парсить HTML код).
В jQuery для проверки, является ли первый аргумент HTML кодом, используется регулярное выражение проверка первого, последнего символов и общее количество символов, которое должно быть больше либо равно трём.
/** Проверяем, не является ли селектор HTML кодом */
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
/** Если это не HTML, то обычный селектор */
} else {
};
Если первый аргумент является HTML кодом, то происходят следующие действия:
Я же расскажу вам про альтернативные функции, с помощью которых можно реализовать данный функционал.
innerHTML
внесём нашу строку с кодом и заберём уже готовые DOM элементы.
var parseHTML = function (HTML) {
/**
* Хоть мы и пишем простую альтернативу,
* но проверить тип аргумента и
* проверить это код или нет — нужно.
* Код это или нет узнаём тем же образом
* что и в условии основной функции
*/
if (typeof HTML === 'string' && HTML[0] === "<" && HTML[HTML.length - 1] === ">" && HTML.length >= 3) {
/** Создаём элемент */
var E = document.createElement('div');
/** Вставляем в элемент наш HTML код */
E.innerHTML = HTML;
/** Далее выводим готовый живой массив */
return E.childNodes;
} else {
/** Если тип — не строка, то вернём пустой массив */
return [];
};
};
DOMParser();
и её дополнительной функции parseFromString();
.
var parseHTML = function (HTML) {
/**
* Хоть мы и пишем простую альтернативу,
* но проверить тип аргумента и
* проверить это код или нет — нужно.
* Код это или нет узнаём тем же образом
* что и в условии основной функции
*/
if (typeof HTML === 'string' && HTML[0] === "<" && HTML[HTML.length - 1] === ">" && HTML.length >= 3) {
/** Создаём новый HTML документ с кодом из аргумента */
var DOM = new DOMParser().parseFromString(HTML, 'text/html');
/** Получаем наше пропарсенное содержимое */
var DOMList = DOM.body.childNodes;
/** Далее выводим готовый живой массив */
return DOMList;
} else {
/** Если тип — не строка, то вернём пустой массив */
return [];
};
};
document.implementation.createHTMLDocument();
.
var parseHTML = function (HTML) {
/**
* Хоть мы и пишем простую альтернативу,
* но проверить тип аргумента и
* проверить это код или нет — нужно.
* Код это или нет узнаём тем же образом
* что и в условии основной функции
*/
if (typeof HTML === 'string' && HTML[0] === "<" && HTML[HTML.length - 1] === ">" && HTML.length >= 3) {
/** Создадим новый HTML документ */
var DOM = document.implementation.createHTMLDocument();
/** Внесём в него HTML строку */
DOM.body.innerHTML = HTML;
/** Получаем уже живой массив */
var DOMList = DOM.body.childNodes;
/** Далее выводим его */
return DOMList;
} else {
/** Если тип — не строка, то вернём пустой массив */
return [];
};
};
Но нам нужна функция, которая повторит весь функционал jQuery функции. Поэтому мы создадим одну функцию, которая включит в себя все три вышеперечисленные варианта:
var parseHTML = function (data, context, keepScripts) {
/** Если к нам отправили не строку, то вернуть пустой массив */
if (typeof data !== 'string') {
return [];
}
/**
* Мы принимаем свойство context
* как keepScripts, если в нём
* указан true/false
*/
if (typeof context === 'boolean') {
keepScripts = context;
context = false;
};
var DOM, DOMList;
if (!context || context === document) {
/** Создаём HTML страницу */
DOM = document.implementation.createHTMLDocument() || new DOMParser().parseFromString('', 'text/html');
/** Внесём в него HTML строку */
DOM.body.innerHTML = data;
/** Получаем наше пропарсенное содержимое */
DOMList = DOM.body.childNodes;
} else if (context.nodeType) {
/** Создаём новый элемент, через который будем парсить код */
DOM = document.createElement('div');
/** Добавляем этот элемент в целевой элемент */
context.appendChild(DOM);
/** Добавляем содержимое в элемент */
DOM.innerHTML = data;
/** Получаем список элементов */
DOMList = DOM.childNodes;
/** Удаляем DOM */
DOM.parentNode.removeChild(DOM);
};
/** Если нужно запустить скрипт, указанный в коде */
if (keepScripts === true) {
/** Получаем список скриптов */
var scripts = DOM.scripts || DOM.querySelectorAll('script');
/** Проверяем наличие файлов скрипта */
if (scripts.length) {
/** Перебираем все скрипты */
for (var i = 0; i < scripts.length; i++) {
/** Создаём новый элемент скрипта */
var script = document.createElement('script');
/** Передаём новосозданному элементу скрипта все параметры */
script.innerHTML = scripts[i].innerHTML;
if (scripts[i].attributes) script.attributes = scripts[i].attributes;
/** Добавляем скрипт в HEAD для запуска */
document.head.appendChild(script);
/** Удаляем скрипт из HEAD */
script.parentNode.removeChild(script);
};
};
};
/** Перебираем значения в обычный массив и выводим */
return (function () {
var array = [];
for (var i = 0; i < DOMList.length; i++) {
array[array.length] = DOMList[i];
};
return array;
})();
};
var merge = function( first, second ) {
var len = +second.length, /** Я не знаю зачем они преобразуют second.length в число, если по идее, это и так число (если кто-то знает, пишите в комментариях) */
j = 0,
i = first.length;
/** Перебираем значения второго массива */
for ( ; j < len; j++ ) {
/**
* Добавляем новое значение в первый массив
* и за одно плюсует счётчик количества
* элементов в первом массиве
*/
first[ i++ ] = second[ j ];
}
/** Записываем новое количество элементов в массиве */
first.length = i;
/** Выводим готовый массив */
return first;
};
Альтернатив у функции jQuery.merge()
есть несколько:
Array.concat();
[2], которая объединяет массивы.
[0, 1, 2].concat([3, 4, 5]); // [0, 1, 2, 3, 4, 5]
Но данная функция не подойдёт, если вы хотите присоединить к массиву HTML коллекцию или список Node, так как у вас выведется массив в массиве, вместо объединения.
Но это довольно легко исправить: нужно преобраовать живой массив в обычный. Это можно сделать с помощью функции [].slice.call()
.
[0, 1, 2].concat( [].slice.call(document.querySelectorAll(selector)) ); // [0, 1, 2, element, element]
Или же это можно сделать перебрав все элементы живого массива и переместив их в обычный массив.
var elements = document.querySelectorAll(selector);
var array = [];
for (var i = 0; i < elements.length; i++) {
array[array.length] = elements[i];
};
[0, 1, 2].concat(array); // [0, 1, 2, element, element]
var merge = function (array0, array1) {
for (var i = 0; i < array1.length; i++) {
array0[array0.length] = array1[i];
/**
* Или array0.push(array1[i]);
* если первый массив не живой
*/
};
return array0;
};
merge([0, 1, 2], [3, 4, 5]); // [0, 1, 3, 4, 5, 6]
context
объектом с параметрами. Если да, то записываем все параметры аргументами в DOM элементы.
if ({}.toString.call(context) === '[object Object]') {
for (var i = 0; i < array.length; i++) {
for (var argument in context) {
array.setAttribute(argument, context[argument]);
};
};
};
Если первый аргумент не HTML код, значит он является селектором. В jQuery элементы по селектору ищутся с помощью другой библиотеки именуемой Sizzle [3]. Преимущество этой библиотеки в том, что её поиск по селектору начинает работать начиная с IE7+.
Поиск элементов по селектору, с помощью этой библиотеки, выглядит следующим образом:
Sizzle( String selector );
Но я воспользуюсь её JavaScript альтернативой — document.querySelectorAll( String selector );
. Единственный недостаток этого метода перед библиотекой Sizzle — он начинает работать только с IE9+.
Прежде чем выводить элементы по селектору, нам нужно проверить аргумент context
и убедится, что он не является элементом или элементами, в которых нужно искать элементы по селектору. Я не буду расписывать каждый шаг, так как в коде объяснил все шаги.
/**
* Проверяем, является ли аргумент context элементом или
* селектором элементов, в которых нужно искать элементы
* по селектору из аргумента selector
*/
/** Если аргумент context является элементом */
if (context && context.nodeType) {
/** Получаем список элементов из родительского */
var Elements = context.querySelectorAll(selector);
/** Переносим список элементов в массив и выводим */
return jQuery.merge(array, Elements);
/** Если аргумент context является живым массив */
} else if ({}.toString.call(context) === '[object HTMLCollection]' || {}.toString.call(context) === '[object NodeList]') {
/**
* Перебираем всех родителей и нужные
* дочерние элементы переносим в массив
*/
for (var i = 0; i < context.length; i++) {
/** Получаем список элементов из родительского */
var Elements = context[i].querySelectorAll(selector);
/** Переносим список элементов в массив */
jQuery.merge(array, Elements);
};
/** Выводим массив */
return array;
/** Если аргумент context является селектором */
} else if (typeof context === 'string') {
/** Получаем все родительские элементы
* по селектору
*/
var Parents = document.querySelectorAll(context);
/**
* Перебираем всех родителей и нужные
* дочерние элементы переносим в массив
*/
for (var i = 0; i < Parents.length; i++) {
/** Получаем список элементов из родительского */
var Elements = Parents[i].querySelectorAll(selector);
/** Переносим список элементов в массив */
jQuery.merge(array, Elements);
};
/** Выводим массив */
return array;
/**
* Если ни одни из вариантов не подходит,
* то осуществляем обычный поисх элементов
*/
} else {
/** Получаем список элементов по селектору */
var Elements = document.querySelectorAll(selector);
/** Выводим массив */
return jQuery.merge(array, Elements);
};
В jQuery все эти проверки заменены функцией поиска — .find();
. Поэтому запись jQuery(selector, elements);
является сокращённой функцией jQuery(elements).find(selector);
Сейчас наша основная функция выглядит следующим образом:
var init = jQuery.prototype.init = function (selector, context) {
/** Для начала создаём массив, в который будем скидывать нужные элементы */
var array = [];
/** Сначала мы проработаем вариан,
* когда кодер указал селектор
* или HTML код в первом аргументе
*/
if (typeof selector === 'string') {
/** Проверяем, не является ли селектор HTML кодом */
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
/** Парсим HTML код и объединям с основным массивом */
jQuery.merge(array, jQuery.parseHTML(selector, context && context.nodeType ? context.ownerDocument || context : document, true));
/** Проверяем, является ли аргумент context объектом
* с параметрами. Если да, то записываем все параметры
* аргументами в DOM элементы.
*/
if ({}.toString.call(context) === '[object Object]') {
for (var i = 0; i < array.length; i++) {
for (var argument in context) {
array.setAttribute(argument, context[argument]);
};
};
};
/** Выводим массив */
return array;
/** Если это не HTML, то обычный селектор */
} else {
/**
* Проверяем, является ли аргумент context элементом или
* селектором элементов, в которых нужно искать элементы
* по селектору из аргумента selector
*/
/** Если аргумент context является элементом */
if (context && context.nodeType) {
/** Получаем список элементов из родительского */
var Elements = context.querySelectorAll(selector);
/** Переносим список элементов в массив и выводим */
return jQuery.merge(array, Elements);
/** Если аргумент context является живым массив */
} else if ({}.toString.call(context) === '[object HTMLCollection]' || {}.toString.call(context) === '[object NodeList]') {
/**
* Перебираем всех родителей и нужные
* дочерние элементы переносим в массив
*/
for (var i = 0; i < context.length; i++) {
/** Получаем список элементов из родительского */
var Elements = context[i].querySelectorAll(selector);
/** Переносим список элементов в массив */
jQuery.merge(array, Elements);
};
/** Выводим массив */
return array;
/** Если аргумент context является селектором */
} else if (typeof context === 'string') {
/** Получаем все родительские элементы
* по селектору
*/
var Parents = document.querySelectorAll(context);
/**
* Перебираем всех родителей и нужные
* дочерние элементы переносим в массив
*/
for (var i = 0; i < Parents.length; i++) {
/** Получаем список элементов из родительского */
var Elements = Parents[i].querySelectorAll(selector);
/** Переносим список элементов в массив */
jQuery.merge(array, Elements);
};
/** Выводим массив */
return array;
/**
* Если ни одни из вариантов не подходит,
* то осуществляем обычный поисх элементов
*/
} else {
/** Получаем список элементов по селектору */
var Elements = document.querySelectorAll(selector);
/** Выводим массив */
return jQuery.merge(array, Elements);
}
};
/** Далее проверяем, не является ли первый аргумент DOM элементом */
} else if (selector.nodeType) {
/**
* Следующая проверка будет на ситуацию
* если первый аргумент является массивом
*/
} else if ({}.toString.call(selector) === '[object Array]') {
/**
* Следующей проверкой будет вариант,
* когда первый аргумент является объектом
*/
} else if ({}.toString.call(selector) === '[object Object]') {
/** Теперь проверяем, может это живой массив элементов? */
} else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') {
/** Если первый аргумент — это функция */
} else if ({}.toString.call(selector) === '[object Function]') {
/** Если же ни однин тип не подошёл, то выводим пустой массив */
} else {
return array;
}
};
Теперь рассмотрим второе условие — если селектор является DOM элементом. Если селектор является DOM элементом, то мы просто вставляем этот элемент в основной массив, который потом выводим.
/** Вставляем элемент в массив */
array[0] = selector;
/** Выводим основной массив */
return array;
Перейдём к третьему условию — если селектор является массивом. Если если это массив, то мы объединяем его с основным массивом, который потом выводим. Объединять массивы мы будем с помощью вышенаписанной функции.
/** Объединям массивы */
jQuery.merge(array, selector);
/** Выводим основной массив */
return array;
Теперь о четвёртом условии — если селектор является объектом с параметрами. Если селектор является объектом с параметрами, то как и в варианте с DOM элементом, просто вносим объект в массив и выводим.
/** Вставляем объект в массив */
array[0] = selector;
/** Выводим основной массив */
return array;
Рассмотрим пятое условия — если селектор является живым массивом. Если селектор является живым массивом, то мы переносим элементы с него в основной массив. Действия выглядят, как и в случае с массивом.
/** Переносим элемент из живого массива в основной массив */
jQuery.merge(array, selector);
/** Выводим основной массив */
return array;
Осталось рассмотреть последнее условие — если селектор является функцией. Когда селектор — функцией, то функция jQuery( Function );
является сокращённой функцией jQuery(document).ready( Function );
.
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,
// Счетчик, чтобы отслеживать количество элементов, ожидающих до начала события.
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)) {
// Проверяем, что body существует, в случае, если IE наложает.
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) {
// Проверяем, что имеются аргументы
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() {
// Проверяем, что body существует, в случае, если 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;
})();
Если мы рассмотрим код, то увидим, что он построен на двух отдельных вариантах запуска скрипта после загрузки DOM:
DOMContentLoaded
[4] повешенного на document
. Этот вариант будет работать начиная с IE9+.
document.addEventListener('DOMContentLoaded', function() {
// Ваш скрипт
}, false);
readystatechange
повешенного на document
и проверкой readyState
. Этот вариант более старый, поэтому он будет работать начиная с IE4+.
document.onreadystatechange = function(){
if(document.readyState === 'complete'){
// Ваш скрипт
}
};
Есть и другие способы запуска скрипта после загрузки DOM:
<script></script>
в body
после всех элементов. При таком расположении сначала загрузится DOM, а потом скрипт.
<body>
<div>...</div>
<script>
// Ваш скрипт
</script>
</body>
body
.
function onload() {
// Ваш скрипт
};
<body>
...
<script>
onload();
</script>
</body>
body
событие onload
.
function myFunc() {
// Ваш скрипт
};
<body onload="myFunc()">...</body>
Эту функцию я заменю одной из альтернатив — событием readystatechange
, о которой писал выше.
/** Запускаем функции после загрузки DOM дерева */
document.onreadystatechange = function(){
if(document.readyState === 'complete'){
selector();
};
};
/** Вернём эту же функцию */
return selector;
Всё, мы записали действия для всех условий и наша функция практически готова. Осталось к параметру __proto__
основного массива присвоить прототип главной функции, чтобы потом можно было создавать дополнительный функционал для jQuery объектов. Объект __proto__
довольно новый и начинает работать только с IE11+. Поэтому мы сделаем некий полфилл.
if (array.__proto__) {
array.__proto__ = jQuery.prototype;
} else {
for (var param in jQuery.prototype) {
array[param] = jQuery.prototype[param];
};
};
Функция готово. В конечном итоге она будет выглядеть следующим образом:
/** Создаём функцию — обложку */
var jQuery = function (selector, context) {
return new jQuery.prototype.init(selector, context);
};
/** Создаём для функции прототип */
jQuery.prototype = {
constructor: jQuery
};
/** Создаём основную функцию */
var init = jQuery.prototype.init = function (selector, context) {
/** Для начала создаём массив, в который будем скидывать нужные элементы */
var array = [];
/**
* К его proto массива присваиваем
* прототип главной функции,
* чтобы потом можно было
* создавать дополнительный
* функционал для jQuery объектов
*/
/** Объект __proto__ достаточно новый
* Начинает работать начиная с IE11+
* Поэтому мы создадим некий полифилл
*/
if (array.__proto__) {
array.__proto__ = jQuery.prototype;
} else {
for (var param in jQuery.prototype) {
array[param] = jQuery.prototype[param];
};
};
/** Сначала мы проработаем вариан,
* когда кодер указал селектор
* или HTML код в первом аргументе
*/
if (typeof selector === 'string') {
/** Проверяем, не является ли селектор HTML кодом */
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
/** Парсим HTML код и объединям с основным массивом */
jQuery.merge(array, jQuery.parseHTML(selector, context && context.nodeType ? context.ownerDocument || context : document, true));
/** Проверяем, является ли аргумент context объектом
* с параметрами. Если да, то записываем все параметры
* аргументами в DOM элементы.
*/
if ({}.toString.call(context) === '[object Object]') {
for (var i = 0; i < array.length; i++) {
for (var argument in context) {
array.setAttribute(argument, context[argument]);
};
};
};
/** Выводим массив */
return array;
/** Если это не HTML, то обычный селектор */
} else {
/**
* Проверяем, является ли аргумент context элементом или
* селектором элементов, в которых нужно искать элементы
* по селектору из аргумента selector
*/
/** Если аргумент context является элементом */
if (context && context.nodeType) {
/** Получаем список элементов из родительского */
var Elements = context.querySelectorAll(selector);
/** Переносим список элементов в массив и выводим */
return jQuery.merge(array, Elements);
/** Если аргумент context является живым массив */
} else if ({}.toString.call(context) === '[object HTMLCollection]' || {}.toString.call(context) === '[object NodeList]') {
/**
* Перебираем всех родителей и нужные
* дочерние элементы переносим в массив
*/
for (var i = 0; i < context.length; i++) {
/** Получаем список элементов из родительского */
var Elements = context[i].querySelectorAll(selector);
/** Переносим список элементов в массив */
jQuery.merge(array, Elements);
};
/** Выводим массив */
return array;
/** Если аргумент context является селектором */
} else if (typeof context === 'string') {
/** Получаем все родительские элементы
* по селектору
*/
var Parents = document.querySelectorAll(context);
/**
* Перебираем всех родителей и нужные
* дочерние элементы переносим в массив
*/
for (var i = 0; i < Parents.length; i++) {
/** Получаем список элементов из родительского */
var Elements = Parents[i].querySelectorAll(selector);
/** Переносим список элементов в массив */
jQuery.merge(array, Elements);
};
/** Выводим массив */
return array;
/**
* Если ни одни из вариантов не подходит,
* то осуществляем обычный поисх элементов
*/
} else {
/** Получаем список элементов по селектору */
var Elements = document.querySelectorAll(selector);
/** Выводим массив */
return jQuery.merge(array, Elements);
}
};
/** Далее проверяем, не является ли первый аргумент DOM элементом */
} else if (selector.nodeType) {
/** Вставляем элемент в массив */
array[0] = selector;
/** Выводим основной массив */
return array;
/**
* Следующая проверка будет на ситуацию
* если первый аргумент является массивом
*/
} else if ({}.toString.call(selector) === '[object Array]') {
/** Объединям массивы */
jQuery.merge(array, selector);
/** Выводим основной массив */
return array;
/**
* Следующей проверкой будет вариант,
* когда первый аргумент является объектом
*/
} else if ({}.toString.call(selector) === '[object Object]') {
/** Вставляем объект в массив */
array[0] = selector;
/** Выводим основной массив */
return array;
/** Теперь проверяем, может это живой массив элементов? */
} else if ({}.toString.call(selector) === '[object HTMLCollection]' || {}.toString.call(selector) === '[object NodeList]') {
/** Переносим элемент из живого массива в основной массив */
jQuery.merge(array, selector);
/** Выводим основной массив */
return array;
/** Если первый аргумент — это функция */
} else if ({}.toString.call(selector) === '[object Function]') {
/** Запускаем функции после загрузки DOM дерева */
document.onreadystatechange = function(){
if(document.readyState === 'complete'){
selector();
};
};
/** Вернём эту же функцию */
return selector;
/** Если же ни однин тип не подошёл, то выводим пустой массив */
} else {
return array;
}
};
jQuery.merge = function (first, second) {
var len = +second.length, /** Я не знаю зачем они преобразуют second.length в число, если по идее, это и так число */
j = 0,
i = first.length;
/** Перебираем значения второго массива */
for (; j < len; j++) {
/**
* Добавляем новое значение в первый массив
* и за одно плюсует счётчик количества
* элементов в первом массиве
*/
first[i++] = second[j];
}
/** Записываем новое количество элементов в массиве */
first.length = i;
/** Выводим готовый массив */
return first;
};
jQuery.parseHTML = function (data, context, keepScripts) {
/** Если к нам отправили не строку, то вернуть пустой массив */
if (typeof data !== 'string') {
return [];
}
/**
* Мы принимаем свойство context
* как keepScripts, если в нём
* указан true/false
*/
if (typeof context === 'boolean') {
keepScripts = context;
context = false;
};
var DOM, DOMList;
if (!context || context === document) {
/** Создаём HTML страницу */
DOM = document.implementation.createHTMLDocument() || new DOMParser().parseFromString('', 'text/html');
/** Внесём в него HTML строку */
DOM.body.innerHTML = data;
/** Получаем наше пропарсенное содержимое */
DOMList = DOM.body.childNodes;
} else if (context.nodeType) {
/** Создаём новый элемент, через который будем парсить код */
DOM = document.createElement('div');
/** Добавляем этот элемент в целевой элемент */
context.appendChild(DOM);
/** Добавляем содержимое в элемент */
DOM.innerHTML = data;
/** Получаем список элементов */
DOMList = DOM.childNodes;
/** Удаляем DOM */
DOM.parentNode.removeChild(DOM);
};
/** Если нужно запустить скрипт, указанный в коде */
if (keepScripts === true) {
/** Получаем список скриптов */
var scripts = DOM.scripts || DOM.querySelectorAll('script');
/** Проверяем наличие файлов скрипта */
if (scripts.length > 0) {
/** Перебираем все скрипты */
for (var i = 0; i < scripts.length; i++) {
/** Создаём новый элемент скрипта */
var script = document.createElement('script');
/** Передаём новосозданному элементу скрипта все параметры */
script.innerHTML = scripts[i].innerHTML;
if (scripts[i].attributes) script.attributes = scripts[i].attributes;
/** Добавляем скрипт в HEAD для запуска */
document.head.appendChild(script);
/** Удаляем скрипт из HEAD */
script.parentNode.removeChild(script);
};
};
};
/** Перебираем значения в обычный массив и выводим */
return (function () {
var array = [];
for (var i = 0; i < DOMList.length; i++) {
array[array.length] = DOMList[i];
};
return array;
})();
};
Теперь мы можем использовать функцию, как в обычном jQuery и писать для его объектов новые функции через прототип.
jQuery.prototype.myFunction = function () {...};
На этом я закончу эту статью. Главное помнить: jQuery тоже написан на JavaScript :)
Автор: Yuri Spivak
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/262436
Ссылки в тексте:
[1] habrahabr.ru/post/164533: https://habrahabr.ru/post/164533/
[2] Array.concat();
: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
[3] Sizzle: https://github.com/jquery/sizzle/
[4] DOMContentLoaded
: https://developer.mozilla.org/ru/docs/Web/Events/DOMContentLoaded
[5] Источник: https://habrahabr.ru/post/335958/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.