skip to main content

Почти всё в JavaScript — объект

js

Кратко 🔗

В JavaScript объект является прародителем всех других сущностей. Все типа данных и структуры, кроме примитивных, являются потомками объекта. По этой причине абсолютно у всех наследников объекта имеется набор общих методов: toString, valueOf и др.

Как понять 🔗

Массивы и функции 🔗

Объект — это сущность с набором свойств. Мы можем добавлять, менять и удалять эти свойства.

const programmer = { name: "John", level: "Junior" }

programmer.mainLanguage = "JavaScript"
delete programmer.level

console.dir(programmer)

консоль, в которой выведены свойства объекта из примера

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

const shows = ["Breakind Bad", "The Office", "Silicon Valley"]

shows.length // свойство массива

shows[1] // получить элемент массива, аналогично как у объекта shows['1']

Аналогичная ситуация с функциями — у них тоже есть набор свойств, который можно увидеть, выведя информацию о ней в консоль.

function sum(a, b) {
return a + b
}

sum.arguments // можно вызвать свойство функции
sum.someField = "value" // можно присвоить значение в поле

console.dir(sum)

В выводе есть и свойство someField, которое мы присвоили, и набор встроенных свойств и методов.

консоль, в которой выведены свойства функции

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

Давайте посмотрим на свойство __proto__ у функции sum, описанной выше.

Свойство __proto__ является устаревшим (deprecated), не используйте его в коде, особенно для того, чтобы самостоятельно устанавливать прототип.

консоль, в которой выведен прототип функции sum

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

методы объекта в консоли

В этой цепочке следующего прототипа уже нет, а это значит что мы дошли до самого конца цепочки, т.е нашли прародителя. Если подобным образом вывести в консоль любой массив, то спускаясь ниже по цепочке прототипов, в конце обязательно будет именно прототип объекта. Любая сущность в JavaScript наследуется от объекта.

схема цепочки прототипов

Примитивы 🔗

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

const show = "Breaking Bad"

show.length // 12
show.charAt(1) // "r"
show.toUpperCase() // "BREAKING BAD"

Но строка является примитивном типом данных, откуда же у нее поведение как у объекта? Когда происходит обращение к какому-то свойству или методу у примитива, происходит автоматическая обертка (autoboxing) в специальный конструктор для примитива, который является наследником объекта. Для строки это будет функция String. У этого объекта есть свойства и методы, которые и вызываются.

const pet = "dog"
const pet2 = new String("dog") // будет создан объект

console.log(pet === pet2) // false, потому что в pet2 находится объект

console.dir(pet2)
/* Выведет
{
0: "d",
1: "o",
2: "g",
length: 3
}
*/

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

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

const cat = "Boris"

cat.color = "red" // свойство не добавится
delete color.length // так же ничего не изменится

const cats = ["Boris", "Vasya", "Murzik"]
cats.length = 5 // теперь массив стал длинной в 5 элементов
cats.someField = "value" // добавлось поле

console.dir(cats)
/*
{
0: "Boris",
1: "Vasya",
2: "Murzik",
someField: "value",
length: 5
}
*/


// Но не стоит путать примитив и объект, созданный через конструктор для примитива
const cat = new String("Boris")
cat.color = "black" // добавится, т.к в cat лежит объект, а не строка

Как пишется 🔗

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

const array = [1, 2, 3, 4]
array[1] // 2
array.map[(1, 2, 3)].forEach[(1, 2, 3)][2] // function map()... // function forEach()... // 3

const obj = { name: "Boris", color: "red" }
obj.color(
// red

// Здесь стоит обратить внимание на скобки, в них нужно объявение объекта
{ name: "Vasya", age: 30 }
).age /// 30

У примитивов без переменной можно обращаться к методам только у строки. Для всех остальных, чтобы не получить синтаксическую ошибку, нужно оборачивать значение в скобки, либо вызывать функцию-примитив (String, Number, Boolean и т.д.)

'Boris'.length // 5
'Boris'.charAt(3) // "i"

1.toFixed(1) // Так нельзя, будет ошибка

// А вот если обернуть в скобки, то будет работать
(1).toFixed(1) // "1.0"

// Так тоже будет работать
Number(1).toFixed(1) // "1.0"
Boolean(1).toString() // "true"

В работе 🔗

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