JavaScript: Слабая типизация
Нам известно про два разных типа данных: числа и строки. Мы, например, можем складывать числа, потому что операция сложения — это операция для типа «числа».
А что, если применить эту операцию не к двум числам, а к числу и строке?
console.log(1 + '7'); // => 17
Несмотря на то, что ‘7’ — это строка, а не число, интерпретатор JavaScript выдал ответ 17 , как если бы мы складывали две строки. Когда JavaScript видит несоответствие типов, он сам пытается преобразовать информацию. В данном случае он преобразовал число 1 в строку ‘1’ , а потом спокойно сделал конкатенацию ‘1’ и ‘7’ .
Не все языки так делают. JavaScript — это язык со слабой типизацией. Он знает о существовании разных типов (числа, строки и др.), но относится к их использованию не очень строго, пытаясь преобразовывать информацию, когда это кажется разумным. Иногда JavaScript даже доходит до крайностей. Большинство выражений, не работающих в других языках, прекрасно работают в JavaScript. Попробуйте выполнить любую арифметическую операцию (кроме сложения), подставив туда строки или любые другие типы данных (кроме ситуации, когда оба операнда — это числа или строки, содержащие только число) — вы увидите, что они всегда будут работать и возвращать NaN , что довольно логично.
const result = 'one' * 'two'; console.log(result); // => NaN
В языках со строгой типизацией сложить число со строкой не получится.
JavaScript был создан для интернета, а в интернете вся информация — это строки. Даже когда вы вводите на сайте номер телефона или год рождения, на сервер эта информация поступает не как числа, а как строки. Поэтому авторы языка решили, что автоматически преобразовывать типы — правильно и удобно.
Такое автоматическое неявное преобразование типов с одной стороны и правда удобно. Но на практике это свойство языка создает множество ошибок и проблем, которые трудно найти. Код может иногда работать, а иногда не работать — в зависимости от того, «повезло» ли в конкретном случае с автоматическим преобразованием. Программист это заметит не сразу.
В дальнейших заданиях вы будете встречаться с таким поведением не раз. Часто будет возникать вопрос «почему мой код работает не так, как я ожидаю?».
Слабая типизация красной нитью проходит сквозь всю разработку на Javascript.
Задание
Выведите на экран результат выражения: 7 — (-8 — -2) . Попробуйте сделать число 7 не числом, а строкой. Поэкспериментируйте с другими числами тоже.
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное
Определения
- Слабая типизация — это типизация, при которой язык программирования выполняет множество неявных преобразований типов автоматически, даже если может произойти потеря точности или преобразование неоднозначно.
TypeScript и статическая типизация
Как TypeScript и статическая типизация помогают писать код.
Время чтения: 6 мин
Открыть/закрыть навигацию по статье
Контрибьюторы:
Обновлено 13 июня 2023
Кратко
Скопировать ссылку «Кратко» Скопировано
В JavaScript слабая динамическая типизация. Это означает две вещи:
- При операции с переменными разных типов они будут автоматически приведены к одному типу.
- Любая переменная может произвольно менять свой тип во время выполнения программы. (За исключением строго типизированных массивов, например Uint8Clamped Array )
Эти свойства языка часто усложняют разработку больших и надёжных приложений. Поэтому появились решения, которые добавляют в JavaScript строгую статическую типизацию. «Строгая» означает запрет автоматического приведения типов, «статическая» значит, что проверка типов происходит на этапе сборки приложения или написания кода.
Самое популярное решение в этой области — TypeScript. Другие, менее популярные — Rescript, Flow и Hegel.
Как пользоваться
Скопировать ссылку «Как пользоваться» Скопировано
Настройка
Скопировать ссылку «Настройка» Скопировано
TypeScript — это язык, очень похожий на JavaScript. Браузеры и Node.js не могут выполнять TypeScript, поэтому его необходимо преобразовать в JavaScript.
Современные системы сборки умеют работать с TypeScript: Vite и Parcel поддерживают его из коробки, в Webpack и Rollup нужно будет добавить дополнительные плагины или лоадеры. Настройки TypeScript задаются в специальном файле — tsconfig.json. Структура файла tsconfig.json_ описана на сайте TypeScriptLang.org.
"compilerOptions": // Папка, куда попадёт результат "outDir": "./dist", >, // Какие файлы следует компилировать "include": ["src/**/*"], // Внешние зависимости, обычно, исключают из списка компилируемых файлов "exclude": ["node_modules"]>"compilerOptions": // Папка, куда попадёт результат "outDir": "./dist", >, // Какие файлы следует компилировать "include": ["src/**/*"], // Внешние зависимости, обычно, исключают из списка компилируемых файлов "exclude": ["node_modules"] >
После этого можно писать код в файлах с расширением .ts вместо .js.
Язык
Скопировать ссылку «Язык» Скопировано
Главное отличие TypeScript от JavaScript — возможность добавлять аннотации типов к переменным, аргументам функций и их возвращаемым значениям.
// Теперь переменной age можно присвоить только числоlet age: number // Будет работатьage = 43 // Выдаст ошибкуage = '34'// Теперь переменной age можно присвоить только число let age: number // Будет работать age = 43 // Выдаст ошибку age = '34'
Ошибки несовпадения типов будут заметны на стадии написания кода. То есть не нужно запускать программу, чтобы узнать об ошибках в типизации.
Проверка корректности типов — это разновидность статического анализа.
Примерно таким же образом можно типизировать параметры функции:
function sayMyName(name: string) console.log(`Привет, $`)> // Будет работатьsayMyName('Игорь')// Привет, Игорь // Выдаст ошибкуsayMyName(42)function sayMyName(name: string) console.log(`Привет, $name>`) > // Будет работать sayMyName('Игорь') // Привет, Игорь // Выдаст ошибку sayMyName(42)
В TypeScript можно типизировать не только параметры функции, но и возвращаемое значение.
function getCurrentDate(): Date // Будет работать return new Date()> function getCurrentDate(): Date // Выдаст ошибку return 'now'>function getCurrentDate(): Date // Будет работать return new Date() > function getCurrentDate(): Date // Выдаст ошибку return 'now' >
Другая особенность TypeScript — строгость типизации. Он запрещает операции с переменными разных типов, чтобы не допустить неоднозначности результата.
const age = 43const name = 'Mary' // Выдаст ошибку, складывать числа и строки нельзяconst result = age + nameconst age = 43 const name = 'Mary' // Выдаст ошибку, складывать числа и строки нельзя const result = age + name
Как понять
Скопировать ссылку «Как понять» Скопировано
TypeScript — надмножество JavaScript. На практике это означает, что любой JavaScript-код является корректным TypeScript-кодом. А вот обратное неверно.
Главное преимущество строгой статической типизации — возможность найти ряд ошибок ещё на этапе написания кода. Например, классическая ошибка, когда в переменной не оказывается значения, приводит в JavaScript к ошибке во время выполнения, когда код уже работает в браузере.
function generateEmail(user) return `$@mycompany.com`> // При вызове функции программист передает другой объект,// и происходит ошибка во время выполнения, пользователь её замечаетgenerateEmail(< fullName: 'Пётр Сергеевич Никольский' >)function generateEmail(user) return `$user.name>@mycompany.com` > // При вызове функции программист передает другой объект, // и происходит ошибка во время выполнения, пользователь её замечает generateEmail( fullName: 'Пётр Сергеевич Никольский' >)
Если переписать этот пример на TypeScript, потенциальная проблема исчезнет:
// Аргумент функции всегда должен быть объектом,// у которого есть строковое поле namefunction generateEmail(user: < name: string >) return `$@mycompany.com`> // При вызове функции программист передает другой объект,// происходит ошибка во время сборки, программист её замечает и исправляетgenerateEmail(< fullName: 'Петр Сергеевич Никольский' >) // Редактор кода или линтер укажет ошибку примерно такого содержания:// Argument of type '< fullName: string; >' is not assignable to parameter of type '< name: string; >'.// Аргумент функции всегда должен быть объектом, // у которого есть строковое поле name function generateEmail(user: name: string >) return `$user.name>@mycompany.com` > // При вызове функции программист передает другой объект, // происходит ошибка во время сборки, программист её замечает и исправляет generateEmail( fullName: 'Петр Сергеевич Никольский' >) // Редактор кода или линтер укажет ошибку примерно такого содержания: // Argument of type '< fullName: string; >' is not assignable to parameter of type '< name: string; >'.
Чем раньше обнаруживается ошибка, тем легче её исправить. Поэтому статическая типизация, статический анализ и написание автоматизированных тестов — это правила хорошего тона в современной разработке приложений. По сути, статическая типизация (как и статический анализ) добавляет в процесс разработки программы ещё один шаг — проверку типов, который выполняется перед сборкой проекта и может помочь найти ошибки на раннем этапе.

