Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Нативные объекты JS: Программирование, управляемое данными

Ошибки видео

На самом деле боксинг срабатывает, даже если мы обращаемся к примитивному типу напрямую

'hello'.length

Но важно понимать, что у строки нет свойств, прозрачно для нас происходит оборачивание в объект и работает это примерно так: new String('hello').

Изменение объекта

Обычно, изменение объекта происходит так:

const obj = { key: 'value' };
obj.key = 'another value';

Но что делать, если свойство заранее неизвестно? Тогда можно воспользоваться таким способом:

const name = 'key';
const obj = { key: 'value' };
obj[name] = 'another value';

Вычислимые свойства встречаются достаточно часто на этапе создания объекта, по этой причине появился специальный синтаксис, позволяющий задавать подобные свойства в литерале. Дополнительный бонус в том, что при таком подходе в коде пропадают лишние изменения (мутации) и код становится более функциональным:

const name = 'key2';
const obj = { key: 'value', [name]: 'another value' };
// { key: 'value', key2: 'another value' }

Единственное отличие от стандартного синтаксиса в том, что ключ - это переменная (а не имя), взятая в квадратные скобки.

Еще одна интересная возможность объектов в JS - сокращенный синтаксис создания объектов при использовании переменных или констант. Обычное создание выглядит так:

const name = 'Mike';

const user = { name: name };

В примере выше, имя свойства совпадает с именем переменной, в которой хранится нужное значение. JS позволяет написать этот код лаконичнее. Можно просто опустить часть name:. И получится:

const name = 'Mike';

const user = { name };

То же самое работает и для нескольких переменных:

const name = 'Mike';
const surname = 'Smith';

const user = { name, surname };

Можно даже мешать разные стили в рамках одного объекта:

const name = 'Mike';

const user = { name, surname: 'Smith' };

--

Теперь, когда мы уже хорошо понимаем в чём соль объектно-ориентированного программирования, мы можем познакомиться с настоящими нативными объектами, которые реализованы прямо в JS.

Объекты в JS

Разберём простой пример:

// primitive type
const str = 'hello, world';

https://repl.it/@hexlet/js-ddp-native-objects-properties-and-methods

Записываем в константу строчку и после этого используем известный уже для нас синтаксис доступа к свойствам, только не через модуль, а через объект. Это очень похожий механизм, когда мы вызывали методы или передавали сообщение. Например, с помощью length, мы можем посмотреть длину строчки:

// property
str.length; // 12

Метод toUpperCase возвращает нам новую строчку (он не мутирует текущую), которая переводит строчку в верхний регистр:

// method
str.toUpperCase(); // HELLO, WORLD

Если мы не будем делать вызов после toUpperCase, а просто сделаем обращение к свойству и распечатаем, то что лежит внутри, мы увидим, что там лежит function:

// actually, just property
str.toUpperCase; // [Function: toUpperCase]

В большинстве языков принято разделять понятия свойства и методы и говорить об этом явно. В JavaScript нет методов, есть только свойства, просто в некоторых свойствах могут лежать функции, что довольно просто, потому что с функциями мы уже довольно хорошо знакомы: их можно куда угодно записывать, можно передавать как аргументы, получать как значения.

С объектом примерно также. Есть некоторый объект внутри переменной или константы, точка – вызов какого-то свойства, которое может быть функцией (тогда ставятся скобки).

Давайте договоримся, что в будущем мы будем говорить слово «методы», просто потому что так принято и все так делают во всем мире, на всех языках. При этом мы понимаем, что в JS – это просто функция, которая записана и лежит внутри определённого свойства с тем же именем.

JavaScript не является простым языком, когда мы говорим о понимании его внутренностей и глубоком понимании того, как он работает. На самом деле строчка – это примитивный тип, а не объект. Но если мы обратимся к строке напрямую, то все отработает также, как если бы она была записана в константу или переменную:

'hello'.length // 12

Почему нет ошибки и строка начинает вести себя, как объект?

