JavaScript с нуля — страница 17 из 30

Из вежливости можно сказать, что все типы одинаково интересны и занятны, но и вы, и я знаем, что это неправда. Некоторые из них весьма скучны. Одним из таких примеров является логический тип данных, и вот почему. Мы создаем логический тип каждый раз, когда инициализируем переменную, используя true либо false:

let sunny = false;

let traffic = true;

Примите мои поздравления! Если вам это известно, то вы уже на 80 % достигли полного понимания функционирования логических типов. Конечно, если задуматься, то 80 % недостаточно. Это как есть хот-дог без соуса, уйти с концерта, не дождавшись выхода на бис, или не дописать предложение.

Мы же собираемся заполнить эти недостающие 20 %, которые состоят из различных особенностей логических типов, объекта Boolean, функции Boolean и очень важных операторов === и!==.

Поехали!

Объект Boolean

Логические типы рождены для использования в качестве примитивов. Я не стану бороться с ленью и просто приведу пример, который вы только что видели, чтобы продемонстрировать, как выглядит этот примитив:

let sunny = false;

let traffic = true;

Как вы уже видели много раз, в тени каждого примитива скрывается его объектная форма. Создание логического объекта происходит с помощью ключевого слова new, имени конструктора Boolean и начального значения:

let boolObject = new Boolean(false);

let anotherBool = new Boolean(true);

В виде начального значения вы можете передать логическому конструктору либо true, либо false. Но при этом вы вполне можете передать и нечто иное, что в итоге будет вычислено как true или false. Расскажу немного о том, какие виды значений будут предсказуемо становиться true или false, но относительно этого подхода есть обязательное предостережение: используйте логические объекты только в исключительных случаях, в остальных старайтесь придерживаться примитивов.

Логическая функция

Конструктор Boolean предоставляет одну существенную выгоду, которая связана с возможностью передачи любого произвольного значения или выражения в процессе создания объекта Boolean:

let boolObject = new Boolean(< arbitrary expression >);

Выгодно же это, потому что вам может понадобиться вычислить логическое выражение, в котором итоговые данные оказываются не чистыми true или false. Это особенно актуально, когда вы имеете дело с внешними данными или кодом и не контролируете получение значения false или true. Вот пример из головы:

let isMovieAvailable = getMovieData()[4];

Значение isMovieAvailable, вероятно, true или false. Когда дело доходит до обработки данных, у вас зачастую нет уверенности, что в какой-то момент что-либо вдруг не даст сбой или не вернет иное значение. Как и в реальной жизни, простая вера в то, что все будет работать как надо, неразумна, если не предпринять действенные меры. Одной из таких мер и является функция Boolean.

Создание специальной функции для разрешения двусмысленности может быть излишним, но у конструктора Boolean есть побочный эффект — у вас остается логический объект, что нежелательно. К счастью, есть способ получить гибкость конструктора Boolean совместно с легковесностью логического примитива, причем достаточно легко. Этот способ основывается на функции Boolean:

let bool = Boolean(true);

Логическая функция позволяет передавать произвольные значения и выражения, при этом по-прежнему возвращая примитивное логическое значение true либо false. Главным же отличием этого подхода от использования конструктора является то, что вы не используете ключевое слово new. Как бы то ни было, давайте приостановимся и рассмотрим, что именно вы можете передать в логическую функцию. Имейте в виду, что все это также можно передавать в логический конструктор, который мы видели в предыдущем разделе.

Для возвращения false вы можете передать следующие значения: null, undefined, пусто или ничего, 0, пустую строку и, конечно же, false:

let bool;


bool = Boolean(null);

bool = Boolean(undefined);

bool = Boolean();

bool = Boolean(0);

bool = Boolean("");

bool = Boolean(false);

Во всех этих примерах переменная bool вернет false. Чтобы вернуть true, мы можем передать значение true или что угодно, что не приведет к одному из перечисленных выше значений false:

let bool;


bool = Boolean(true);

bool = Boolean("hello");

bool = Boolean(new Boolean()); // Внедрение!!!

bool = Boolean("false"); // "false" — это строка

bool = Boolean({});