На первый взгляд кажется, что явное объявление типов увеличивает время разработки, так как требует писать много лишнего. Но это иллюзия. Аннотации типов — способ обеспечить большую надёжность написанного кода. Кроме этого, строгая типизация помогает компилятору эффективнее оптимизировать код.
TypeScript — это язык с опциональной типизацией. Он не заставляет программиста указывать типы, можно просто писать код как раньше. TypeScript постарается сам определить типы из контекста, и дать подсказки. Если контекст непонятен языку, он пометит тип переменной как any . Это означает, что в ней может лежать значение любого типа.
// Видимо, переменная должна иметь тип numberconst age = 12 // Язык не знает, какой тип имеет аргумент name,// он пометит его как anyfunction sayMyName(name) console.log(`Привет, $`)>// Видимо, переменная должна иметь тип number const age = 12 // Язык не знает, какой тип имеет аргумент name, // он пометит его как any function sayMyName(name) console.log(`Привет, $name>`) >
Эта особенность языка называется выводом типов и присутствует во многих современных языках программирования. К сожалению, она не слишком развита в TypeScript, и чаще всего приходится всё-таки «подсказывать» компилятору типы. В других языках, например, в Scala, она развита сильнее, там типы приходится указывать значительно реже.
На практике
Скопировать ссылку «На практике» Скопировано
Дмитрий Скрыльников советует
Скопировать ссылку «Дмитрий Скрыльников советует» Скопировано
Если у вас нет возможности использовать TypeScript, а типизировать код хочется, то можно использовать JavaScript с аннотациями типов в JSDoc.
/** @type */let age; /** * @param name */function sayMyName(name) /** * @returns */function getCurrentDate()/** @type number> */ let age; /** * @param string> name */ function sayMyName(name) . > /** * @returns Date> */ function getCurrentDate() . >
Это, конечно, не полноценная проверка типов, а только документация и удобное автодополнение в IDE:

