TypeScript
Основы
Каждое значение в JavaScript
при выполнении над ним каких-либо операций ведет себя определенным образом. Это может звучать несколько абстрактно, но, в качестве примера, попробуем выполнить некоторые операции над переменной message
:
// Получаем доступ к свойству `toLowerCase`
// и вызываем его
message.toLowerCase()
// Вызываем `message`
message()
На первой строке мы получаем доступ к свойству toLowerCase
и вызываем его. На второй строке мы пытаемся вызвать message
.
Предположим, что мы не знаем, какое значение имеет message
- обычное дело - поэтому мы не можем с уверенностью сказать, какой результат получим в результате выполнения этого кода.
- Является ли переменная
message
вызываемой? - Имеет ли она свойство
toLowerCase
? - Если имеет, является ли
toLowerCase
вызываемым? - Если оба этих значения являются вызываемыми, то что они возвращают?
Ответы на эти вопросы, как правило, хранятся в нашей пам яти, поэтому остается только надеяться, что мы все помним правильно.
Допустим, message
была определена следующим образом:
const message = 'Hello World'
Как вы, наверное, догадались, при запуске message.toLowerCase()
мы получим ту же строку, только в нижнем регистре.
Что насчет второй строки кода? Если вы знакомы с JS
, то знаете, что в этом случае будет выброшено исключение:
TypeError: message is not a function
// Ошибка типа: message - это не функция
Было бы здорово, если бы мы имели возможность избегать подобных ошибок.
При запуске нашего кода, способ, с помощью которого движок JS
определяет, что делать, заключается в выяснении типа (type) значения - каким поведением и возможностями он обладает. На это намекает TypeError
- она говорит, что с трока 'Hello World'
не может вызываться как функция.
Для некоторых значений, таких как примитивы string
и number
, мы можем определить их тип во время выполнения кода (runtime) с помощью оператора typeof
. Но для других значений, таких как функции, соответствующий механизм для определения типов во время выполнения отсутствует. Например, рассмотрим следующую функцию:
function fn(x) {
return x.flip()
}
Читая этот код, мы можем сделать вывод, что функция будет работать только в случае передачи ей объекта с вызываемым свойством flip
, но JS
не обладает этой информацией. Единственным способом определить, что делает fn
с определенным значением, в чистом JS
является вызов этой функции. Такой вид поведения затрудняет предсказание поведения кода во время его написания.
В данном случае тип - это описание того, какие значения могут передаваться в fn
, а какие приведут к возникновению ошибки. JS
- это язык с динамической (слабой) типизацией - мы не знаем, что произойдет, до выполнения кода.
Статическая система типов позволяет определять, что ожидает код до момента его выполнения.
Проверка статических типов
Вернемся к TypeError
, которую мы получили, пытаясь вызвать string
как функцию. Никто не любит получать ошибки или баги (bugs) при выполнении кода.
Было бы здорово иметь инструмент, помогающий нам выявлять баги перед запуском кода. Это как раз то, что делают инструменты проверки статических типов, подобные TS
. Системы статических типов описывают форму и поведение значений. TS
использует эту информацию и сообщает нам о том, что, возможно, имеет место несоответствие определенным типам.
const message = 'Hello!'
message()
// This expression is not callable. Type 'String' has no call signatures.
// Данное выражение не является вызываемым. Тип 'String' не обладает сигнатурами вызова
При использовании TS
, мы получаем ошибку перед выполнением кода (на этапе компиляции).