5 малоизвестных особенностей jQuery-методов

jQuery является самой популярной JavaScript-библиотекой в мире, но об этом-то мы все хорошо осведомлены. В последнее время jQuery стала получать очень много критических замечаний, но поток внимания к ней со стороны разработчиков от этого не ослабел.

Как новички, так и матёрые JavaScript-кодеры могут порой упустить какую-то незаметную, но существенную деталь в работе этой сложной и объёмной системы.

Давайте же рассмотрим пять моментов, не слишком хорошо освещённых в документации jQuery.

Возврат false обработчиками событий, назначенными через jQuery

Как известно, важнейшей задачей jQuery была унификация работы с браузеро-зависимыми функциями с помощью набора универсальных методов. jQuery по мере возможности дополняет средства, не реализованные в определённых браузерах.

Например, только благодаря jQuery разработчики могут использовать в IE 6 и 7 селекторы атрибутов вроде “:not”, “:last-child” и так далее.

Иногда (и эти исключения действительно редки) реализация некоторых методов в jQuery отличается от стандарта. Например, если обработчик, назначенный вами событию через функцию on(), возвращает значение false, это эквивалентно вызовам:

event.stopPropagation();
event.preventDefault();

Если же значение false вернёт обработчик, назначенный по умолчанию, например, через вызов addEventListener(), это будет работать как простое:

event.preventDefault();

Причину такого поведения легко понять, заглянув в исходники jQuery:

if ( ret !== undefined ) {
   if ( (event.result = ret) === false ) {
      event.preventDefault();
      event.stopPropagation();
   }
}

Псевдо-селекторы делают больше, чем кажется

В документации jQuery ко многим псевдо-селекторам сказано примерно следующее (на примере:checkbox”):

Вызов $(“:checkbox”) эквивалентен вызову $(“[type=checkbox]”). Как и в случае использования других псевдо-селекторов класса (селекторы, начинающиеся с “:”), рекомендуется предварять их именем тэга или другим селектором, иначе подразумевается наличие перед ними универсального селектора “*”. Другими словами, $(“:checkbox”) эквивалентен $(“*:checkbox”), так что используйте $(“input:checkbox”).


Снова заглянем в исходный код jQuery:

function createInputPseudo( type ) {
    return function( elem ) {
        var name = elem.nodeName.toLowerCase();
        return name === "input" && elem.type === type;
    };
}

Как видите, документация в этом случае не совсем корректна. $(“:checkbox”) на самом деле эквивалентен $(“input:checkbox”) в плане предмета поиска (обратите внимание наname === «input”), но в плане области поиска он соответствует $(“*:checkbox”).

Учитывая это, мы можем поддаться искушению использовать псевдо-селекторы обособленно, не задумываясь о том, чем бы их предварить:

var $checkboxes = $(':checkbox');

Но ради производительности стоит дисциплинировать себя и использовать рекомендованные конструкции, а не заставлять jQuery сканировать каждый элемент на странице:

var $checkboxes = $('input:checkbox');

jQuery.type()

Вы наверняка осведомлены о существовании метода jQuery.type(), позволяющего определить JavaScript-класс любого объекта.

Особенность данного метода заключается в том, что результат его вызова не совсем эквивалентен результату работы JavaScript-оператора typeof. Фактически jQuery может рассказать об объекте больше, чем стандартные средства JavaScript. Давайте убедимся в этом на примере:

// prints object
console.log(typeof null);

// prints object
console.log(typeof [1, 2, 3]);

// prints object
console.log(typeof new Number(3));

А теперь – при помощи jQuery:

// prints null
console.log($.type(null));

// prints array
console.log($.type([1, 2, 3]));

// prints number
console.log($.type(new Number(3)));

Так что теперь, разрабатывая свою функцию, вы можете использовать в ней jQuery.type(), чтобы лучше понять, какие аргументы ей переданы.

attr() может работать, как removeAttr()

Звучит странно, но это правда. Метод attr() обычно используется для чтения значения атрибутов, точнее говоря, он возвращает значение заданного атрибута первого элемента в наборе подходящих элементов. Если же методу attr() передан второй параметр (value), он устанавливает в него значения соответствующих атрибутов у всех элементов набора.

Вы можете игнорировать эту особенность, но вторым параметром может быть не только число, строка или объект, но также и null. В последнем случае attr() работает так же, как его противоположность – removeAttr(), то есть удаляет соответствующие атрибуты у всего набора подходящих элементов.

Не верите? Снова загляните в исходники:

attr: function( elem, name, value ) {
    ...
    if ( value !== undefined ) {

        if ( value === null ) {
            jQuery.removeAttr( elem, name );
    ...
}

Как видите, в реализацию метода встроена явная проверка аргумента value на значение null, и в случае совпадения вызывается removeAttr().

Такая особенность может прийтись очень кстати, если вам необходимо добавить или удалить свойство в зависимости от какого-то условия. Например, вы можете написать:

$(selector).attr(anAttr, condition ? value : null);

вместо:

condition ? $(selector).attr(anAttr, value) : $(selector).removeAttr(anAttr);

Но я на вашем месте не стал бы злоупотреблять этим трюком, потому что он, во-первых, снижает читаемость вашего кода. А, во-вторых, это поведение attr() является недокументированным. По крайней мере, пока. На этот счёт уже давно ведётся дискуссия.

Превращение массивоподобных объектов в массивы

Как вы знаете, в JavaScript существуют типы данных, например, nodeList или arguments, которые похожи на массивы, но не являются таковыми. Это значит, что к их элементам можно обращаться при помощи соответствующей нотации (как arguments[0]), но к ним нельзя применить методы вроде forEach() или join().

Допустим, у нас есть список элементов DOM (nodeList), полученный как:

var list = document.getElementsByClassName('book');

и нам хотелось бы обойти его при помощи forEach(). Но если мы попробуем использовать list.forEach(), то получим ошибку: «Uncaught TypeError: undefined is not a function». Чтобы избежать этой проблемы, довольно часто используется следующая техника:

Array.prototype.forEach.call(list, function() {...});

Вместо этого можно использовать:

[].forEach.call(list, function() {...});

Как видите, оба этих решения одинаково неудобочитаемы. К счастью, jQuery может помочь нам облагородить данный код. Давайте используем метод jQuery.makeArray():

$.makeArray(list).forEach(function() {...});

Гораздо понятнее, не правда ли?

Заключение

Помимо рассмотренных нами пяти частных случаев работы с jQuery, вы должны вынести из этой статьи очень важный общий вывод: даже такие стабильные и масштабные проекты, как jQuery, по сути, неидеальны. Программные баги и недоработки документации могут встретиться нам в любой момент.

И самым надёжным описанием работы конкретного метода является его исходный код. Конечно, иногда и сам код может лгать, делая совсем не то, что подразумевал его автор. Но это уже другая история.

Ещё один важный вывод: вы как разработчик должны хорошо знать свои инструменты. Изучайте фреймворки и библиотеки, которые используете, не бойтесь заглядывать в их код, используйте любой шанс поучиться у других программистов их приёмам и техникам.

И в заключение хочу сказать следующее: если вам, как и мне, нравится jQuery, постарайтесь быть полезным этому проекту. Сообщайте разработчикам об ошибках в коде и документации. Даже самая скромная правка может помочь миллионам ваших коллег.

Перевод статьи «5 Little Known Details About jQuery Methods» был подготовлен дружной командой проекта Сайтостроение от А до Я.