Типизация — Введение в программирование
В одном из прошлых уроков мы говорили об ошибках и как с ними справляться. Есть несколько видов ошибок, и я хочу напомнить об одном конкретном виде. Вот небольшой фрагмент того урока:
Взгляните на этот код:
const length = 12; const num = length(54);
Сначала мы создали константу. Помните, что это как давать чему-то название: в нашем случае — числу 12 даётся название length . В следующей строке мы вызываем функцию length и передаём ей аргумент — число 54. Но подождите! length — это не функция! Это всего лишь число. Числа — это не функции, не ящики, которые производят какие-то действия. И JavaScript пожалуется именно на это:
= length(-54); ^ TypeError: length is not a function at Object. (/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'
Можно предположить, что неявная конверсия из типа в тип — не самая лучшая идея. Неявный, значит скрытый, а скрытый — значит трудно понимаемый и предрасположенный к ошибкам. Поведение программы становится менее очевидным. Вы пишете меньше кода, да, но код более хрупкий и менее понятный.
Дополнение к уроку
null
В 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
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 — это язык со слабой типизацией. У него есть представление о типах, но он расслаблено к ним относится и может оперировать значениями, можно сказать, произвольно. Чем сильнее система типизации, тем строже правила.
Явные конверсии в JavaScript
Number('590'); // 590 Number('aaa!!'); // NaN Boolean(1); // true Boolean(0); // false String(true); // 'true' String(false); // 'false' String(44843); // '44843'
Дополнительные материалы
- Wat by Gary Bernhardt (video)
- typeof
- Data type / Wikipedia
- Dynamic typing (WikiWikiWeb)
- Weak And Strong Typing (WikiWikiWeb)
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Об обучении на Хекслете
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Урок «Как эффективно учиться на Хекслете»
- Вебинар « Как самостоятельно учиться »
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Типы данных 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.
Основные принципы программирования: статическая и динамическая типизация
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
Когда вы изучаете языки программирования, то в разговорах часто слышите фразы наподобие “статически типизированный” или “динамически типизированный”. Эти понятия описывают процесс проверки соответствия типов, и как статическая проверка типов, так и динамическая, относятся к разным системам типов. Система типов — это набор правил, которые присваивают свойство, называющееся “тип”, различным сущностям в программе: переменным, выражениям, функциям или модулями — с конечной целью уменьшения количества ошибок путём подтверждения того, что данные отображаются корректно.
Не волнуйтесь, я знаю, что это всё звучит запутанно, поэтому мы начнём с основ. Что такое “проверка соответствия типов” и что такое вообще тип?
Тип
Тип, также известный как тип данных, это вид классификации, отмечающий одних из различных видов данных. Я не люблю использовать слово “тип” в этом смысле, поэтому скажем так: тип описывает возможные значения структуры (например, переменной), её семантическое значение и способ хранения в памяти. Если это звучит непонятно, подумайте о целых, строках, числах с плавающей запятой и булевых величинах — это всё типы. Типы можно разбить на категории:
- Примитивные типы — они варьируются в зависимости от языка, но самые основные — это целые, числа с плавающей запятой, булевы величины и символы.
- Сложные типы — они состоят из несколько примитивных типов, например, массив или запись (но не хэш). Все сложные типы считаются структурами данных.
- Абстрактные типы — типы, у которых нет конкретной реализации, такие как хэш, множество, очередь и стек.
- Прочие типы — например, указатели (тип, в значении которого хранится ссылка на другое место в памяти).
В различных языках могут различаться примитивные типы и структуры данных, но принцип везде один. Тип просто определяет набор правил и протоколов, которым должен соответствовать элемент данных.
Проверка соответствия типов
Существование типов не имеет смысла без процесса подтверждения того, что эти типы имеют логический смысл и программа может быть корректно исполнена. Тут и приходит на помощь проверка соответствия типов. Это процесс подтверждения и применения ограничений типов, и он может проходить как во время компиляции (т.е. статически), так и во время выполнения (т.е. динамически). Проверка соответствия типов нужна, чтобы убедиться в типобезопасности программы, что сведёт количество ошибок, связанных с типами, к минимуму. Это такие ошибки, которые возникают, когда операция производится с тем типом, с каким она производиться не должна: например, когда целое число принимается за число с плавающей запятой, или когда происходит сложения строки и целого:
x = 1 + "2"
Хотя во многих языках и строки, и числа могут использовать оператор +, это зачастую приводит к ошибкам типа, поскольку это выражение обычно не работает с разными типами данных.
Для случая не типобезопасной программы единого алгоритма действий нет. Какие-то языки выдают ошибку типа, которая останавливает компиляцию или выполнение, а в каких-то есть встроенные обработчики таких ошибок (что позволяет разработчикам порой ошибаться при работе с типами). Вне зависимости от этого, процесс проверки типов — это необходимость.
Теперь, когда мы знаем, что такое типы и как работает проверка соответствия типов, рассмотрим два основных вида проверки: статическую и динамическую.
Статическая проверка типов
Язык обладает статической типизацией, если тип переменной известен во время компиляции, а не выполнения. Типичными примерами таких языков являются Ada, C, C++, C#, JADE, Java, Fortran, Haskell, ML, Pascal, и Scala.
Большим преимуществом статической проверки типов является тот факт, что большую часть ошибок типов можно отловить на ранней стадии разработки. Статическая типизация обычно приводит к более быстрому исполнению скомпилированного кода, потому что компилятор знает точные типы используемых данных и создаёт оптимизированный машинный код. Статическая проверка типов оценивает лишь информацию, доступную во время компиляции, а также может подтвердить, что проверенные условия соблюдаются для всех возможных вариантов исполнения программы, что избавляет от необходимости проверки перед каждым запуском программы. Без статической проверки типов даже 100%-ное покрытие тестами не всегда поможет выявить некоторые ошибки типизации.
Динамическая проверка типов
Динамическая проверка типов — это процесс подтверждения типобезопасности программы во время её выполнения. Типичными примерами динамически типизированных языков являются Groovy, JavaScript, Lisp, Lua, Objective-C, PHP, Prolog, Python, Ruby, Smalltalk и Tcl.
Большая часть типобезопасных языков в той или иной мере использует динамическую проверку типов, даже если основным инструментом является статическая. Так происходит из-за того, что многие свойства невозможно проверить статически. Предположим, что программа определяет два типа, A и B, где B — подтип A. Если программа пытается преобразовать тип A в тип B, т.е. произвести понижающее приведение, то эта операция будет одобрена лишь в том случае, когда значение на самом деле имеет тип B. Поэтому для подтверждения безопасности операции нужна динамическая проверка типов.
В отличие от статической проверки типов, динамическая может привести к прекращению выполнения программы из-за ошибок типизации. В некоторых языках этого может избежать (например, благодаря обработке ошибок или слабой типобезопасности). Для избежания подобных ситуации рекомендуется использовать юнит-тесты.
Код, прошедший динамическую проверку типов, в общем случае менее оптимизирован; кроме того, существует возможность ошибок выполнения и, как следствие, необходимость проверки перед каждым запуском. Тем не менее, динамическая типизация открывает дорогу другим, мощным техникам программирования, например, метапрограммированию.
Типичные заблуждения
Миф 1: статическая / динамическая типизация == сильная / слабая типизация
Обычным заблуждение является мнение, что все статически типизированные языки являются сильно типизированными, а динамически типизированные — слабо типизированными. Это неверно, и вот почему.
Сильно типизированный язык — это такой язык, в котором переменные привязаны к конкретным типам данных, и который выдаст ошибку типизации в случае несовпадения ожидаемого и фактического типов — когда бы не проводилась проверка. Проще всего представить сильно типизированный язык как язык с высокой типобезопасностью. Например, в уже использованном выше куске кода сильно типизированный язык выдаст явную ошибку типизации, которая прервёт выполнение программы:
x = 1 + "2"
Мы часто ассоциируем статически типизированные языки, такие как Java и C#, с сильно типизированным (они такими и являются), поскольку тип данных задаётся явно при инициализации переменной — как в этом примере на Java:
String foo = new String("hello world");
Тем не менее, Ruby, Python и JavaScript (все они обладaют динамической типизацией) также являются сильно типизированными, хотя разработчику и не нужно указывать тип переменной при объявлении. Рассмотрим такой же пример, но написанный на Ruby:
foo = "hello world"
Оба языка являются сильно типизированными, но используют разные методы проверки типов. Такие языки, как Ruby, Python и JavaScript не требуют явного определения типов из-за вывода типов — способности программно выводить нужный тип переменной в зависимости от её значения. Вывод типов — это отдельное свойство языка, и не относится к системам типов.
Слабо типизированный язык — это язык, в котором переменные не привязаны к конкретному типу данных; у них всё ещё есть тип, но ограничения типобезопасности гораздо слабее. Рассмотрим следующий пример кода на PHP:
$foo = "x"; $foo = $foo + 2; // not an error echo $foo; // 2
Поскольку PHP обладает слабой типизацией, ошибки в этом коде нет. Аналогично предыдущему предположению, не все слабо типизированные языки являются динамически типизированными: PHP — это динамически типизированный язык, но вот C — тоже язык со слабой типизацией — воистину статически типизирован.
Хотя статическая / динамическая и сильная / слабая системы типов и являются разными, они обе связаны с типобезопасностью. Проще всего это выразить так: первая система говорит о том, когда проверяется типобезопасность, а вторая — как.
Миф 2: статическая / динамическая типизация == компилируемые / интерпретируемые языки
Будет верным сказать, что большинство статически типизированных языков обычно компилируются, а динамически типизированных — интерпретируются, но обобщить это утверждение нельзя, и тому есть простой пример.
Когда мы говорим о типизации языка, мы говорим о языке как о целом. Например, неважно, какую версию Java вы используете — она всегда будет статически типизированной. Это отличается от того случая, когда язык является компилируемым или интерпретируемым, поскольку в этом случае мы говорим о конкретной реализации языка. В теории, любой язык может быть как компилируемым, так и интерпретируемым. Самая популярная реализация языка Java использует компиляцию в байткод, который интерпретирует JVM — но есть и иные реализации этого языка, которые компилируются напрямую в машинный код или интерпретируются как есть.
Если это всё ещё непонятно, советую прочесть одну из предыдущих статей этого цикла.
Заключение
Я знаю, что в этой статье было много информации — но я верю, что вы справились. Я бы хотел вынести информацию про сильную / слабую типизацию в отдельную статью, но это не такая важная тема; к тому же, нужно было показать, что этот вид типизации не имеет отношения к проверке типов.
Нет однозначного ответа на вопрос “какая типизация лучше?” — у каждой есть свои преимущества и недостатки. Некоторые языки — такие как Perl и C# — даже позволяют вам самостоятельно выбирать между статической и динамической системами проверки типов. Понимание этих систем позволит вам лучше понять природу возникающих ошибок, а также упростит борьбу с ними.
Динамическая типизация — Преобразование в Строку, Число и в Булев тип
Динамическая типизация — это одна из особенностей JavaScript как языка программирования.
О том, что такое динамическая типизация уже шла речь здесь. А также в статье о преобразовании и приведении типов.
И прежде, чем продолжить разговор о динамической типизации не лишним будет вспомнить, какие типы данных встречаются в JavaScript.
Что такое динамическая типизация?
Если говорить простым языком, то динамическая типизация — это возможность одного типа данных превращаться в другой .
Например, число может стать строкой, строка — числом. А объект — булевым значением.
Но в мире программирования, в других языках есть и статичиская типизация, когда к примеру число всегда остается числом .
Далее рассмотрим виды динамической типизации.
Преобразование в строку — String
Первый вид динамической типизации — Преобразование в строку.
Как и что можно превратить в строку в JavaScript?
Команда String позволяет любой тип данных конвертировать в строку .
Пример 1.1
console.log(typeof(String(null))); console.log(typeof(String(11)));
string string
При помощи команды String конвертируем null и число в строку , и используем оператор typeof, чтобы убедиться в этом.
Такой способ конвертации различных типов данных в строку используется редко .
Преобразование в строку — Конкатенация
Конкатенация — это сложение/объединение строк. Или сложение строки с чем-то : с каким-либо другим типом данных.
При этом происходит преобразование в строку этого типа данных.
Пример 1.2
console.log(typeof(11 + '')); console.log(typeof(undefined + ''));
string string
В результате при сложении числа со строкой мы получаем строку. Тот же результат мы увидим при сложении строки с другими типами данных , напримиер, undefined.
На практике часто используется этот способ преобразования различных типов данных в строку, особенно чисел.
Преобразование в число — Number
Второй вид динамической типизации — Преобразование в число.
Команда Number позволяет любой тип данных конвертировать в число .
Пример 2.1
const obj = <>; console.log(typeof(obj)); console.log(typeof(Number(obj))); console.log(typeof(Number('expert')));
object number number
При помощи команды Number конвертируем объект и строку в число , и используем оператор typeof, чтобы убедиться в этом.
Такой способ конвертации различных типов данных в число используется очень редко .
Преобразование в число — Унарный плюс
Унарный плюс более лаконичный и удобный способ конвертации различных типов данных в число.
Здесь приведен пример того, как унарный плюс может использоваться на практике.
Но вернемся к примеру 2.1.
Пример 2.2
const obj = <>; console.log(typeof(obj)); console.log(typeof(+obj)); console.log(typeof(+'expert'));
object number number
Унарный плюс конвертирует объект и строку в числовой тип данных.
Методы parseInt() и parseFloat()
Стоит упомянуть о числовых методах parseInt() и parseFloat(), которые, не часто, но используются для конвертации строк в числа .
Пример 2.3
console.log(typeof('15px')); console.log(typeof(parseInt('15px'))); console.log(parseInt('15px'));
string number 15
В результате строка 15px конвертируется в число 15 .
Преобразование в булевы значения
Третий вид динамической типизации — преобразование типов данных в булевы значения .
При подобных преобразованиях полезно обращаться к логике.
Например, null — пустота, то чего не существует. Что это? Истина или ложь: true или false ?
Логично предположить, что пустота или «не ничего» — это ложь. И это действительно так в языке JavaScript null — это false .
Итак, что в JavaScript всегда будет конвертироваться в ложь?
Всегда конвертируются в false : 0 (ноль), » (пустая строка), null , undefined и NaN.
Строка, содержащая пробел — это уже не пустая строка .
Все остальное , что существует в JavaScript , в логическом контексте является истиной true .
Пример 3.1
let switcher = null; if(switcher) < // -->false console.log('Hello'); >
Здесь мы не получим результата , так как в условие помещается null , что в логическом контексте является ложью false .
Пример 3.2
let switcher = 1; if(switcher) < // -->true console.log('Hello'); >
Hello
Здесь код работает — выдает результат, так как в условие помещается число , что в логическом контексте является истиной true .
Команда Boolean
Команда (оператор или функция) Boolean — это 2-ой и явный способ преобразования типов данных в булевы значения.
Пример 3.3
console.log(Boolean(null)); console.log(Boolean('hi'));
false true
Здесь null и строка были конвертированы в false и true соответственно.
Такой способ конвертации различных типов данных в булевы значения используется редко .
Два отрицания !!
Два отрицания !! — это 3-ий (и еще более редкий чем предыдущий) способ конвертации различных типов данных в булевы значения.
Пример 3.4
console.log(!!null); console.log(!!'hi');
false true
Результат тот же, что и в предыдущем примере.
Типы данных в JavaScript — Иллюстрация
На этой иллюстрации продемонстрирована классификация типов данных в JavaScript.
Читайте также.
Введение в массивы — Создание массивов
Введение в условия – Оператор If – Равенство и присваивание
Оператор равенства == — Преобразование и сравнение типов данных
Приведение типов — Преобразование в строку