Null, ноль и Undefined: разбираемся в сортах пустоты
Может показаться, что null — это название переменной, но на самом деле это специальное значение, которое означает, что переменная есть, но в переменной ничего нет. Кроме этого, есть ещё одно интересное состояние переменной — undefined , которое означает, что переменная не определена. Давайте разберёмся, в чём разница и для чего это всё нужно.
Минутка информатики. Если копать совсем глубоко, то в переменной не всегда лежат именно те данные, которые мы туда отправили.
Чаще всего при объявлении переменной компьютер выделяет сколько-то байтов в памяти, в которые можно записать любое помещающееся значение. Но это работает только для простых переменных — чисел и символов (которые внутри тоже числа). Если нам нужна строка, которая состоит из множества символов, или массив, то в переменной хранится не значение, а только ссылка на область памяти, в которой лежат наши данные. В этих случаях переменная — это карта, которая объяснит компьютеру, где найти всё остальное.
Зная это, будет проще разобраться, почему компьютер иногда вместо конкретного значения переменной записывает в неё null или undefined.
Что такое undefined
Когда мы только создаём переменную без явного указания типа и ничего в неё не кладём, то компьютер многого не знает:
- он не знает, что в ней может оказаться;
- не в курсе, сколько памяти надо будет выделить;
- не понимает, по каким правилам с этой переменной нужно работать дальше.
Чтобы пометить себе, что пока ничего не ясно, компьютер помечает эту переменную как undefined , что означает «неопределённость»:

В тех языках, где переменные не обязательно объявлять заранее, например в Python, интерпретатор тоже выведет подобное сообщение. Отличие будет в том, что здесь это приведёт к ошибке, так как компьютер даже не знает, что у него есть такая переменная.
Представьте, что вы занимаетесь уборкой и вам говорят: «Возьми мешок». Что это значит? Пока неясно: это мешок для мусора или вещей; какого объёма; полный или пустой. Просто «мешок». Если представить, что мешок — это переменная, то undefined мешок — это как раз «просто некий мешок, без уточнения».
Чаще всего в языках с сильной динамической типизацией у переменной не может быть значения undefined , потому что переменная в них появляется в момент её объявления и присваивания сразу какого-то значения. Но само понятие undefined тоже там используется — оно означает, что компьютер вообще ничего не знает про эту переменную.
Зачем нужен undefined
Чаще всего это используется для проверки состояния переменной — есть ли в ней хоть что-то. Например, если в ней что-то есть, то с этим можно работать дальше, а если нет — вывести сообщение о том, что переменная пустая. Это полезно для отладки кода или для проверки входящих в нашу программу данных и вывода информативных ошибок.

Что такое null
В отличие от undefined, когда компьютер не знает, что лежит в переменной, null — это как раз одно из значений переменной. Оно означает, что переменная — пустая, при этом компьютер точно знает, как с ней можно работать.
Допустим, мы хотим обратиться к элементу на странице по его id. Для этого мы сначала заводим переменную element, и на старте она будет определяться как undefined. После этого мы присваиваем этой переменной ссылку на нужный элемент, но если этого элемента на странице нет, то произойдёт такое:
- Скрипт попытается найти элемент на странице.
- После того как он убедится, что элемента нет, он вернёт null — это значит, что на странице ничего такого нет.
- Это значение null отправится в переменную element, и у неё появится определённое значение null.
Если вспомнить наш мешок во время уборки:
— undefined — это когда вы ничего не знаете про мешок — даже то, есть ли этот мешок в принципе или его нужно пойти и где-то купить;
— null — это когда вы знаете, что мешок есть, он определённого размера и формы, но он точно пустой и даже ещё не раскрытый;

В некоторых языках, например Python, вместо null используют None — это тоже значение переменной, которое означает, что в переменной ничего нет, но сама переменная определена верно.
Undefined и null — это одно и то же?
Нет, и в этом легко убедиться, используя строгое сравнение. Дело в том, что при обычном сравнении компьютер смотрит, есть что-то в переменной или нет, при этом ему неважно, работали мы с ней раньше или нет. А при строгом сравнении компьютер смотрит ещё и на тип данных — в этом случае сразу видно, что у undefined никакого типа данных нет:

А что такое ноль?
А ноль — это самая «жирная» пустота из всех вышеперечисленных. Ноль — это полноценное значение численной переменной (например, integer, то есть целого числа).
Представьте, что вы пишете программу, которая обрабатывает прогнозы погоды. Эти данные она получает с погодного сервера. И с этого сервера прилетала переменная temperature. Сравните:
Если temperature === undefined, то, похоже, с сервера не прилетело никакой переменной с температурой. Программа завела переменную для приёма данных, но дальше ничего не произошло. Мы даже не знаем, какого типа данные нам прилетят. А то и вовсе нет никакой переменной.
Если temperature === null, то мы знаем, в каком формате прилетит температура, но сами данные ещё не прилетели.
Если temperature === 0, то температура — ноль градусов (предположим, по Цельсию). Ноль градусов — это вполне себе значение, с которым можно работать — например нарисовать снежинку.
Возвращаясь к метафоре с мешком: ноль — это когда нам объяснили, какой именно нужен мешок, мы его взяли и запихнули в него ноль, который занял всё место. Зачем? Видимо, так надо.
Хранение инстанса карты mapbox-gl вне React
В этом посте будет рассмотрен способ использования mapbox-gl в React приложении, с хранением инстанса карты во вспомогательном объекте обертке. Это позволяет обращаться к карте из любой части приложения, без необходимости передавать ссылку на карту средствами React
Под словами «ссылка на инстанс карты» или «ссылка на карту» подразумевается переменная содержащая объект карты.
Это обусловлено тем, что переменная содержащая объект на самом деле содержит ссылку на него, но не сам объект, тут можно узнать об этом подробнее https://blog.noveogroup.ru/2021/02/upravlenie-pamyatju-v-javascript/
Эта статья входит в цикл статей
Управление состоянием mapbox-gl в React
Описание проблемыВ процессе моей работы в geoalert.io я не раз сталкивался с проблемой управления со.
Инструкцию по созданию нового проекта и имплементацию компонента карты, вы можете посмотреть в моем предыдущем посте
Использование mapbox-gl в React и Next.js
ВведениеВ данной статье я хочу описать известные мне способы встраивания mapbox-gl в React приложени.
Классический подход
При использовании mapbox-gl в React приложении, возникает проблема организации доступа к нему из других компонентов приложения
Как правило ссылку на инстанс mapbox-gl можно передавать через props либо через контекст
Допустим наше приложение состоит из полноэкранного компонента с веб картой и боковой панели, содержащей элементы для взаимодействия с картой

