Перевод заметки Daniel Li: (Not) Everything in JavaScript is an Object.
Те, кто просто хочет получить ответы, не стесняйтесь сразу переходить к «Итого» в конце заметки
Существует много путаницы в том, является ли JavaScript объектно ориентированным языком программирования (ООП) или функциональным языком. Действительно, JavaScript может работать и так, и так.
Но это заставило людей задуматься: «Все ли в JavaScript - объекты?», «А что насчёт функций?».
Эта заметка расставит все на свои места.
В JavaScript существует шесть примитивных типов данных:
- Булевые значения -
true
илиfalse
null
undefined
number
- 64-битный флоат (в JavaScript нет целых чисел)string
symbol
(появился в ES6)
В дополнение к этим шести примитивным типам, стандарт ECMAScript также определяет тип объекта, представляющий собой хранилище ключей.
const object = {
key: "value"
}
Итак, короче говоря, все, что не является примитивным типом, является объектом, включая функции и массивы.
Все функции - объекты
// Примитивные типы
true instanceof Object; // false
null instanceof Object; // false
undefined instanceof Object; // false
0 instanceof Object; // false
'bar' instanceof Object; // false
// Непримитивные типы
const foo = function () {}
foo instanceof Object; // true
Примитивные типы не имеют методов; поэтому вы никогда не встретите undefined.toString()
. Кроме того, из-за этого примитивные типы неизменяемы, потому что у них нет методов, которые могли бы их изменить.
Вы можете переназначить примитивный тип переменной, но это будет новое значение, старое не будет и не может быть изменено.
const answer = 42
answer.foo = "bar";
answer.foo; // undefined
Примитивные типы неизменяемы
Кроме того, примитивные типы сохраняются как значения, в отличие от объектов, хранящихся в качестве ссылки. Это важно иметь ввиду при выполнении проверок равенства.
"dog" === "dog"; // true
14 === 14; // true
{} === {}; // false
[] === []; // false
(function () {}) === (function () {}); // false
Примитивные типы хранятся по значению, объекты хранятся по ссылке
Функция - это особый тип объекта со специальными свойствами, например, constructor
и call
.
const foo = function (baz) {};
foo.name; // "foo"
foo.length; // 1
И как к обычным объектам, вы можете добавлять новые свойства:
foo.bar = "baz";
foo.bar; // "baz"
Это делает функции объектами первого класса, так как их можно передать в качестве аргумента в другие функции, как и любой другой объект.
Метод - это свойство объекта, являющееся функцией.
const foo = {};
foo.bar = function () { console.log("baz"); };
foo.bar(); // "baz"
Если у вас есть несколько объектов, использующих одну и ту же реализацию, вы можете поместить эту логику внутри функции-конструктора, а затем вызвать конструктор для создания этих объектов.
Функция-конструктор ничем не отличается от любой другой функции. Функция используется в качестве конструктора, когда она используется после ключевого слова new
.
Любая функция может быть функцией-конструктором
const Foo = function () {};
const bar = new Foo();
bar; // {}
bar instanceof Foo; // true
bar instanceof Object; // true
Функция-конструктор вернет объект. Вы можете использовать this
внутри тела функции, чтобы назначать новые свойства объекту. Поэтому, если мы хотим сделать много объектов с свойством bar
и его значением baz
, мы можем создать новую функцию-конструктор Foo
, инкапсулирующую эту логику.
const Foo = function () {
this.bar = "baz";
};
const qux = new Foo();
qux; // { bar: "baz" }
qux instanceof Foo; // true
qux instanceof Object; // true
Вы можете использовать функцию-конструктор для создания нового объекта
Запуск функции-конструктора, такой как Foo()
, без new
запустит Foo
как обычную функцию. this
внутри функции будет соответствовать контексту выполнения. Поэтому, если мы вызовем Foo()
вне всех функций, она фактически изменит объект window
.
Foo(); // undefined
window.bar; // "baz"
И наоборот, запуск обычной функции в качестве функции-конструктора обычно возвращает новый пустой объект, как вы уже видели.
const pet = new String("dog");
Путаница возникает из-за таких функций, как String
, Number
, Boolean
, Function
и так далее, которые при вызове с new
создают объекты-оболочки для соответствующих типов.
String
- глобальная функция, создающая примитивную строку при передаче аргумента; она попытается преобразовать аргумент в строку.
String(1337); // "1337"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
String(); // ""
String("dog") === "dog" // true
typeof String("dog"); // "string"
Но вы также можете использовать функцию String
в качестве функции-конструктора.
const pet = new String("dog")
typeof pet; // "object"
pet === "dog"; // false
Будет создан новый объект (часто называемый объектом-оболочкой), представляющий строку dog
со следующими свойствами:
{
0: "d",
1: "o",
2: "g",
length: 3
}
Интересно, что конструктором как примитивных строк, так и объекта является функция String
. Ещё интереснее, что вы можете вызвать .constructor
примитивной строки (и это при том, что мы уже рассмотрели, что примитивные типы не могут иметь методы!).
const pet = new String("dog")
pet.constructor === String; // true
String("dog").constructor === String; // true
Этот процесс называется автоупаковкой (autoboxing). Когда вы пытаетесь вызвать свойство или метод для определенных примитивных типов, JavaScript преобразует его во временный объект-оболочку и получает доступ к его свойству или методу, не затрагивая сам оригинал.
const foo = "bar";
foo.length; // 3
foo === "bar"; // true
В приведенном выше примере, чтобы получить доступ к свойству length
, JavaScript упаковывает foo
в объект-оболочку, получает доступ к свойству length
объекта-оболочки, а после уничтожает его. Это делается без изменения foo
(foo
по-прежнему является примитивной строкой).
Это также объясняет, почему JavaScript не возмущается, когда вы пытаетесь присвоить свойство примитивному типу, потому что присвоение выполняется на временном объекте-оболочке, а не на самом примитивном типе.
const foo = 42;
foo.bar = "baz"; // Присвоение, выполняемое на объекте-оболочке
foo.bar; // undefined
Он будет возмущаться, если вы попробуете проделать это с примитивным типом, не имеющим объект-оболочку, таким как undefined
или null
.
const foo = null;
foo.bar = "baz"; // Uncaught TypeError: Cannot set property 'bar' of null
- Не все в JavaScript - объект
- В JavaScript 6 примитивных типов
- Все, что не является примитивным типом, является объектом
- Функции - особый тип объекта
- Функции могут использоваться для создания новых объектов
- Строки, булевые значения и числа могут быть представлены в качестве примитивных типов и в качестве объектов
- Определенные примитивные типы (строки, булевые значения, числа) ведут себя как объекты благодаря наличию в JavaScript автоупаковки.
Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.