bool = Boolean(3.14);

bool = Boolean(["a", "b", "c"]);

В этих примерах переменная bool вернет true. Это может показаться немного странным, учитывая некоторые варианты инструкций, поэтому давайте обратим внимание на имеющиеся нюансы. Если то, что мы вычисляем, является объектом, как new Boolean(new Boolean()), то вычисляться всегда будет true. Причина в том, что простое существование объекта уже приводит к срабатыванию true, а вызов new Boolean() создает именно новый объект. Если дополнить логику происходящего, это означает, что следующая инструкция if также будет вычислена как true:

let boolObject = new Boolean(false);


if (boolObject) {

console.log("Bool, you so crazy!!!");

}

При этом не важно, если вычисляемый нами объект скрывает в себе значение false… или объект String или Array и т. д. Правила, касающиеся примитивов, гораздо проще. Если мы передаем примитив (или то, что вычисляется как примитив), то все, за исключением null, undefined, 0, пустой строки, NaN или false, будет вычисляться как true.

Операторы строгого равенства и неравенства

Последнее, что мы рассмотрим, объединит наши знания о типах, в том числе и логических, и привнесет разнообразие в условные операторы, изученные ранее. Итак, мы знаем об операторах == и!= и, вероятно, видели их пару раз в деле. Это операторы равенства и неравенства, которые позволяют понять, являются ли два элемента равными или нет. А вот и сюжетный поворот. Они демонстрируют утонченное, отклоняющееся от нормы поведение, о котором мы можем не знать.

Вот пример:

function theSolution(answer) {

if (answer == 42) {

console.log("You have nothing more to learn!");

}

}


theSolution("42"); // 42 передано как строка

В этом примере выражение answer == 42 будет вычислено как true. Так происходит несмотря на то, что переданное значение 42 является строкой, мы же производим сравнение с 42, являющимся числом. Что здесь происходит? Неужели мы попали в мир, где числа и строки равны? При использовании операторов == и!= такое поведение вполне ожидаемо. В этом случае значением обоих сравниваемых элементов будет 42. Для этого JavaScript осуществляет нужные операции, и оба значения в итоге рассматриваются как одинаковые. Формально это называется приведением типа.

Проблема в том, что такое поведение иногда мешает — особенно когда так происходит у нас за спиной. Во избежание подобных ситуаций у нас есть более строгие версии операторов равенства/неравенства, а именно === и!== соответственно. Задача этих операторов заключается в сравнении как значения, так и типа. При этом они не делают приведения типов. Они ведут к тому, что все заботы по обеспечению равенства или неравенства ложатся непосредственно на нас, и это хорошо.

Теперь давайте исправим предыдущий пример, заменив оператор == на ===:

function theSolution(answer) {

if (answer === 42) {

console.log("You have nothing more to learn!");

}

}


theSolution("42"); // 42 передано как строка

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

Общее правило гласит: всегда используйте более строгую форму операторов равенства/неравенства. Помимо всего прочего, их использование поможет обнаруживать ошибки в коде — ошибки, которые в противном случае может быть сложно распознать.

Если мы сравниваем два разных объекта, то строгий оператор равенства (и менее строгий тоже) не будет работать ожидаемым образом. Например, все приведенные ниже случаи будут вычислены как false:

console.log(new String("A") == new String("A"));

console.log([1, 2, 3] == [1, 2, 3]);

console.log({ a: 1 } == { a: 1 });

Имейте это в виду при выяснении равенства/неравенства двух отдельных самостоятельных объектов.

КОРОТКО О ГЛАВНОМ

Логические типы являются одними из наиболее часто используемых типов при написании кода. Несмотря на внешнюю простоту, они играют ключевую роль в разветвлении кода. Хоть я и могу посчитать на одной руке количество раз, когда мне приходилось использовать функцию Boolean или строгие операторы равенства и неравенства, мне не хватит рук и пальцев, чтобы счесть все случаи, когда я сталкивался с этими странными вещами в сторонних проектах.

Если у вас возникнут вопросы, добро пожаловать на форум https://forum.kirupa.com.

Глава 22. null и undefined