Если простыми словами, то происходит какой-то некий процесс, который делает для вас прозрачным трансформацию в объект. Этот процесс называется boxing. То есть в тот момент, когда вы складываете в константу какой-то примитивный тип и после этого через точку к нему обращаетесь, то в этот момент JS за вас оборачивает этот примитивный тип в соответствующий объект, который связан с текущим примитивным типом и он начинает работать таким образом. После этого происходит его unboxing (разворачивание) и внутри всё равно оказывается примитивный тип. Этот механизм следует определённым целям и попозже мы разберёмся с ним чуть глубже.

Тип данных: объект

В JavaScript есть отдельный тип данных, который называется объект и по сути любой объект, с которым мы работаем, является этим типом данных (точнее его подтипом, но об этом мы будем говорить позже, потому что сейчас мы сосредоточены больше на базовом понимании и связи с ООП, чем на особенностях работы JS).

Как же работает этот тип данных:

const card = {
  name: 'percent card',
  key: 'value',
};

Он представляет собой вот такую структуру, в которой мы задаём ограничители (фигурные скобки в начале и в конце), после этого описываем ключ и значение.

Значением может быть всё что угодно. На вход ожидается любое выражение, после этого ставится запятая и дальше опять пара ключ-значение и тд. После этого в card у нас оказывается объект.

Здесь начинается некоторая путаница, которую вводит JS и люди, которые знакомы только с этим языком часто смешивают это в одно и постоянно употребляют в разговоре. В свою очередь люди из других языков это не совсем понимают, потому что в других языках такая запись обозначает несколько иную вещь. К этому надо просто привыкнуть и всегда внутри у себя дифференцировать о чём мы говорим: о типе данных объект или объекте, как мы понимаем это с точки зрения ООП, хотя в каком-то смысле разница в JS стирается.

Как происходит обращение к объекту

Поскольку этот тип данных (объект) является и настоящим объектом в том числе, поэтому обращение к его ключам идёт, как обращение к обычным свойствам в объекте:

const card = {
  name: 'percent card',
  key: 'value',
};

card.key; // value

Мы пишем card.key и получаем значение, всё довольно просто.

Если мы обратимся к неизвестному свойству, то мы получим undefined.

card.wrongKey; // undefined

Имеется в виду, что свойство не определено, хотя оно может быть определено и равно undefined, но обычно так не делают.

Мы уже говорили, что определять самостоятельно undefined – неправильно, потому что вы просто не отличите определено оно или нет и что важно – вы не получаете ошибку. То есть, если вы где-то случайно (в JS легко это сделать, поскольку это динамический язык) допустите синтаксическую ошибку, то ваш код может продолжить работать, но продолжит работать с ошибками и при этом не упадёт. Это довольно опасная вещь, поэтому за этим надо следить.

У объектов в JS есть другой синтаксис. Вы всегда можете обратиться через квадратные скобки определив ключ, как строчку:

// map syntax
card['key']; // value
card['wrongKey']; // undefined

https://repl.it/@hexlet/js-ddp-native-objects-object

Это позволяет, например, обращаться к свойствам, которые названы чуть сложнее, чем просто имена состоящие из алфавита английского языка. Например, если вы используете целое предложение, пробелы или какие-то специальные символы, то используйте такой способ обращения. Это не частый кейс, но такое бывает когда у вас объект динамический, то есть вы откуда-то собираете информацию – это будет достаточно удобно. Этот синтаксис ведёт себя точно также, если мы ошибаемся с ключом, то получаем undefined.

Что такое константа в JS

Есть одна интересная деталь, которая уже может вводить в заблуждение. До этого момента мы привыкли, что константа обозначает реально константу и она никогда не меняется, но посмотрите внимательно на этот пример:

const card = {
  name: 'percent card',
  key: 'value',
};

card.wrongKey; // undefined
card.wrongKey = 'something';
card.wrongKey; // something

// TypeError: Assignment to constant variable.
card = {};