Игорь Камышев советует
Скопировать ссылку «Игорь Камышев советует» Скопировано
В TypeScript существует «строгий режим» — он вынуждает указывать типы в тех случаях, когда язык не может определить их сам.
Если проект только стартует, и TypeScript был изначально выбран как основной язык, лучше включить строгий режим сразу.
"compilerOptions": // Запрещает класть в переменную null без явного объявления "strictNullChecks": true, // Делает вызовы методов bind, call, apply строго типизированными "strictBindCallApply": true, // Делает более строгими типы функций "strictFunctionTypes": true, // Запрещает объявление непустого поля класса без инициализации "strictPropertyInitialization": true, >,>"compilerOptions": // Запрещает класть в переменную null без явного объявления "strictNullChecks": true, // Делает вызовы методов bind, call, apply строго типизированными "strictBindCallApply": true, // Делает более строгими типы функций "strictFunctionTypes": true, // Запрещает объявление непустого поля класса без инициализации "strictPropertyInitialization": true, >, >
Если проект был написан на JavaScript, и TypeScript внедряется туда постепенно, строгий режим может сильно замедлить этот процесс.
Зачем использовать статические типы в JavaScript? (Пример статической типизации на Flow)

Как разработчик JavaScript вы можете целый день программировать, но не встретить ни одного статического типа. Так зачем думать об их изучении?
Ну, на самом деле изучение типов — это не просто упражнение для развития мышления. Если вы вложите некоторое время в изучение статических типов, их преимуществ, недостатков и примеров использования, это может чрезвычайно улучшить ваши навыки программирования.
Заинтересованы? Тогда вам повезло — именно об этом наша серия статей.
Во-первых, определение
Проще всего понять статические типы — это противопоставить их динамическим. Язык со статическими типами называют языком со статической типизацией. С другой стороны, язык с динамическими типами называют языком с динамической типизацией.
Ключевое отличие в том, что языки со статической типизацией выполняют проверку типа во время компиляции, а языки с динамической типизацией выполняют проверку типа во время выполнения программы.
Здесь остаётся усвоить ещё одну концепцию: что означает «проверка типа»?
Чтобы понять, посмотрим на типы Java и JavaScript.
«Типы» относятся к определяемому типу данных.
Например, в Java вы устанавливаете boolean так:
boolean result = true;
У этого значения правильный тип, потому что аннотация boolean соответствует логическому значению, указанному в result , а не целому числу или чему-то ещё.
С другой стороны, если вы попытаетесь объявить:
boolean result = 123;
…то это не скомпилируется, потому что указан неправильный тип. Код явно обозначает результат как boolean , но устанавливает его в виде целого числа 123 .
JavaScript и другие языки с динамической типизацией применяют иной подход, позволяя контексту определить, какой тип данных мы устанавливаем.
var result = true;
Вкратце: языки со статической типизацией требуют от указывать типы данных для ваших конструкций до того, как вы можете использовать их. Языки с динамической типизацией не требуют. JavaScript выводит тип данных из контекста, а Java требует прямо его заявить.
Так что видите, типы позволяют определить инварианты программы, то есть логические утверждения и условия, при которых программа выполнится.
Проверка типов проверяет и обеспечивает, что тип конструкции (константа, логический тип, число, переменная, массив, объект) соответствует инварианту, который вы определили. Например, вы можете определить, что «эта функция всегда возвращает строку». Когда программа запустится, вы можете безопасно предположить, что она вернёт строку.
Различия между статической проверкой типов и динамической проверкой типов имеют большее значение, когда случается ошибка типа. В языке со статической типизацией ошибки происходят на этапе компиляции. В языках с динамической типизацией — только когда программа запустится. То есть во время исполнения.
Это означает, что программа на языке с динамической типизацией (как JavaScript или Python) может скомпилироваться, даже если она содержит ошибки типов.
С другой стороны, если программа на языке со статической типизацией (как Scala или C++) содержит ошибки типов, она не пройдёт компиляцию, пока ошибки не будут исправлены.
Новая эра JavaScript
Поскольку JavaScript является языком с динамической типизацией, вы можете спокойно объявлять переменные, функции, объекты и что угодно, не объявляя тип.
var myString = "my string"; var myNumber = 777; var myObject = < name: "Preethi", age: 26, >; function add(x, y)
Удобно, но не всегда идеально. Вот почему недавно появились инструменты вроде Flow и TypeScript, которые дают разработчикам JavaScript *вариант* использования статических типов.
Flow — это open source библиотека для статической проверки типов, которую разработала и выпустила Facebook. Она позволяет постепенно добавлять типы в ваш код JavaScript.
TypeScript, с другой стороны, представляет собой надмножество, которое компилируется в JavaScript — хотя по ощущениям TypeScript похож на новый язык со статической типизацией сам по себе. То есть очень похож на JavaScript и не сложен в освоении.
В каждом случае если вы хотите использовать типы, то явно говорите инструменту, в каких файлах осуществлять проверку типов. В случае TypeScript вы делаете, создавая файлы с расширением .ts вместо .js . В случае Flow вы указываете в начале кода комментарий @flow .
Как только вы объявили, что хотите осуществить проверку типов в файле, то можете использовать соответствующий синтаксис для указания типов. Различие между инструментами в том, что Flow — это «контролёр» типов, а не компилятор, а TypeScript, с другой стороны, — это компилятор.
Я действительно думаю, что инструменты вроде Flow и TypeScript знаменуют собой смену поколений и прогресс для JavaScript.
Лично я очень многому научилась, используя типы в ежедневной работе. Вот почему я надеюсь, что вы присоединитесь ко мне в этом коротком и приятном путешествии в мир статических типов.
В остальных частях этой статьи будет рассмотрено:
Часть 1. Небольшое введение в синтаксис и язык Flow (эта часть).
Заметьте, что в примерах для этой статьи я выбрала Flow вместо TypeScript, потому что лучше знаю его. Для ваших собственных целей можете изучить их и выбрать себе подходящий инструмент. TypeScript тоже фантастичен.
Без лишних слов, давайте приступать!
Часть 1. Небольшое введение в синтаксис и язык Flow
Чтобы понять преимущества и недостатки статических типов, вам следует сначала изучить основы синтаксиса для статических типов с использованием Flow. Если вы никогда раньше не работали в языке со статической типизацией, может понадобиться некоторое время, чтобы привыкнуть к синтаксису.
Начнём с изучения, как добавлять типы к примитивам JavaScript, а также конструкциям вроде массивов, объектов, функций и т. д.
boolean
Этот тип в JavaScript описывает логические значения (true или false).
var isFetching: boolean = false;
Обратите внимание, что при указании типа всегда используется такой синтаксис:

