Существуют ли в природе объекты, полученные функцией getElementById и не имеющие свойства style? Что надо проверять при работе скрипта, чтобы не было сбоев? Всегда ли надо предотвращать фатальные ошибки?

Проверка условий в Javascript

Веб-программист постоянно вынужден что-то проверять: правую кнопку мыши нажал пользователь или левую, заполнено ли в форме поле «Поиск», есть ли на странице элемент с id = "console"... Иногда это выбор, требуемый самой сутью программы – например, поле ввода типа радио, в котором пользователь должен указать свой пол («мужской», «женский», «не знаю»). Выбор такого типа обычно не доставляет хлопот (потому что он лежит на поверхности, работа с ним очевидна).

Более сложным является выбор другого типа: быть или не быть (продолжать работу программы или по-тихому смыться). Этот выбор сложен потому, что у программиста нет очевидных внешних сигналов, а он сам должен догадаться в определённых точках проверять некие критические условия – чтобы у пользователя на странице не вывалилось сообщение об ошибке. Самый простой пример. Вы пишете:

Но на странице может не быть элемента с id = "msg" (может, страница ещё просто не загрузилась до конца, а может, вы вообще не знаете, что это за страница – запускаете скрипт из какого-нибудь букмарклета). И тогда весь Javascript зависнет. Поэтому вы должны догадаться добавить здесь элементарную проверку:

Пример 1. Простая защита от сбоя.

Начиная изучать какой-нибудь скриптовый язык веб-программирования (не обязательно JavaScript), школьники терпеливо выводят именно такие конструкции типа if (el != null), if (myCheckbox.checked != true) или if ($b == 0). Позже, после изучения примеров старших товарищей, студенты с удивлением узнают, что во всех этих случаях можно однообразно и кратко обойтись простыми if (el), if (!myCheckbox.checked) или if (!$b). Никто особо не задумывается, что же именно отрицает оператор в выражении !$b: то ли наличие у переменной вещественного значения, то ли существование самой пременной, – и чаще всего это и неважно.

Проверка критичных условий

Самое интересное в такой проверке (как и вообще в программировании) – найти наиболее оптимальное (короткое и эффективное) решение. В Примере 1 мы привели простое (наиболее очевидное) решение. Более короткое придумать сложно, но достаточно ли оно эффективно? Ведь взгляд более-менее опытного JS-разработчика сразу начнёт тревожно метаться между el и color – там дыра, там ещё одно свойство style, наличие которого у объекта el не проверяется. В общем случае такая конструкция может вызвать ошибку:

Напрашивается следующее изменение примера:

Пример 1а.

Так вот, у данной статьи две задачи: 1) показать, как все эти проверки можно закодировать получше, покрасивее; 2) понять, надо ли оно вообще (так много проверок). Следуя первой задаче, поэтапно меняем отображение сочетания условий:

Пример 1б.

Пример 1в.

То есть упрощаем проверку для программиста и усложняем для исполняющей системы: if (el) означает: if (el != "" && el != 0 && el != false && el != null && el != undefined). В большинстве случаев это прекрасно работает. Правда, крутые теоретики иногда используют проверку только на undefined, считая, что со всеми остальными значениями (типа null или false) функция справится нормально (а без них, видимо, пропадёт). В нашей практике таких случаев пока не было, но если появятся, мы вам обязательно сообщим.

А теперь, следуя задаче 2, включаем голову: в нашем конкретном примере – зачем вообще проверять наличие свойства style? Разве существуют в природе объекты, полученные функцией getElementById и не имеющие свойства style? В данном случае сама функция, которую мы используем, защищает нас: она может вернуть только HTML-элементы, у которых есть свойство id, а значит, и свойство style. Таким образом, предельное упрощение проверки приводит пример к следующему виду:

Пример 1г.

Да. И ещё нам повезло в том, что атрибуту style программно присвоить никакое значение нельзя. А то какой-нибудь шутник мог бы написать в середине кода el.style = undefined;, после чего, разумеется, обращение к el.style.color всё-таки обрушило бы Javascript в текущем окне.

Что проверять?

В Примере 1 предельно упростить проверку позволяет функция getElementById – с её помощью можно получить объекты достаточно предсказуемые. Такое везение, конечно, бывает редко. Даже в чуть более сложном способе получения элементов с веб-страницы – getElementsByTagName – для избежания фатальных ошибок уже требуется включать мозг.

Вообще-то эта последняя функция тоже должна давать предсказуемые объекты. Ну, по крайней мере, те, у которых есть tagName. Но тут могут быть варианты. Функция getElementsByTagName возвращает объект вида NodeList, и если получать все свойства этого объекта, как положено по теории, с помощью for (var prop in obj) {}, то, кроме найденных элементов, мы получим также и другие свойства объекта NodeList. Какие именно, можно увидеть с помощью двух следующих функций:

Пример 2. Свойства NodeList.

В Firefox'е (ФФ) это будут свойства: length, item, namedItem, BODY, в Интернет Эксплорере (ИЕ): BODY, length. Списки элементов типа NodeList обычно получают для того, чтобы выбрать из них элементы по какому-то условию. Например, это все поля формы, которые пользователь забыл заполнить – тогда условие будет if (el.value != "") (или, после оптимизации, просто: if (el.value)):

field1: field2:

Пример 3. Проверка формы на (el.value).