Одна из величайших загадок мира JS витает вокруг null и undefined. Зачастую код буквально напичкан этими значениями, и вы, возможно, уже с ними встречались. Но как только спадает завеса тайны, оказывается, что null и undefined не такое уж странное явление. Они просто ужасно скучные. Возможно, скучнейшие (но важные) элементы JavaScript из всех, с какими вам когда-либо предстоит познакомиться.

Поехали!

Null

Начнем с null. Ключевое слово null — это примитив, который выполняет особую роль в мире JavaScript. Он является явным определением, обозначающим отсутствие значения. Если вам доводилось просматривать чужой код, то, вероятно, вы видели, что null встречается достаточно часто. Этот элемент весьма популярен, так как имеет преимущество в виде определенности. Вместо работы с переменными, содержащими устаревшие значения или таинственные неопределенные значения, вы можете установить их как null, однозначно указав, что значение существовать не должно.

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

Вот пример:

let name = null;


if (name === null) {

name = "Peter Griffin";

} else {

name = "No name";

}

Примитив null не появляется сам собой. Его вы присваиваете сознательно, поэтому предстоит часто встречаться с ним в объявлениях переменных или среди аргументов, передаваемых в вызовы функций. Использовать null легко. Проверить его наличие также несложно:

if (name === null) {

// делает что-нибудь интересное или нет

}

Имейте в виду, что при этом нужно использовать более строгий оператор === вместо ==. Хоть от использования == конец света и не наступит, но при работе с null лучше производить проверку как значения, так и типа.

Undefined

А вот здесь уже интереснее. Чтобы представить что-то, что не определено, вы используете примитив undefined. Он пригождается в нескольких случаях. Чаще всего это происходит, когда вы пытаетесь обратиться к переменной, которая не была инициализирована или когда обращаетесь к значению функции, которая ничего не возвращает.

Следующий фрагмент кода приводит несколько реальных случаев с undefined:

let myVariable;

console.log(myVariable); // undefined


function doNothing() {

// watch paint dry

return;

}


let weekendPlans = doNothing();

console.log(weekendPlans); // undefined


let person = {

firstName: "Isaac",

lastName: "Newton"

}

console.log(person.title); // undefined

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

if (myVariable === undefined) {

// делает что-нибудь

}

Оборотная сторона этого подхода связана с истинной природой undefined. Держитесь крепче: undefined — это глобальная переменная, которая определяется за нас автоматически. Это означает, что потенциально мы можем ее переопределить, например, на true или что-либо другое, что нам нужно. Если undefined будет переопределена, то нарушит работу кода в случае проверки только с оператором === или ==. Чтобы избежать подобного безобразия, наиболее безопасным способом выполнения проверки на undefined будет использование typeof и затем уже оператора ===:

let myVariable;


if (typeof myVariable === "undefined") {

console.log("Define me!!!");

}

Это гарантирует выполнение проверки на undefined и возвращение верного ответа.

NULL == UNDEFINED, но NULL!== UNDEFINED

Продолжая тему странности == и ===: если вы когда-нибудь проверите null == undefined, то ответом будет true. Если же вы используете ===, то есть null === undefined, то ответом будет false.

Причина в том, что == производит приведение, присваивая значениям такие типы, какие JS посчитает целесообразными. Используя ===, вы проверяете и тип, и значение. Это уже полноценная проверка, которая определяет, что undefined и null на деле являются двумя разными вещами.

Монету в шляпу шестиглазому (то есть Тревору Маккаули) за то, что указал на это!

КОРОТКО О ГЛАВНОМ

Я неспроста отложил напоследок эти встроенные типы. null и undefined — наименее интересные члены коллектива, но при этом зачастую самые недопонятые. Умение использовать null, а также обнаруживать его и undefined — это очень важные навыки, которыми следует овладеть. Иначе вы рискуете столкнуться с ошибками, которые будет очень сложно обнаружить.

Если у вас появились вопросы о null и undefined или вы просто хотите пообщаться с самыми дружелюбно настроенными разработчиками на планете, пишите на https://forum.kirupa.com.

ЧАСТЬ III. DOM, милый DOM