number
Этот тип описывает числа с плавающей запятой по стандарту IEEE 754. В отличие от многих других языков программирования, JavaScript не выделяет разные типы чисел (вроде целых, коротких, длинных, с плавающей запятой). Вместо этого все числа всегда хранятся как числа двойной точности. Следовательно, вам нужен только один тип для описания любого числа.
number включает в себя Infinity и NaN .
var luckyNumber: number = 10; var notSoLuckyNumber: number = NaN;
string
Этот тип соответствует строке.
var myName: string = 'Preethi';
null
Тип данных null в JavaScript.
var data: null = null;
void
Тип данных undefined в JavaScript.
var data: void = undefined;
Обратите внимание, что null и undefined воспринимаются по разному. Если вы попытаетесь написать:
var data: void = null; /*------------------------FLOW ERROR------------------------*/ 20: var data: void = null ^ null. This type is incompatible with 20: var data: void = null ^ undefined
Flow выдаст ошибку, потому что тип предполагал тип undefined , а это не то же самое, что тип null .
Массив
Описывает массив JavaScript. Вы применяете синтаксис Array для определения массива, элементы которого имеет некий тип .
var messages: Array = ['hello', 'world', '!'];
Обратите внимание, что мы заменили T на string . Это значит, что мы объявляем messages как массив строк.
Объект
Описывает объект JavaScript. Есть разные способы добавить типы к объектам.
Вы можете добавить типы, чтобы описать форму объекта:
var aboutMe: < name: string, age: number >= < name: 'Preethi', age: 26, >;
Можете определить объект как карту, в которой строкам присваиваются некие значения:
var namesAndCities: < [name: string]: string >= < Preethi: 'San Francisco', Vivian: 'Palo Alto', >;
Также можете определить объекту тип данных Object :
var someObject: Object = <>; someObject.name = <>; someObject.name.first = 'Preethi'; someObject.age = 26;
В последнем варианте мы можем установить любой ключ и любое значение для объекта без ограничений, так что с точки зрения проверки типов здесь нет особого смысла.
any
Он представляет собой буквально любой тип. Тип any эффективно избегает любых проверок, так что вам не следует использовать его без крайней необходимости (например, если вам требуется обойти проверку типов или нужен аварийный люк).
var iCanBeAnything:any = 'LALA' + 2; // 'LALA2'
Тип any может пригодиться, если вы используете стороннюю библиотеку, которая расширяет другие прототипы системы (вроде Object.prototype).
Например, если библиотека расширяет Object.prototype свойством doSomething :
Object.prototype.someProperty('something');
то вы можете получить ошибку:
41: Object.prototype.someProperty('something') ^^^^^^ property `someProperty`. Property not found in 41: Object.prototype.someProperty('something') ^^^^^^^^^^^^ Object
Чтобы избежать этого, используем any :
(Object.prototype: any).someProperty('something'); // No errors!
Функции
Самый распространённый способ добавлять типы к функциям — это назначать типы передаваемым аргументам и (когда уместно) возвращаемому значению:
var calculateArea = (radius: number): number => < return 3.14 * radius * radius >;
Можно добавлять типы даже к функциям async (см. ниже) и генераторам:
async function amountExceedsPurchaseLimit( amount: number, getPurchaseLimit: () => Promise ): Promise < var limit = await getPurchaseLimit(); return limit >amount; >
Обратите внимание, что наш второй параметр getPurchaseLimit описан как функция, которая возвращает Promise . И функция amountExceedsPurchaseLimit тоже должна возвращать Promise , в соответствии с описанием.
Псевдонимы типов
Назначение псевдонимов типов — мой любимый способ использовать статические типы. Псведонимы позволяют составлять новые типы из существующих типов (число, строка и др.):
type PaymentMethod = < id: number, name: string, limit: number, >;
Выше создан новый тип под названием PaymentMethod , свойства которого составлены из типов number и string .
Теперь, если хотите использовать PaymentMethod , можете написать:
var myPaypal: PaymentMethod = < id: 123456, name: 'Preethi Paypal', limit: 10000, >;
Также можно создавать псевдонимы для любых примитивов оборачивая лежащий в основе тип внутрь другого типа. Например, если хотите псевдонимы типов для Name и Email :
type Name = string; type Email = string; var myName: Name = 'Preethi'; var myEmail: Email = 'iam.preethi.k@gmail.com';
Поступая так, вы подчёркиваете, что Name и Email — это разные вещи, а не просто строки. Поскольку имя и почтовый адрес не очень заменяют друг друга, то теперь вы не спутаете их случайно.
Параметризованные типы
Параметризованные типы (Generics) — это уровень абстракции над самими типами. Что имеется в виду?
type GenericObject = < key: T >; var numberT: GenericObject = < key: 123 >; var stringT: GenericObject = < key: "Preethi" >; var arrayT: GenericObject=
Создана абстракция для типа T . Теперь можно использовать любой тип, какой захотите, для представления T . Для numberT наше T будет числом. А для arrayT , оно будет принадлежать типу Array .
Да, знаю. Голова немного кружится, если вы первый раз имеете дело с типами. Обещаю, что это «нежное» введение почти закончено!
Maybe
Maybe позволяет нам установить тип для значения, которое потенциально может быть null или undefined . Для некоего T будет установлен тип T|void|null . Это означает, что оно может быть или T , или void , или null . Для установления типа maybe нужно добавить вопросительный знак перед определением типа:
var message: ?string = null;
Здесь мы говорим, что сообщение является либо строкой string , либо null , либо undefined .
Вы также можете использовать maybe для указания, что свойство объекта будет или некоего типа T , или undefined :
type Person = < firstName: string, middleInitial?: string, lastName: string, >;
Поместив вопросительный знак после имени свойства middleInitial , можно указать, что это необязательное поле.
Непересекающиеся множества
Ещё один мощный способ для представления моделей данных. Непересекающиеся множества полезны, если программе нужно обрабатывать разные типы данных одновременно. Другими словами, формат данных может зависеть от ситуации.
Расширим тип PaymentMethod из наших предыдущих примеров по параметризованным типам. Предположим, что в приложении у пользователя может быть один из трёх платёжных методов. В этом случае можно написать что-то вроде такого:
type Paypal = < id: number, type: 'Paypal' >; type CreditCard = < id: number, type: 'CreditCard' >; type Bank = < id: number, type: 'Bank' >;
Затем вы можете установить тип PaymentMethod как непересекающееся множество их этих трёх вариантов.
type PaymentMethod = Paypal | CreditCard | Bank;
Теперь платёжный метод всегда будет одним из трёх вариантов. Множество назначается «непересекающимся» благодаря свойству type .
Далее во второй части вы увидите больше практических примеров непересекающихся множеств.
Итак, почти закончили. Скажем только о парочке других особенностей Flow, достойных упоминания.
1) Вывод типа: Flow старается вывести типы везде, где только можно. Эта функция активируется, когда контролёр типов способен автоматически вывести тип данных в выражении. Это помогает избежать излишних аннотаций.
Например, можно написать:
/* @flow */ class Rectangle < width: number; height: number; constructor(width, height) < this.width = width; this.height = height; >circumference() < return (this.width * 2) + (this.height * 2) >area() < return this.width * this.height; >>
Хотя в этом классе нет типов, Flow способен адекватно проверить их:
var rectangle = new Rectangle(10, 4); var area: string = rectangle.area(); // Flow errors 100: var area: string = rectangle.area(); ^^^^^^^^^^^^^^^^ number. This type is incompatible with 100: var area: string = rectangle.area(); ^^^^^^ string
Здесь я попыталась установить area как string , но в определении класса Rectangle мы установили, что width и height являются числами. Соответственно, по определению функции area , она должна возвращать number . Пусть я не определяла явно типы для функции area , Flow нашёл ошибку.
Заметим одну вещь, что мейнтейнеры Flow рекомендуют при экспорте определения класса добавлять явные определения, чтобы потом было проще установить причину ошибок, когда класс не используется в локальном контексте.
2) Тесты динамических типов: По существу это означает, что логика Flow позволяет определить, какой тип будет у значения во время выполнения программы, так что эту информацию можно использовать во время статического анализа. Такие тесты полезны в ситуациях, когда Flow находит ошибку, но вам нужно убедить Flow, что вы всё делаете правильно.
Не хочу вдаваться в подробности, потому что это более продвинутая функция, о которой я надеюсь написать отдельную статью, но если есть желание изучить её, стоит изучить документацию.
Мы закончили с синтаксисом
Мы немало рассмотрели в первой части! Надеюсь, что этот поверхностный обзор был полезен и понятен. Если интересно копнуть глубже, то советую погрузиться в хорошо написанную документацию и изучать.
С окончанием описания синтаксиса давайте перейдём, наконец, к интересной части: изучению преимуществ и недостатков использования типов!