https://repl.it/@hexlet/js-ddp-native-objects-constant

Мы пишем card.wrongKey, который естественно пишет undefined, потому что он не определён, но после этого мы делаем присваивание (ничего не происходит, всё срабатывает молча) и после этого мы делаем снова вызов и получаем это значение.

И вот тут возникает 2 интересных момента:

  1. Объекты являются mutable, то есть изменяемыми, что может и не привычно после наших курсов, в которых мы всё делали неизменяемым, но JS – это классический императивный язык, в котором всё изменяемое и при этом с хорошими возможностями функционального программирования.
  2. Ключ поменял сам объект. И если до этого мы говорили про константу, как о чём-то, что нельзя менять, то получается что константа – это что-то, что можно менять.

Возникает вопрос, а почему это константа?

Константа она только по той простой причине, что вы по имени card не можете записать какой-то объект целиком, то есть вы не можете заменить card полностью.

Если вы перезапишите card чем-то новым (динамический язык позволяет это) – вы получите ошибку:

// TypeError: Assignment to constant variable.
card = {};

Кстати, так можно определять пустой объект const obj = {};

Но если вы меняете внутренности этого объекта, который вы положили в card, то всё будет работать.

У вас может оставаться какое-то странное ощущение, что константа не совсем константа, но какая-то определенная логика в этом тоже есть и к этому нужно просто привыкнуть. Этот момент будет детальнее разобран в следующем курсе JS: Коллекции.

Объект с функциями

Давайте посмотрим, как использовать наши объекты вместе с функциями.

Функции – это объекты первого рода.

В данном случае под словом "объект" имеется в виду, как некоторая сущность в обычном понимании этого слова. Важно не путать это с ООП.

В объект (тип данных) мы можем записать любую функцию и пользоваться ей.

const card = {
  name: 'percent card',
  damage: health => Math.round(health * (80 / 100)),
};

card.name; // percent card
card.damage(10); // 8

https://repl.it/@hexlet/js-ddp-native-objects-object-using

Создаём карту и после этого вызываем name и damage.

name – статичное свойство, которое возвращает имя карты damage – вызов метода (на самом деле это свойство, внутри которого записана функция) куда передаётся значение здоровья.

Как видите, такой синтаксис выглядит приятнее, потому что это уже встроенный механизм, но по своему смыслу, семантике и результату действия – это абсолютно то же самое, что мы делали самостоятельно.

Создание объекта

Наши конструкторы и наши объекты превращаются в стандартный механизм, с которым мы работали.

// percentCard.js

export const make = (name, percent) => {
  return {
    name: name,
    damage: health =>
      Math.round(health * (percent / 100)),
  }
};

У нас есть файл, который представляет из себя модуль. Он экспортирует некий конструктор, внутри которого мы теперь чуть-чуть проще реализуем всю нашу логику.

Мы просто возвращаем объект, как тип данных, внутрь которого мы записываем переданные параметры: в name – имя, а в функцию damage мы передаём проценты исходя из которых будет считаться урон.

С точки зрения интерфейсов ничего не меняется кроме варианта использования. То есть мы теперь не просто вызываем функцию и передаём туда строчку, а берём константу, пишем точку и обращаемся к какому-то свойству.

Динамическая диспетчеризация

Используя стандартный механизм мы получаем диспетчеризацию.

const card = percentCard.make('percent card', 60)
// const card = simpleCard.make('simple card', 3)
card.name; // percent card
card.damage(10); // 6

Диспетчеризация происходит по имени свойства, потому что здесь выбор строится на основе объекта. То есть объект уже сам связан с какой-то конкретной функцией.

На самом деле этот механизм чуть сложнее, просто сейчас нет смысла в это погружаться.

В JS существует крайне важная концепция, он сильно отличается от многих ООП языков – это прототипы. Об этом мы поговорим позже, чтобы не вводить сейчас слишком много новой информации. Тем более она не имеет никакого значения к решаемым нами проблемам.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»