В одном из прошлых уроков мы говорили об ошибках и как с ними справляться. Есть несколько видов ошибок, и я хочу напомнить об одном конкретном виде. Вот небольшой фрагмент того урока:
Взгляните на этот код:
const length = 12;
const num = length(54);
Сначала мы создали константу. Помните, что это как давать чему-то название: в нашем случае — числу 12 даётся название length
. В следующей строке мы вызываем функцию length
и передаём ей аргумент — число 54. Но подождите! length
— это не функция! Это всего лишь число. Числа — это не функции, не ящики, которые производят какие-то действия. И JavaScript пожалуется именно на это:
→ node test.js
/Users/rakhim/test.js:2
const num = length(-54);
^
TypeError: length is not a function
at Object.<anonymous> (/Users/rakhim/test.js:2:13)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.runMain (module.js:605:10)
at run (bootstrap_node.js:420:7)
at startup (bootstrap_node.js:139:9)
at bootstrap_node.js:535:3
Это Ошибка типизации: тип объекта, который вы использовали, неверный. Интерпретатор JavaScript не скажет чем что-то является, но точно скажет чем оно не является. length
— это не функция.
Ошибка типизации — это как просить кошку постирать бельё. Возможно, вы хотели попросить об этом вашего друга.
В программировании "типизация" — это классификация информации. Это общий термин и разные языки программирования справляются с типизацией по-разному. Как вы уже знаете, JavaScript умеет отличать типы. Функция — это один тип, Число — другой, и вы не можете просто использовать число как функцию.
typeof
— это специальный оператор, который возвращает строку, в которой написан тип.
typeof 42; // 'number'
typeof 3.14; // 'number'
typeof NaN; // 'number'
typeof 'Berry'; // 'string'
typeof true; // 'boolean'
typeof false; // 'boolean'
42 и 3.14, очевидно, числа, несколько комбинаций букв в кавычках — строка, а true и false — булево значение. Всё это — типы в JavaScript — число, строка и булево значение.
NaN означает — "не число", но тип NaN — это "число". Да, я знаю. Еще одна странность JavaScript. Такие правила в этом языке.
Типизация полезна. Когда мы попытаемся запустить число, как будто это функция, JavaScript начнёт жаловаться и мы увидим ошибку и починим её. Если бы никакого обозначения типов в JavaScript не было, мы бы сталкивались либо с каким-нибудь аномальным поведением, либо с мистической ошибкой. Вместо чёткого "length — это не функция", мы бы видели что-то вроде "I'm sorry Dave, I'm afraid I can't do that".
А что, если создать переменную, но не задать ей никакого значения? Какой в этом случае будет тип? Это ни число, ни строка, ничто... Потому что нет значения, правильно?
JavaScript в этом случае кое-что делает в тайне от вас. Переменная без значения на самом деле имеет специальное значение — "undefined". И тип такой переменной называется "undefined".
let a;
console.log(a); // undefined
typeof a; // 'undefined'
Например, тип number
имеет множество потенциальных значений: 1, 2, -10, 69000 и другие числа. А тип undefined
только одно — undefined
.
Когда дело касается типизации в программировании, важно различать две концепции: динамическая против статической и слабая против сильной.
Чтобы понимать разницу между динамической и статической типизацией, нам сначала нужно посмотреть как написанные программы становятся запущенными программами.
Код, который вы пишете, обычно конвертируется в понятную для запуска компьютером форму. Этот процесс называется компиляцией, а промежуток времени, за который это происходит — "стадией компиляции" или compile time.
После того, как компиляция закончена и программа запущена, начинается отсчёт времени, который называется "стадией исполнения" или run time.
Некоторые языки проверяют типы и ищут ошибки типизации на стадии компиляции. У них статическая типизация.
Другие языки проверяют типы и ищут ошибки типизации на стадии исполнения. Такая типизация — динамическая.
Иными словами: статическая типизация означает проверку типов перед запуском программы, динамическая — проверку типов, когда программа запущена.
C#, C++, Java, Go — статически типизированные языки. Если в одном из этих языков вы создадите число и попытаетесь проводить с ним операции, как с функцией, вы получите ошибку во время компиляции, а программа не станет запускаться — она даже не дойдёт до этой стадии, потому что ошибка типизации будет обнаружена перед исполнением, в период компиляции.
JavaScript, Ruby, PHP — динамически типизированные языки. Как вы видели раньше, если использовать неверную типизацию, ваша программа запустится, а ошибка обнаружится только когда будет исполняться конкретная строчка кода. Здесь типы проверяются в период исполнения.
Вообще-то, в JavaScript обычно нет никакой компиляции, но это тема другого урока.
Динамическая типизация не хуже и не лучше статической. Оба способа имеют свои преимущества и недостатки. Динамически типизированные языки обычно проще изучать и писать на них программы, но, как вы можете представить, это потенциально увеличивает ошибки.
Теперь давайте поговорим о слабой и сильной типизации. Посмотрите на этот JavaScript код:
4 + '7'; // '47'
4 * '7'; // 28
2 + true; // 3
false - 3; // -3
М-да… Это… Ок, что тут происходит? Сложение числа 4 со строкой "7" даёт нам строку "47". JavaScript конвертирует число 4 в строку "4" и конкатенирует две строки — склеивает их друг с другом. JavaScript просто берёт на себя ответственность предположить, что это то, что мы хотели. Глупо обвинять его — чего мы действительно хотели? Складывать число со строкой не имеет никакого смысла. Какой-нибудь другой язык, вроде Ruby или Python просто бы пожаловался и ничего не сделал.
Произведение числа 4 со строкой "7", это, как видите, 28, по мнению JavaScript. В этом случае он сконвертировал строку "7" в число 7 и произвёл обычное умножение.
JavaScript постоянно так делает. Он знает о типах разных значений, но когда типы не соответствуют, он пытается предположить и сконвертировать один тип в другой, не предупреждая вас. Иногда это полезно, иногда мозгодробяще. Такое происходит потому что JavaScript — язык со слабой типизацией. У него есть представление о типах, но он типа "это всего лишь игра, чего ты злишься?"
У этой концепции нет ничего общего с динамической и статической типизацией, смысл которых — КОГДА проверять типы. Сильная против слабой — это НАСКОЛЬКО СЕРЬЁЗНО проверять типы.
Вы можете считать, что слабая — это нестрогая типизация, а сильная — это требовательная.
В отличие от динамичности-статичности, сила типизации это спектр. У PHP типизация немного сильнее. У Python ещё сильнее. И все они динамически типизированные языки.
JavaScript делает множество неявных конвертаций, но он так же даёт нам инструменты, чтобы мы могли делать явные конвертации сами. Мы можем конвертировать строки в числа, числа в строки, булевы значения в строки и так далее:
// Конвертация числа в строку
String(44843); // '44843'
// Конвертация строки в число
Number('590'); // 590
Number('aaa!!'); // NaN
// Конвертация числа в булево значение
Boolean(1); // true
Boolean(0); // false
// Конвертация булева значения в строку
String(true); // 'true'
String(false); // 'false'
Можно предположить, что неявная конверсия из типа в тип — не самая лучшая идея. Неявный, значит скрытый, а скрытый — значит трудно понимаемый и предрасположенный к ошибкам. Поведение программы становится менее очевидным. Вы пишете меньше кода, да, но код более хрупкий и менее понятный.
В JavaScript кроме undefined
существует null
. Оно означает, что «значение отсутствует». Например, если создать переменную, но не задавать ей значения, то у нее будет значение undefined:
let a;
console.log(a); // undefined
Тут значения не оказалось ненамеренно. Видимо, просто еще не пришло время дать этой переменной значение.
null
нужен для явного, намеренного указания, что значения нет. Можно сказать let a = null;
. Например, вы попросили пользователя ввести информацию, но он ничего не ввел. В таком случае уместно записать в результат null
.
null
, в отличие от undefined
, можно задавать вручную, передавать как аргумент в функцию и в целом использовать как любое другое явное значение.
(undefined
тоже можно задавать вручную, но никогда не нужно этого делать: это значение семантически создано только для того, чтобы его генерировал компьютер, а не программист).
При сравнении null и undefined нужно быть осторожным:
typeof null; // "object" (не "null" по историческим причинам)
typeof undefined; // "undefined"
null === undefined; // false
null == undefined; // true
null === null; // true
null == null; // true
!null; // true
isNaN(1 + null); //false
isNaN(1 + undefined); //true
В этом курсе мы сравниваем данные, используя три знака равенства:
a === b;
12 === 12;
Это сравнение прямое: являются ли эти данные абсолютно идентичными?
В JavaScript есть расслабленное сравнение, с двумя знаками равенства. Оно показывает, что происходит внутри JavaScript, при сравнении значений разных типов:
1 === '1'; // false
1 == '1'; // true
true === 1; // false
true == 1; // true
JavaScript имеет представление о типах: числах, строках, функциях, логических значениях и так далее. typeof
возвращает строку, в которой записан тип:
typeof 42; // 'number'
typeof 3.14; // 'number'
typeof NaN; // 'number'
typeof 'Berry'; // 'string'
typeof true; // 'boolean'
typeof false; // 'boolean'
NaN
означает "не число", но тип этого значения — number
.
Переменная без значения имеет специальное значение undefined
. Тип такой переменной — undefined
:
let a;
console.log(a); // undefined
typeof a; // 'undefined'
Код конвертируется в другую форму, которую компьютер может запустить. Этот процесс называется компиляцией, а период времени, за который этот процесс происходит — стадией компиляции (compile time).
После того, как компиляция закончена, запускается программа и период, пока она запущена, называется стадией исполнения (run time).
Статически типизированные языки проверяют типы и ищут ошибки типизации на стадии компиляции.
Динамически типизированные языки проверяют типы и ищут ошибки типизации на стадии исполнения.
Иными словами: статическое типизирование означает проверку типов перед запуском программы; динамическое — проверку типов пока программа запущена.
JavaScript часто конвертирует типы автоматически:
4 + '7'; // '47'
4 * '7'; // 28
2 + true; // 3
false - 3; // -3
JavaScript — это язык со слабой типизацией. У него есть представление о типах, но он расслаблено к ним относится и может оперировать значениями, можно сказать, произвольно. Чем сильнее система типизации, тем строже правила.
Number('590'); // 590
Number('aaa!!'); // NaN
Boolean(1); // true
Boolean(0); // false
String(true); // 'true'
String(false); // 'false'
String(44843); // '44843'
Вам ответят команда поддержки Хекслета или другие студенты.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт