Объектно-ориентированный JavaScript: примитивы и объекты
Скорее всего, вы читали или слышали, что всё в JavaScript является объектом. Ещё чаще можно увидеть опровержения этого утверждения. Подобные споры возникают регулярно и обусловлены в большей степени недопониманием концепций JavaScript. И, на самом деле, причина считать примитивы объектами есть — у примитивов можно запрашивать свойства и методы, поведение которых во многом схоже со свойствами и методами объектов. Например, строки и массивы содержат одинаковые свойство length
:
На первый взгляд, может показаться, что свойство length
работает одинаково для строк и массивов. Тем не менее, разница есть. Чтобы понять в чём она заключается достаточно использовать оператор typeof
для каждого типа данных:
И что? Это не отменяет того, что мы можем использовать полноценные методы и свойства у примитивов. Всё верно. Но следует учитывать то, что происходит “за сценой”. У каждого примитивного типа данных есть своя функция конструктор, например, для строк — String
, для чисел — Number
. Когда вы хотите получить какое-либо свойство или метод для примитивов, вы обращаетесь не к самому примитиву, а к объекту, созданному функцией-конструктором данного примитива. Другими словами, примитивы не имеют свойств и методов и не являются объектами. Чтобы разобраться в том, что происходит “за сценой”, можно инициализировать создание новой строки с помощью функции конструктора String
:
Подобное обращение к конструктору String
происходит каждый раз, когда вы запрашиваете у строки свойство или метод. Создаётся новый объект с помощью функции конструктора, выполняется определённое действие (получение свойства или выполнение метода), а затем созданный ранее объект уничтожается, оставляя после себя только результат. Представить для себя подобное выполнение кода можно следующим образом:
Почему важно понимать, что не всё в JavaScript является объектом? Всё просто. Для любого объекта можно задать значение свойства вручную. Например, то же свойство length
для массивов и строк:
Почему свойство length
у строки не изменилось? Когда вы пытаетесь установить свойство у любого примитива, то происходит следующее:
- Создаётся новый объект:
new String('this is string')
- У нового объекта устанавливается свойство
length
со значением10
- Созданный объект уничтожается
В результате подобной схемы становится очевидно, что данные, находящиеся в исходной переменной не изменяются, а все изменения происходят с новым объектом, который в будущем будет просто уничтожен.
То же самое произойдёт и в случае, если вы захотите присвоить новое свойство или метод примитиву:
Схема работы такая же, как и в прошлом примере:
- Создаётся новый объект:
new String('str')
, которому присваивается свойствоnewProp
со значением'my new property'
- Созданный объект уничтожается
- Создаётся ещё один новый объект
new String('str')
, которому присваивается методnewMethod
- Объект уничтожается
- Мы пытаемся получить значение несуществующего свойства
newProp
, в результате чего получаемundefined
- Мы пытаемся выполнить несуществующий метод
newMethod
, что эквивалентноundefined()
, и получаем ошибку
Примитивные данные
В JavaScript все примитивные данные разделяются на 6 типов:
- строки:
'str'
,"str"
,`str`
- числа:
2
,0
,100.34
- boolean:
true
иfalse
undefined
null
- символы
Symbol()
У строк, чисел и boolean есть функции конструкторы, с помощью, которых можно их инициализировать:
Но несмотря на то, что у вас есть возможность создавать примитивы с помощью функций конструкторов, делать это в реальных проектах не надо ни при каких обстоятельствах. Всегда используйте литералы для создания примитивов (за исключением символов, так как для них не предусмотрено формы литерала, их нужно создавать при помощи функции Symbol()
). Почему не стоит создавать примитивы с помощью функций конструкторов? Всё, что вам нужно запомнить — при подобном создании примитива вы не получаете само значение, а только его объект-обвертку. В некоторых ситуациях подобное поведение может быть опасным и привести к проблемам. Например, при создании значения типа boolean
через конструктор:
В результате выполнения данного кода в консоль выведется сообщение. Код внутри конструкции if
выполняется, так как значение, находящееся в переменной val
, является объектом с содержимым {[[PrimitiveValue]]: false}
, а любой объект, даже пустой, в JavaScript является правдивым значением.
Вы можете использовать функции String
, Number
и Boolean
без ключевого слова new
. При подобном использовании всё, что они будут делать — приводить типы. Несколько примеров:
Иногда подобные решения бывают полезными и позволяют сократить объем необходимого кода, например, если нужно отфильтровать все ложные значения из массива, то можно воспользоваться функцией Boolean
:
null является объектом?
Если вы попробуете использовать оператор typeof
для всех примитивов, то, в целом, вы не обнаружите ничего нового для себя:
Всё складывается хорошо, до тех пор, пока вы не опробуете данный оператор на null
Если вы подумали, что что-то тут неправильно и нелогично, то вы абсолютно правы. Подобное поведение является не более, чем багом языка. Но за столько лет можно же было бы это исправить? Конечно, можно, но следует понимать, что подобное поведение typeof null
используется практически в каждом приложении или библиотеке. Таким образом, исправить подобный баг невозможно, так как это сломает весь существующий на данный момент JavaScript код. Ввести подобное нововведение в новый стандарт ECMAScript тоже не получится, так как будет потеряна обратная совместимость.
Итого, null
не является объектом, несмотря на то, что оператор typeof
утверждает обратное. Это значит, что в null
не могут быть записаны никакие свойства или методы, то есть действуют все правила примитивов.
Объекты
Выше были перечислены все типы данных, которые являются примитивами. Всё остальное в JavaScript является объектом: массивы, функции, сами объекты. Любому объекту можно присвоить свойства и методы. Например, для любой функции:
В данном примере к свойству messages
функции log
можно обратиться сразу при её инициализации. Разумеется, подобным образом можно записывать и методы:
Подобным образом можно присвоить свойства и методы любому другому объекту. Но всегда стоит понимать, зачем вы это делаете и как это работает. Если раздавать методы и свойства всем видам объектов подряд, то, скорее всего, вы очень быстро заметите странное поведение. Например, если задать свойство newProp
у массива, то вы обнаружите, что в результате выполнения любого перебирающего метода (например, forEach
) с именованным свойством newProp
не была вызвана callback
функция, а также оно не влияет на длину самого массива:
Итого
Все типы данных в JavaScript разделяются на примитивы и объекты. Примитивы не могут иметь собственных свойств и методов, объекты могут. Утверждение, что всё в JavaScript является объектом неверное. Более правильно говорить, что всё в JavaScript ведёт себя, как объект, но необязательно им является.
Комментарии