Об авторе: Прити Касиредди (Preethi Kasireddy), сооснователь и ведущий инженер компании Sapien AI, Калифорния
Продолжение: «Преимущества и недостатки статических типов»
- JavaScript
- статическая типизация
- динамическая типизация
- Flow
- TypeScript
- параметризованные типы
- generics
- maybe
- Семантика
- JavaScript
- Программирование
- TypeScript
Типы данных JavaScript и структуры данных
Все языки программирования содержат встроенные типы данных, но они часто отличаются друг от друга в разных языках. Эта статья — попытка описать встроенные структуры (типы) данных, доступные в JavaScript, и их свойства. На их основе строятся другие структуры данных. Когда это возможно, то мы будем сравнивать типы данных в разных языках.
Динамическая типизация
JavaScript является слабо типизированным или динамическим языком. Это значит, что вам не нужно определять тип переменной заранее. Тип определится автоматически во время выполнения программы. Также это значит, что вы можете использовать одну переменную для хранения данных различных типов:
var foo = 42; // сейчас foo типа Number foo = "bar"; // а теперь foo типа String foo = true; // foo становится типа Boolean
Типы данных
Стандарт ECMAScript определяет 8 типов:
- 6 типов данных являющихся примитивами:
- Undefined (Неопределённый тип) : typeof instance === «undefined»
- Boolean (Булев, Логический тип) : typeof instance === «boolean»
- Number (Число) : typeof instance === «number»
- String (Строка) : typeof instance === «string»
- BigInt : typeof instance === «bigint»
- Symbol (в ECMAScript 6) : typeof instance === «symbol»
И здесь нам необходимо сделать предостережение относительно использования оператора typeof для определения типа структур, т.к. все структуры будут возвращать «object» при его использовании, так как назначение typeof — проверка типа данных, но не структур. Если проверить тип структуры всё же необходимо, то в этом случае желательно использовать оператор instanceof, так как именно он отвечает на вопрос о том, какой конструктор был использован для создания структуры.
Стоит отметить два особых случая работы оператора typeof : возврат «object» для значения null и «function» для функций: первое принято считать ошибкой языка, сохраненной ради обратной совместимости, второе является условностью, удобной для проверки на принадлежность значения категории функций, где функция — это особый, «вызываемый», объект.
Примитивные значения
Все типы данных в JavaScript, кроме объектов, являются иммутабельными (значения не могут быть модифицированы, а только перезаписаны новым полным значением). Например, в отличии от C, где строку можно посимвольно корректировать, в JavaScript строки пересоздаются только полностью. Значения таких типов называются «примитивными значениями».
Булевый тип данных
Булевый тип представляет логическую сущность и имеет два значения: true (истина) и false (ложь) . Смотрите Boolean и Boolean для получения подробностей.
Null
Этот тип данных имеет всего одно значение: null . Смотрите null и Null для получения подробностей.
Undefined
Переменная, которой не было присвоено значение, будет иметь значение undefined . Смотрите undefined и undefined для получения подробностей.
Числа
В соответствии со стандартом ECMAScript, существует только один числовой тип, который представляет собой 64-битное число двойной точности согласно стандарту IEEE 754. Другими словами, специального типа для целых чисел в JavaScript нет. Это означает, что при числовых операциях вы можете получить неточное (округлённое) значение. В дополнение к возможности представлять числа с плавающей запятой, есть несколько символических значений: +Infinity (положительная бесконечность), -Infinity (отрицательная бесконечность), и NaN (не число).
Для получения самого большого или самого меньшего доступного значения в пределах +/-Infinity , можно использовать константы Number.MAX_VALUE или Number.MIN_VALUE . А начиная с ECMAScript 2015, вы также можете проверить, находится ли число в безопасном для целых чисел диапазоне, используя метод Number.isSafeInteger() , либо константы Number.MAX_SAFE_INTEGER и Number.MIN_SAFE_INTEGER . За пределами этого диапазона операции с целыми числами будут небезопасными, и возвращать приближённые значения.
Ноль в JavaScript имеет два представления: -0 и +0. («0» это синоним +0). На практике это имеет малозаметный эффект. Например, выражение +0 === -0 является истинным. Однако, это может проявиться при делении на ноль:
> 42 / +0 Infinity > 42 / -0 -Infinity
Хотя число в большинстве случаев представляет только своё значение, JavaScript предоставляет несколько бинарных операций. Они могут использоваться для того, чтобы представлять число как несколько булевых значений, с помощью битовой маски. Это считается плохой практикой, так как JavaScript предлагает другие способы представления булевых значений (например, массив элементов с булевыми значениями или объект, содержащий набор булевых свойств). Кроме того, битовые маски часто делают код более трудным для чтения, понимания и дальнейшей поддержки. Эта техника может быть необходима в условиях технических ограничений, таких как объём локального хранилища данных, или в такой экстремальной ситуации, когда каждый бит передаваемый по сети на счету. Данный подход следует использовать как крайнюю меру, когда не остаётся других путей для необходимой оптимизации.
Текстовые строки
В JavaScript для представления текстовых данных служит тип String . Он представляет собой цепочку «элементов» 16-битных беззнаковых целочисленных значений. Каждый такой элемент занимает свою позицию в строке. Первый элемент имеет индекс 0, следующий — 1, и так далее. Длина строки — это количество элементов в ней.
В отличие от языков подобных C, строки в JavaScript являются иммутабельными. Это означает, что после того, как строковое значение создано, его нельзя модифицировать. Остаётся лишь создать новую строку путём совершения некой операции над исходной строкой. Например:
- Получить часть исходной строки выборкой отдельных символов, либо применением метода String.substr() .
- Объединить две строки в одну, применив оператор ( + ) или метод String.concat() .
Избегайте повсеместного использования строк в своём коде!
Иногда может показаться соблазнительным использование строк для представления сложных структур данных. Это даст небольшие краткосрочные выгоды:
- Легко соединять данные в кучу сложением строк.
- Легко отлаживать (данные выглядят «как есть», в читаемом текстовом виде).
- Строки — это распространённый формат данных, используемый разнообразными API (поля ввода (en-US), значения локального хранилища, XMLHttpRequest возвращает ответ в виде строки, и т. д.) и использовать только строки может показаться заманчивым.
Несмотря на то, что в строке можно выразить данные любой сложности, делать это — не самая лучшая идея. Например, используя разделитель, строку можно использовать как список элементов (массив JavaScript будет более подходящим решением). К сожалению, если такой сепаратор встретится в значении одного из элементов, такой список будет сломан. Выходом может стать добавление символа экранирования, и т. д. Всё это потребует добавления множества ненужных правил, и станет обременительным при поддержке.
Используйте строки только для текстовых данных. Для составных структур преобразуйте строки в подобающие конструкции.
Тип данных Символ (Symbol)
Символы являются нововведением JavaScript начиная с ECMAScript 2015. Символ — это уникальное и иммутабельное примитивное значение, которое может быть использовано как ключ для свойства объекта (смотрите ниже). В некоторых языках программирования символы называются атомами. Их также можно сравнить с именованными значениями перечисления (enum) в языке C. Подробнее смотрите Symbol и Symbol .
Тип данных Большое целое (BigInt)
BigInt является встроенным объектом, который предоставляет способ представления целых чисел, которые больше 2 53, что является наибольшим числом, которое JavaScript может надёжно представить с помощью Number примитива.
> let bigInt = 19241924124n; > console.log(bigInt); 19241924124n > console.log(typeof bigInt); "bigint"
Объекты
В компьютерной терминологии, объект — это значение в памяти, на которое возможно сослаться с помощью идентификатора.
Свойства
В JavaScript объект может расцениваться как набор свойств. Литеральная инициализация объекта задаёт определённое количество начальных свойств, и в процессе работы приложения поля могут добавляться и удаляться. Значения свойств могут иметь любой тип, включая другие объекты, что позволяет строить сложные, разветвлённые иерархии данных. Каждое свойство объекта идентифицируется ключом, в качестве которого может выступать значение с типом Строка или Символ.
Есть два типа свойств: свойство-значение и свойство-акцессор (свойство, обёрнутое в геттер и сеттер). Они отличаются определёнными атрибутами.
Свойство-значение
Ассоциирует ключ со значением, и имеет следующие атрибуты:
Атрибут Тип Описание Значение по умолчанию [[Value]] Любой тип JavaScript Значение, возвращаемое при обращении к свойству. undefined [[Writable]] Boolean Если false , то [[Value]] свойства не может быть изменено. false [[Enumerable]] Boolean Если true , свойство будет перечислено в цикле for. in. Смотрите подробнее Перечисляемость и владение свойствами. false [[Configurable]] Boolean Если false , то свойство не может быть удалено, а его атрибуты, кроме [[Value]] и [[Writable]] не могут быть изменены. false Атрибут Тип Описание Read-only Boolean Зарезервировано по атрибуту [[Writable]] ES5. DontEnum Boolean Зарезервировано по атрибуту [[Enumerable]] ES5. DontDelete Boolean Зарезервировано по атрибуту [[Configurable]] ES5. Свойство-акцессор
Ассоциирует ключ с одной из двух функций-акцессоров (геттер и сеттер) для получения или изменения значения свойства, и имеет следующий атрибуты:
Атрибут Тип Описание Значение по умолчанию [[Get]] Function или undefined Функция вызывается без параметров и возвращает значение свойства каждый раз, когда происходит чтение свойства. Смотрите также get (en-US) . undefined [[Set]] Function или undefined Функция вызывается с одним аргументом, содержащим присваиваемое значение, каждый раз, когда происходит попытка присвоить свойству новое значение. Смотрите также set (en-US) . undefined [[Enumerable]] Boolean Если true , свойство будет перечислено в цикле for. in. false [[Configurable]] Boolean Если false , то свойство не может быть удалено, и не может быть преобразовано в свойство-значение. false Примечание: Атрибуты обычно используются движком JavaScript, поэтому вы не можете обратиться к ним напрямую (смотрите подробнее Object.defineProperty()). Вот почему в таблицах выше они помещены в двойные квадратные скобки вместо одиночных.
«Обычные» объекты и функции
Объект JavaScript — это таблица соотношений между ключами и значениями. Ключи — это строки (или Symbol ), а значения могут быть любыми. Это делает объекты полностью отвечающими определению хеш-таблицы.
Функции — это обычные объекты, имеющие дополнительную возможность быть вызванными для исполнения.
Даты
Для работы с датами служит встроенный глобальный объект Date .
Массивы общие и типизированные
Массив — это обычный объект с дополнительной связью между целочисленными ключами его свойств и специальным свойством length. Вдобавок ко всему, массивы наследуют Array.prototype , предоставляющий исчерпывающий набор методов для манипуляции массивами. Например, метод indexOf (служит для поиска значения в массиве), push (en-US) (добавляет элемент в конец массива) и т. д. Всё это делает массив идеальным кандидатом для представления списков и перечислений.
Типизированный массив является новинкой ECMAScript Edition 6 и является массивоподобным представлением для лежащего в его основе бинарного буфера памяти. Следующая таблица поможет вам найти соответствующие типы языка C:
Объекты TypedArray
Тип Диапазон значений Размер (байты) Описание Тип Web IDL Эквивалентный тип языка C Int8Array -128 до 127 1 8-битное целое со знаком с дополнением до двух byte int8_t Uint8Array (en-US) 0 до 255 1 8-битное беззнаковое целое octet uint8_t Uint8ClampedArray 0 до 255 1 8-битное беззнаковое целое (фиксированное от 0 до 255) octet uint8_t Int16Array (en-US) -32768 до 32767 2 16-битное целое со знаком с дополнением до двух short int16_t Uint16Array (en-US) 0 до 65535 2 16-битное беззнаковое целое unsigned short uint16_t Int32Array -2147483648 до 2147483647 4 32-битное целое со знаком с дополнением до двух long int32_t Uint32Array (en-US) 0 до 4294967295 4 32-битное беззнаковое целое unsigned long uint32_t Float32Array 1.2×10-38 to 3.4×1038 4 32-битное число с плавающей точкой IEEE-стандарта (7 значащих цифр, например 1.123456) unrestricted float float Float64Array 5.0×10-324 to 1.8×10308 8 64-битное число с плавающей точкой IEEE-стандарта (16 значащих цифр, например, 1.123. 15) unrestricted double double Коллекции: Maps, Sets, WeakMaps, WeakSets
Эти наборы данных используют ссылку на объект в качестве ключа, и введены в JavaScript с приходом ECMAScript Edition 6. Set и WeakSet являют собой набор уникальных объектов, в то время как Map (en-US) и WeakMap ассоциируют с объектом (выступающим в качестве ключа) некоторое значение. Разница между Map и WeakMap заключается в том, что только у Map ключи являются перечисляемыми. Это позволяет оптимизировать сборку мусора для WeakMap.
Можно было бы написать собственную реализацию Map и Set на чистом ECMAScript 5. Однако, так как объекты нельзя сравнивать на больше или меньше, то производительность поиска в самодельной реализации будет вынужденно линейной. Нативная реализация (включая WeakMap) имеет производительность логарифмически близкую к константе.
Обычно, для привязки некоторых данных к узлу DOM, приходится устанавливать свойства этому узлу непосредственно, либо использовать его атрибуты data-* . Обратной стороной такого подхода является то, что эти данные будут доступны любому скрипту, работающему в том же контексте. Maps и WeakMaps дают возможность приватной привязки данных к объекту.
Структурированные данные: JSON
JSON (JavaScript Object Notation) — это легковесный формат обмена данными, происходящий от JavaScript, но используемый во множестве языков программирования. JSON строит универсальные структуры данных. Смотрите JSON и JSON для детального изучения.
Больше объектов и стандартная библиотека
JavaScript имеет стандартную библиотеку встроенных объектов. Пожалуйста, обратитесь к справочнику, чтобы найти описание всех объектов доступных для работы.
Определение типов оператором typeof
Оператор typeof может помочь определить тип вашей переменной. Смотрите страницу документации, где приведены его детали и случаи использования.
Смотрите также
- Nicholas Zakas, коллекция некоторых классических структур данных и алгоритмов, написанных на JavaScript.
- Реализация некоторых структур данных для JavaScript
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