В последующих примерах кода некоторые детали опущены, например стили
Пример передачи ссылки через props
Код приложения в данном случае будет выглядеть примерно так
components/pass-map-with-props.tsx
import * as React from "react"; import MapboxMap from "./mapbox-map"; export const Sidebar: React.FC> = (< map, >) => < return . some sidebar content. ; >; const App: React.FC = () => < const [map, setMap] = React.useState(); return ( /> /> ); >; export default App;
Когда карта полностью загрузится, ссылка на нее сохраняется внутри React.useState , далее она передается в компонент Sidebar через props
Пример передачи ссылки через контекст
Чтобы передать ссылку на инстанс через контекст, создадим контекст и обернем в него Sidebar компонент, а так же хук useMapboxMap для доступа к инстансу карты через контекст
components/pass-map-with-context.tsx
import * as React from "react"; import MapboxMap from "./mapbox-map"; const MapboxMapContext = React.createContext( undefined ); function useMapboxMap() < return React.useContext(MapboxMapContext); >const Sidebar: React.FC = () => < const mapboxMap = useMapboxMap(); return . some sidebar content. ; >; const App: React.FC = () => < const [map, setMap] = React.useState(); return ( /> > ); >; export default App;
Теперь на инстанс карты можно сослаться откуда угодно изнутри Sidebar используя useMapboxMap
В обоих примерах есть один общий недостаток, передача ссылки на карту в дочерние компоненты влечет за собой дополнительные неудобства при разработке, это может вызвать такие проблемы как prop drilling, а при использовании контекста отдельное внимание потребуется выделить организации иерархии компонентов. Если вы будете использовать его в ваших хуках, это может потребовать дополнительного кода для избежания проблем с exhaustive-deps. Так же могут возникать сложности при необходимости обращения к карте из внешнего хранилища состояния, например из Redux или XState.
Хранение вне React
Хотелось бы иметь возможность обращаться к карте из любого компонента, без необходимости как-то специально его туда передавать и указывать в списках зависимостей хуков
Вспомогательный объект
Для того чтобы иметь возможность сделать это потребуется вспомогательный объект-обертка в котором будет храниться ссылка на инстанс карты
lib/map-wrapper.ts
class MapWrapper < private _map?: mapboxgl.Map; set map(instance: mapboxgl.Map) < this._map = instance; >get map() < if (typeof this._map === "undefined") throw new Error("Cannot access mapbox map before inilizing it"); return this._map; >remove() < if (typeof this._map === "undefined") throw new Error("Cannot remove mapbox map before inilizing"); this._map.remove(); this._map = undefined; >> export const mapbox = new MapWrapper();
Создадим класс MapWrapper используя возможности typescript для работы с классами
Инстанс карты будет храниться в приватной переменной _map , зададим также сеттер и геттер для этой переменной и метод для удаления инстанса карты
- set — для сохранения карты в приватную переменную
- get — если карта не инициализирована, при попытке обратиться к ней выбрасывается исключение
- remove — если страница с картой например была закрыта, инстанс карты необходимо удалить чтобы избежать проблем с утечкой памяти, метод можно вызвать только если карта была инициализирована, в ином случае вызывается исключение
Применительно к предыдущим примерам получим следующее:
import * as React from "react"; import "mapbox-gl/dist/mapbox-gl.css"; import MapboxMap from "../components/mapbox-map"; // Импорт объекта обертки import mapbox from "../lib/map-wrapper" const Sidebar: React.FC = () => < const setCenterToMoscow = () =>mapbox.map.setCenter([ 37.60345458984374, 55.695776911386126 ]) return ( ); >; const WithOutsideMap: React.FC = () => < const onMapCreated = React.useCallback((map: mapboxgl.Map) =>< // сохраняем инстанс карты в обертку при его создании mapbox.map = map; >, []); return ( /> ); >; export default WithOutsideMap;
Теперь мы можем обращаться к карте из компонента Sidebar , без необходимости его передавать в компонент средствами React .
Живой пример
Давайте воспроизведем пример из документации mapboxgl с отображением текущего центра и зума карты
Use Mapbox GL JS in a React app | Help
components/with-outside-map.tsx
import * as React from "react"; import "mapbox-gl/dist/mapbox-gl.css"; import MapboxMap from "../components/mapbox-map"; import mapbox from "../lib/map-wrapper"; const WithOutsideMap: React.FC = () => < // текущий зум и центр карты const [viewport, setViewport] = React.useState(< center: ["-74.5165", "40.0021"], zoom: "9.00", >); const < center: [lng, lat], zoom >= viewport; const onMapCreated = React.useCallback((map: mapboxgl.Map) => < mapbox.map = map; // после того как карта создана, привяжем ивент, по событию "move" // обновляющий значение viewport mapbox.map.on("move", () =>< setViewport(< center: [ mapbox.map.getCenter().lng.toFixed(4), mapbox.map.getCenter().lat.toFixed(4), ], zoom: mapbox.map.getZoom().toFixed(2), >); >); >, []); return ( Longitude: | Latitude: | Zoom: * зададим начальный центр и зум для карты */> initialOptions=> /> ); >; export default WithOutsideMap;
Центр карты будет храниться в объекте viewport , при передвижении карты пользователем, будет срабатывать событие move , по которому мы обновляем текущее значение viewport .
Значения хранящиеся во viewport используются для отображения текущих координат центра и зума карты, а так же для передачи параметров в initialOptions компонента карты.
Объект initialOptions используется единожды, при создании карты.
При передаче параметров в initialOptions можно заметить + перед переменными, это нужно для конвертации их в числа, так как мы храним цифровые значения как строки.
В следующей, завершающей, статье цикла я планирую рассказать об управлении состоянием React приложения с mapbox-gl с использованием XState
Ссылки на исходный код и запущенное приложение
GitHub — dqunbp/using-mapbox-gl-with-react: This is a repository for an example of using React with mapbox-gl and Next.js. You can use it as a new project template. Read more about this project from my blog post, link in README.md
Странное поведение карт: Uncaught TypeError: Cannot read property ‘geoObjects’ of undefined?
То все метки ставятся на карту! Все отлично. Обращаю внимание, что «ошибочные» координаты присутствуют в этом массиве.
var myMap; function init()< myMap = new ymaps.Map("map", < center: [56.49540919, 84.95061710], zoom: 12, controls: ['zoomControl'] >); > function reloadYandexMap(responseCoords) < myMap.geoObjects.removeAll(); for (var i = 0; i < responseCoords.length; i++) < myMap.geoObjects.add(new ymaps.Placemark(responseCoords[i])); >//myMap.setBounds(myMap.geoObjects.getBounds()); > ymaps.ready(function()< init(); var arrStreets = []; $('#streets-box span').each(function(ind, val)< arrStreets.push($(val).text()); >); var responseCoords = []; $.ajax(< type: 'POST', url : '/ajax/getCoordsByAddress', async: false, data: < 'arrStreets': arrStreets >, dataType: 'json', success: function(res)< if(res.success) < $.each( JSON.parse(res.msg), function( key, val ) < responseCoords.push(val); >); reloadYandexMap(responseCoords); > > >); >);
- Вопрос задан более трёх лет назад
- 2621 просмотр
Типы данных в JavaScript. Инструкция для начинающих
Как и любая компьютерная программа, JavaScript нуждается в наведении порядка в данных, в их структурировании. На языке JavaScript мы говорим, что если данные имеют одинаковую структуру, то они имеют одинаковый тип. Внутренняя организация данных может быть простой, как ДА или НЕТ, а может быть весьма замысловатой, как дерево HTML-элементов или маршруты на карте навигатора.
Примитивные типы. JavaScript предлагает разработчику несколько простых, примитивных, типов. Среди них: boolean , number , string . Примитивными эти типы называют за то, что значения этих типов нельзя поменять. Их можно клонировать, встроить в другие значения. Через минуту увидим как это происходит
Работа с наборами. Дополнительно JavaScript предлагает несколько типов объектов для работы с наборами — массивы, словари, множества. Это очень кстати, попробуй вспомни, как эффективно реализовать сортировку.
Готовые структуры для хранения информации на все случаи жизни не напасешься, поэтому JavaScript предоставляет разработчику полную свободу в этих вопросах, и разработчик может создавать самостоятельно бесконечное разнообразие типов для собственных нужд.
Давайте обсудим детали разных типов данных, как примитивных, так и встроенных. Поговорим и о кастомных типах.
Примитивные типы
JavaScript различает семь типов:
undefined — обозначает тип значения переменной, которую объявили, но не инициализировали. Этот тип для данных, которых нет.
boolean — принимает только два значения «истина» и «ложь».
number — попытка научиться записывать любое число в ограниченную память компьютера (провалилась).
string — последовательность символов. JavaScript умеет преобразовывать значение любого примитивного типа в строку. Значение типа string в JavaScript неизменяемое, нельзя изменить одну букву в слове.
symbol — специальный тип данных. Задача значений типа symbol — служить специальными именами для обозначения специальных свойств объектов. Так много слов «специально», что мы дадим разъяснения ниже.
bigint — низкий поклон в сторону крипро- валют, токенов, контрактов. Они оперируют большими числами, и там этот тип данных очень пригождается. Скорее всего, не пригодится в вашем обычном фронтенде.
object — кроме примитивных типов в JavaScript есть структурные типы (объекты), а среди объектов особо выделяют функции. Обратите внимание, что в JavaScript функции это тоже объекты.
А как же null ? null — ещё одна возможность обозначить данные, которых нет. Да, в JavaScript есть целых два способа для обозначения ситуации, когда в переменной нет значения. Обратите внимание, что в JavaScript null это значение, а не тип данных и это значение может содержаться в JSON, а undefined в JSON не бывает.
Тип undefined
Когда вы объявляете переменную, но не присваиваете ей значение, переменная получает значение «по умолчанию» и это значение undefined , которое имеет тип undefined.
let buffer;
Переменная buffer объявлена с ключевым словом let , но до тех пока она не получила явного значения ее значение undefined .
Хитрость JavaScript в том, что иногда разработчик присваивает переменной результат работы функции, но переменная все равно остается undefined . В следующем примере buffer получает значение при объявлении. Можете ли вы догадаться почему buffer всё равно undefined ?
function trickyComputation () < let a = 0; while(a<3)< a+=2; >> let buffer = trickyComputation();
Все дело в том, что trickyComputation не возвращает значения. Попробуйте рассуждать о том, что цель функции — «присвоить значение» ключевому слову return . В вышеприведенном примере нет return , и этот самый воображаемый return остается неинициализированным, отсюда и undefined .
Будьте внимательны к функциям.
Тип boolean
В JavaScript про любое значение можно задать вопрос «похоже ли оно на правду?». Значение true — правда, false — ложь. Пустое значение обычно значит false.
Посмотрим на практике. Для проверки правдивости будем пользоваться вот таким методом showTrueness .
const showTrueness = (n,value)=> console.log(`$ - $`);
// эти значения похожи на ложь showTrueness(1,false); // false showTrueness(2,''); // false showTrueness(3,0) // false
Когда значение отличается от банально-начального, оно обычно истинно.
showTrueness(1, true); // true showTrueness(2, 42); // true showTrueness(3, 'false') // true
Как видите, слово false получает приговор — истина! Но это еще не самый курьёзный случай с boolean :
showTrueness(1, []); // true showTrueness(2, <>); // true showTrueness(3, [] === []); // false showTrueness(4, <> === <>); // false
Хотя обычно пустые значения это ложь, пустой массив (1) и пустой объект (2) оказались истиной. А ещё в следующем отрывке JavaScript рассматривает число 0 эквивалентной строке ‘0’.
Видите 0==’0′ показывает true . При этом по отдельности число 0 и строка ‘0’ относятся к разным типам.
showTrueness(1, 0 == '0'); // true showTrueness(2, 0); // false showTrueness(3, '0'); // true
Это происходит из-за способности JavaScript преобразовывать значения из одного типа в другой. Если невнимательно следить за такими преобразованиями, могут возникнуть ошибки, которые очень тяжело найти.
Для избежания сюрпризов используйте явное преобразования значений между типами и строгое сравнение (===) (! ==). В этом вам поможет набор правил eslinter от академии.
Тип number
Тип значения number предназначен для моделирования действительных чисел. Действительных чисел очень много — бесконечно много — а компьютерная память ограничена. Инженерам из IEEE пришлось даже выпустить отдельный международный стандарт для чисел с плавающей точкой — IEEE 754.
Во многих случаях числа с плавающей точкой ведут себя нормально. Сумма чисел 1 и 2 равна 3, а сумма чисел 0.1 и 0.2 равна сумме чисел 0.2 и 0.1. Но это не всегда так, и 0.1 + 0.2 может быть не равно 0.3. Если в школе вам говорили, что от перемены мест сумма не меняется, то в JavaScript это не всегда верно.
И если будете делать выписку в крипто-банке, два раза подумайте, в каком порядке вы хотите складывать числа. Потому что значение переменной currentBalanceV1 больше значения переменной currentBalanceV2 , а всё потому что величины складываемых чисел сильно отличаются.
showTrueness(1, 1+2 === 3); // true const lastMonthCredits = Array.from (,()=>Number.EPSILON/10); const previousMonthBalance = 0.8; const currentBalanceV1 = previousMonthBalance + lastMonthCredits.reduce((a,b)=>a+b,0); const currentBalanceV2 = 0 + lastMonthCredits.reduce((a,b)=>a+b,previousMonthBalance); showTrueness(2, currentBalanceV1>currentBalanceV2); // true showTrueness(3, 0.1+0.2 === 0.2+0.1) // true
Советы при работе с числами
- При работе с числами старайтесь сначала делать действия над числами сравнимой величины
- При работе с числами старайтесь сравнивать их порядок, (что больше, а что меньше), а не равенство.
- Изучите назначение предопределенных констант Number.MAX_SAFE_INTEGER , Number.EPSILON и других.
Задание для самопроверки. На сколько отличаются currentBalanceV1 , currentBalanceV2 и previousMonthBalance ?
Тип string
Строки в JavaScript — это неизменяемые цепочки букв. Вы можете добавлять строки одну к другой, брать нужную букву по порядку. Нумерация букв в строке начинается с нуля, поэтому вы видите в примере (-1).
const show = (value)=>console.log(value); const alfavit = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'; show(alfavit); // "абвгдеёжзийклмнопрстуфхцчшщъыьэюя" show(alfavit[1-1]); // "а" show(alfavit[33-1]); //"я"
Поскольку строка неизменяемая, вы не можете просто взять и поменять букву, забить звездочками часть номера банковской карты не получится.
alfavit[2-1]='*'; show(alfavit); // "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
В виде строк можно хранить любой вид данных, нужно только договориться. Однако мы не рекомендуем вам изобретать велосипед. Лучше используйте общепринятые способы преобразования данных в строку и обратно: JSON.stringify , JSON.parse (для превращения в JSON и обратно), Intl.NumberFormat , Intl.DateFormat .
Оператор typeof
Вы можете использовать оператор typeof для определения типа значения. Этот оператор воздействует на переменную и возвращает имя типа (если знает)
const show = (value, type)=>console.log(`typeof ($) is $`); const whatIsMyTypeName = (value)=> < switch(typeof value)< case 'boolean': case 'number': case 'string': return show(value, typeof value); default: show(value, 'выясним позднее') >> whatIsMyTypeName(true); // typeof (true) is boolean" whatIsMyTypeName(42); // "typeof (42) is number" whatIsMyTypeName('миру-мир!'); // "typeof (миру-мир!) is string" whatIsMyTypeName(<>); // "typeof ([object Object]) is выясним позднее" whatIsMyTypeName(window); // "typeof ([object Window]) is выясним позднее"
Встроенные типы
В JavaScript вам доступно много типов данных — ими могут быть элементы браузера, документа, видео и изображения и многое другое. Полный набор зависит от окружения. Но почти наверняка вам будут доступны массивы, словари и множества.
Array (массивы)
Сразу обратите внимание, что слово Array мы употребляем с большой буквы, в то время как boolean , number , string — с маленькой.
Если вы разработчик на JavaScript, то методы работы с массивами нужно знать и уметь вспомнить, даже если вас разбудили посреди ночи. Проверьте, что вы знаете о существовании методов массива length, from, map, sort, reduce, filter, find, indexOf, findIndex
Вы можете получить значение из массива по индексу, а можете заменить это значение другим. Индексом значения в массиве выступает число. Индексы идут от 0 в сторону увеличения.
JavaScript не будет следить за тем, обращаетесь ли вы по индексу правомерно или выходите за пределы массива. Вы даже можете положить новое значение по индексу «минус один» и получить его обратно. Однако вас ждут сюрпризы, если вы не будете контролировать размеры массива и значения индекса.
const show = (index, value)=> console.log(`$: $`); const items = [ 'Chrome', 'Opera', 'Edge' ]; // Предоставляет элемент show(1,items[1]) // "1: Opera" // Ошибки нет, но и элемента тоже show(100, items[100]); "100: undefined" // Как, впрочем, и здесь show(-1, items[-1]);// "-1: undefined" items[12] = 'safari'; items[-1] = 'IE'; // Ошибки нет, а элемент появился show(12, items[12]);//"12: safari" show(-1, items[-1]);// "-1: IE" // а где IE? show('all', items)"all: Chrome,Opera,Edge. safari" // А тут всё ещё есть show(-1, items[-1]);// "-1: IE"
Совет: контролируйте индексы, знайте размер массивов, с которыми работаете.
Set
Тип данных Set позволяет вам хранить набор уникальных элементов. Этим он отличается от массива. При работе с массивом вам придется предпринимать специальные усилия для поддержания уникальности элементов, Set сделает это за вас. В отличии от массива Set не позволяет произвольный доступ к элементу. Вы можете проверить наличие и получить список в порядке вставки.
const items = new Set(Array.from (,(_,ix)=>ix)); const str = (value)=>`$`; const compare = (left,right)=>leftcompare(left,right); const byStringValue =(left,right)=>compare(str(left),str(right)); console.log([. items].sort(byNumbericalValue)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] console.log([. items].sort(byStringValue)); // [0, 1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9] items.add(100); items.add(20); //порядок, в котором JavaScript отдает содержимое Set, зависит от порядка добавления элементов console.log([. items].sort(byNumbericalValue)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 20, 100] console.log([. items].sort(byStringValue)); // [0, 1, 10, 100, 11, 2, 20, 3, 4, 5, 6, 7, 8, 9] console.log([. items]); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 100, 20]
Совет: для вывода элементов из Set в нужном порядке позаботьтесь о функции сортировки.
Кастомные объекты <> — ассоциативный массив
JavaScript объекты — это словари, где строковому ключу поставлен в соответствие элемент-значение. Возможны два синтаксиса доступа к значению ключа
const dictionary = < 'language':'javascript', 'type': 'structured', 'age': 25 >console.log(dictionary.language); // javascript console.log(dictionary['language']); // javascript const key = 'language'; console.log(dictionary[key]); // javascript
Важно! Ключ в кастомных объектах — это строковое значение. JavaScript неявно преобразует значение к строковому типу перед использованием его в качестве ключа.
Это ограничение не позволяет использовать объекты для создания связей между HTML-элементами и дополнительными данным, нужными для работы программы. Для этого используйте тип Map .
Map
По логическому устройству Map очень похож на ассоциативный массив, но свободен от ограничения на вид ключа.
const DictionaryCO = <>; // инициализируем вариант ассоциативного массива на основе обычного объекта const DictionaryMap = new Map(); // инициализируем вариант ассоциативного массива на основе встроенного типа Map // Первый эксперимент будет связывать ключ 'k1' и значение 'v1' const aKey = 'k1'; const aValue = 'v1'; // Второй эксперимент будет связывать ключ в виде объекта и значение - строку // такой вариант в жизни встречается если для нескольких HTML элементов нужно ввести дополнительные данные, которые нельзя положить в data-xxx атрибут const bKey = ; const bValue = 'v2'; // Третий эксперимент покажет нам недостатки ассоциативного массива на основе обычного объекта. Мы используем в качестве ключа - другой объект const cKey = ; const cValue = 'v3'; // заполняем первый ассоциативный массив (который в виде объекта) DictionaryCO[aKey] = aValue; DictionaryCO[bKey] = bValue; DictionaryCO[cKey] = cValue; // заполняем второй ассоциативный массива (которы в виде Map) DictionaryMap.set(aKey, aValue); DictionaryMap.set(bKey, bValue); DictionaryMap.set(cKey, cValue); console.log('a object', DictionaryCO[aKey]); // "a object", "v1" console.log('b object', DictionaryCO[bKey]); // "b object", "v2" // хотя мы пытаемся получить значение по ключу cKey, почему-то // мы получаем значение, которое связали с ключом bKey console.log('c object, oops!', DictionaryCO[cKey]); // "c object, oops!", "v2" console.log('a map', DictionaryMap.get(aKey)); // "a map", "v1" console.log('b map', DictionaryMap.get(bKey)); // "b map", "v2" // в этом варианте ассоциативного массива все сработало так, как мы и ожидали console.log('c map', DictionaryMap.get(cKey)); // "c map", "v3"
Собственные структуры данных
Утиная типизация
Программисты работают с лозунгом «Algorithms + Data Structures = Programs». При работе с JavaScript разработчик преобразует требования заказчика в алгоритмы и структуры данных. Примитивных типов данных, доступных в JavaScript явно недостаточно для всего разнообразия бизнес задач, поэтому приходится использовать кастомные структуры. Для приложения электронной очереди потребуется структура с номером и временем, для умного дома — структура с данными о температуре чайника, заполненности холодильника и т. п.
let cusomerInQueue = < numberOnScreen: 'A42', timeTaken: '11:40' >let smartHouseState = < kettler: 80, fridge:< milk: true, banana: false, >, >
В вашей программе вы получаете сведения о заполнении холодильника, потому что ожидаете наличия поля fridge в структуре объекта, а не потому, что переменная называется smartHouseState . Выяснив, что в структуре объекта нет поля fridge вы смело можете сказать, что значение переменной cusomerInQueue не связано с управлением умным домом, и наоборот наличие такого поля в переменной smartHouseState подсказывает вам, что её значение описывает умный дом. Такой подход, когда о назначении значения вы судите по его структуре, называется структурной типизацией.
Иногда вы знаете структуру значения (потому что знаете), а иногда вам приходится как-то догадываться. Но как, ведь typeof в этой ситуации не поможет?
Object.keys и другие шпионские средства
JavaScript предоставляет вам ряд средств и сервисов, с помощью которых вы можете исследовать значение и принимать решение, как его обрабатывать.
Основными помощниками в исследовании полученных данных вам будут
- Статический метод Object.keys
- Оператор typeof
- Оператор instanceof
- Статический метод Object.hasOwn
const someValue = < title: 'Cruella', release: new Date(2021,05,03), empty: undefined, >console.log('all keys', Object.keys(someValue)); // "all keys", ["title", "release", "empty"] console.log('typeof key release', typeof someValue.release); // "typeof key release", "object" console.log('instance of Date', someValue.release instanceof Date) // "instance of Date", true console.log('missing value', typeof someValue.empty); // "missing value", "undefined" console.log('missing key', Object.hasOwn(someValue, 'missing')); // "missing key", false
Проблемы типов JavaScript
Динамическая структурная типизация JavaScript имеет давнюю историю и восходит к идее создания небольших обработчиков событий в статических HTML страницах. С тех пор все поменялось, и динамическая типизация становится серьезным риском при постепенном и постоянном совершенствовании приложения.
Например, у нас была функция transform . Мы обнаружили, что она ломается при получении значения null .
const transform = (value) => value.replace('с', 'б'); let data = 'соль' console.log(transform(data).toUpperCase()) // "БОЛЬ" data = null; try < console.log(transform(data).toUpperCase()) // не выполнится >catch (err) < console.log(err.message) // "Cannot read properties of null (reading 'replace')" >
Исправили программу в одном месте — она начала ломаться в другом. Как разорвать замкнутый круг?
const transform2 = (value) => < if (typeof value === 'string') < return value.replace('с', 'б'); >> data = 'соль'; console.log(transform2(data).toUpperCase()) // "БОЛЬ" data = null try < console.log(transform2(data).toUpperCase()) >catch (err) < console.log(err.message) // "Cannot read properties of undefined (reading 'toUpperCase')" >
Вам поможет TypeScript! Но об этом в следующий раз.
Узнать больше
- Что нужно знать фронтендеру, кроме JavaScript
- 12 полезных книг по JavaScript
- Зачем фронтендерам React, если есть JavaScript
- TypeScript. Зачем он нужен и почему так популярен
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.