Проверка в ИЕ показывает, что не заполнено поле field2 и «поле» undefined, в ФФ ещё два именованных свойства. Скрипт не вылетает, но и правильной его работу назвать нельзя. Забавно, что правильную работу в данном случае можно получить, вернув изначально задуманную точную проверку if (el.value != "") (точнее, if (el.value == "")); потому что !el.value означает как пустую строку, так и отсутствие у объекта свойства value – вот туда и попадает всё подряд; а сравнение с пустой строкой предполагает обязательное наличие самого свойства, которого нет в объекте length, поэтому для этого объекта конструкция if (el.value == "")) вернёт false точно так же, как и для объекта с заполненным свойством value.

Но ясно, что всё это держится на соплях. Например, проверить длину поля уже будет нельзя, так как обращение к el.value.length при отсутствующем свойстве value остановит скрипт. Откуда вообще взялись эти «левые» свойства у объекта NodeList? Гуру говорят, что из прототипа: у самого полученного объекта свойства length нет, но при попытке получить свойства через for (var prop in obj) выдаются свойства не только самого объекта, но и его прототипа. Гуру говорят, надо проверять, самого ли объекта это свойство с помощью функции hasOwnProperty, и лишние выкидывать:

Вот только ИЕ говорит: «Объект не поддерживает этот метод». И сами гуру им ни черта не пользуются. Гуру, чтобы не забивать мозг, предпочитают тупую и надёжную цепочку проверок:

Да, и «производственники» (зарабатывающие на больших и сложных проектах) вообще не используют такой способ (for (var prop in obj)) получения элементов из NodeList – коль уж у него (или у его прототипа) есть свойство length, можно избежать лишних проблем, получая элементы с помощью конструкции, предназначенной для работы с массивами:

Так Javascript становится неправильным, надёжным и неинтересным. Шутка. В данном случае тупая надёжность перевешивает теоретическую «правильность» и уж конечно «интересность». Есть масса других интересных проблем, которым можно начать уделять внимание, забыв о головной боли, связанной с (var prop in obj).

Проверяй то, к чему обращаешься

В давние времена разработчики любили определять, каким браузером пользуются пользователи. Что-нибудь вроде: isIE = /msie/i.test(navigator.useragent). А потом вставлять полученный результат в концепции и функции: if (isIE) window.event.returnValue = false;.

Это порочно и неправильно – проверять одно, а потом на основе этого определять другое. Хорошие разработчики теперь так не делают. Они проверяют прямо то, с чем предстоит иметь дело. Неважно, is_IE или не is_IE, важно, существует ли объект window.event. Поэтому правильно делать так: if (window.event) window.event.returnValue = false;. А ещё лучше учитывать сразу и другие модели DOM, как учит Илья Кантор (хотя и другие тоже учат, но я лично усмотрел идею именно на его сайте):

Как часто надо проверять объекты, к которым обращаешься? Это довольно больной вопрос. Кажется, что можно сэкономить мыслительные усилия, проверяя всё всегда и с избытком. Но нет! Например, простая функция добавления класса HTML-элементу:

С первого взгляда видно, что там какой-то левый (не native) hasClass(). А что если его нет в скрипте (забыли добавить)? Надо проверять? Нет. Потому что функции addcClass, hasClass и delClass изначально находятся в одном файле-библиотеке ir2.js, а если смелый экспериментатор выдерет оттуда какой-то кусок, значит, он очень умный и знает, что делает (например, добавит этот кусок к своей библиотеке, в которой есть аналоги коррелирующих функций). И ему только помешает «смягчающая» («молчаливая») обработка if (!hasClass) return: труднее будет понять, почему функция не работает (всё тихо, ошибки нет, а класс не добавляется!); здесь фатальная ошибка полезна!

Другое дело – обращение к аргументу функции. Если функция, как это обычно бывает, вызывается из другой функции (а не по нажатию кнопки пользователем), может так случиться, что та, другая функция передаст в addClass неправильный параметр – например, неправильно вычисленный, ненайденный (пустой) obj. Тут остановка JS во время отладки (той, другой, вызывающей функции) иногда может сильно достать, поэтому «silent»-проверка if (!obj) return здесь очень облегчает работу.

Выводы

  1. Проверять «на валидность» (защищать работу скрипта, чтобы не сдох JS) нужно именно те объекты, которые собираетесь использовать прямо сейчас.
  2. Самая простая и надёжная защита – проверка «на существование» всех вышестоящих свойств в обратном порядке, вида if (el && el.value && el.value.length).
  3. Фатальные ошибки не всегда нужно обрабатывать: разработчик должен видеть, когда со скриптом что-то по-настоящему не так.
D.M., admin

Комментарии

Дмитрий 10.05.2014 22:35:44

Подскажите а как сделать проверку на наличие символов. Ну например. phone_1 = "{$goods.phone_1|escape}"; /Присвоим заначение переменной/

Если есть значения то показать <div id="msg">Блабалабала</div>

Если нет то не показываем.

Дмитрий 10.05.2014 22:37:29

Подскажите а как сделать проверку на наличие символов.

Ну например. phone_1 = "{$goods.phone_1|escape}"; /Присвоим заначение переменной/

Если есть значения то показать <div id="msg">Блабалабала</div>

Если нет то не показываем. <div id="msg">Блабалабала</div>