Класс `Object` в JavaScript
В этой статье рассматривается класс Object в JavaScript.
В этой статье рассматривается класс Object в JavaScript с практическими примерами.
YouTube Video
Класс Object в JavaScript
Object — встроенный объект, который является основой для всех объектов в JavaScript. Многие основные возможности языка, такие как управление свойствами, наследование (цепочка прототипов), перечисление, клонирование и заморозка, предоставляются через поведение объекта Object.
Object.create
Существует несколько способов создавать объекты, и следует выбирать их в зависимости от ваших целей.
Объектный литерал (наиболее распространённый способ)
Следующий код показывает самый простой и читаемый способ создания объекта.
1// Create an object using object literal
2const user = {
3 name: "Alice",
4 age: 30,
5 greet() {
6 return `Hello, I'm ${this.name}`;
7 }
8};
9
10console.log(user.greet()); // "Hello, I'm Alice"
- В этом примере свойства и методы определяются с использованием литералов. Это просто и обычно обеспечивает высокую производительность.
Конструктор new Object()
Конструктор Object редко используется, но полезно понимать, как он работает.
1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
new Object()возвращает пустой объект, но литерал{}короче и используется чаще.
Указание прототипа с помощью Object.create
Object.create используется для создания объекта с заданным прототипом.
1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
Object.createидеально подходит для объектного проектирования на основе наследования, так как позволяет точно управлять цепочкой прототипов.
Атрибуты и дескрипторы свойств
Свойства имеют атрибуты, такие как 'value', 'writable', 'enumerable' и 'configurable', которыми можно подробно управлять с помощью Object.defineProperty.
Базовый пример использования defineProperty
Далее приведён пример определения неперечисляемых и только для чтения свойств с помощью defineProperty.
1// Define a non-enumerable read-only property
2const person = { name: "Carol" };
3
4Object.defineProperty(person, "id", {
5 value: 12345,
6 writable: false,
7 enumerable: false,
8 configurable: false
9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
- Использование
definePropertyпозволяет точно управлять поведением свойств, таким как перечисление, изменение и удаление.
Акцессорные свойства (getter / setter)
С помощью акцессоров можно внедрять логику при чтении и записи свойств.
1// Use getter and setter to manage internal state
2const data = {
3 _value: 1,
4 get value() {
5 return this._value;
6 },
7 set value(v) {
8 if (typeof v === "number" && v > 0) {
9 this._value = v;
10 } else {
11 throw new Error("value must be a positive number");
12 }
13 }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
- С помощью
getterиsetterдоступ к свойству можно реализовать как внешний API, добавляя валидацию или побочные эффекты.
Прототип и наследование (prototype / __proto__ / Object.getPrototypeOf)
Наследование в JavaScript основано на цепочке прототипов, а не на классах. Объекты могут ссылаться на другие объекты как на свои прототипы.
Object.getPrototypeOf и Object.setPrototypeOf
Следующий пример показывает, как просматривать и устанавливать прототипы.
1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
Object.getPrototypeOfполучает прототип объекта.Object.setPrototypeOfизменяет прототип существующего объекта, но это может повлиять на производительность, поэтому используйте осторожно.
Важные встроенные методы
Мы подробно объясним наиболее часто используемые и важные методы, выбранные из методов экземпляра, предоставляемых Object.prototype, а также статические методы, принадлежащие объекту Object.
hasOwnProperty, isPrototypeOf, toString, valueOf
hasOwnProperty, isPrototypeOf, toString и valueOf определяют базовое поведение объектов.
1// Demonstrate prototype methods
2const base = { greet() { return "hello"; } };
3const child = Object.create(base);
4const a = { x: 1 };
5
6console.log(a.hasOwnProperty("x")); // true
7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
8
9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
hasOwnProperty— важнейший способ проверить, является ли свойство собственным у объекта.isPrototypeOfпроверяет, содержит ли целевой объект данный объект в качестве прототипа.
Object.keys, Object.values, Object.entries
Object.keys, Object.values и Object.entries возвращают списки собственных перечисляемых свойств объекта. Они полезны для итераций и преобразования.
1// Keys, values and entries
2const item = { id: 1, name: "Widget", price: 9.99 };
3
4// ["id", "name", "price"]
5console.log(Object.keys(item));
6
7// [1, "Widget", 9.99]
8console.log(Object.values(item));
9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));- Они часто используются для перебора и преобразования объектов.
Object.assign
Object.assign используется для поверхностного копирования и слияния объектов. Обратите внимание, что прототипы и акцессорные свойства не копируются.
1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
- Для вложенных объектов копируются только ссылки, поэтому для глубокой копии требуется другая реализация.
Object.freeze, Object.seal, Object.preventExtensions
Object.freeze, Object.seal и Object.preventExtensions управляют изменяемостью объектов.
1// Freeze vs seal vs preventExtensions
2const obj = { a: 1 };
3Object.freeze(obj);
4obj.a = 2; // fails silently or throws in strict mode
5delete obj.a; // fails
6
7const obj2 = { b: 2 };
8Object.seal(obj2);
9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
freeze— самый строгий, запрещает любые изменения свойств объекта.sealпредотвращает добавление или удаление свойств, но позволяет изменять значения уже существующих свойств.preventExtensionsлишь запрещает добавление новых свойств; существующие свойства по-прежнему можно изменять или удалять.
Перечисляемость, порядок свойств и циклы for...in / for...of
for...in перебирает все перечисляемые свойства, включая те, что есть в цепочке прототипов, поэтому его часто используют вместе с hasOwnProperty. Использование Object.keys() вместе с for...of более безопасно и делает ваши намерения более ясными.
1// Safe enumeration
2const obj = Object.create({ inherited: true });
3obj.own = 1;
4
5for (const key in obj) {
6 if (obj.hasOwnProperty(key)) {
7 console.log("own prop:", key);
8 } else {
9 console.log("inherited prop:", key);
10 }
11}
12
13for (const key of Object.keys(obj)) {
14 console.log("key via Object.keys:", key);
15}- Правила перечисления свойств определены в спецификации ECMAScript; в некоторых случаях порядок обхода гарантирован, в других — нет. Как правило, ключи, интерпретируемые как числа, сортируются по возрастанию, в то время как остальные ключи идут в порядке добавления.
Клонирование и глубокое копирование
Существуют два типа копирования объектов: поверхностное копирование с помощью Object.assign или синтаксиса spread, и глубокое копирование с использованием рекурсивных методов. Важно правильно использовать их в зависимости от ситуации.
Поверхностное копирование (spread-синтаксис / Object.assign)
1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
- При поверхностном копировании вложенные объекты остаются общими, поэтому изменения исходного объекта могут затрагивать копию.
Простое глубокое копирование (с нюансами)
Трюк с JSON позволяет быстро сделать глубокую копию, но у него есть недостатки: теряются функции, объекты Date, появляются проблемы с циклическими ссылками и значениями undefined. Для настоящего глубокого клонирования нужно использовать специализированную библиотеку.
1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
- Методы, основанные на JSON, удобны для быстрой работы с простыми данными, но в обычных случаях их поведение может быть некорректным.
Миксины и композиция объектов
Вместо множественного наследования часто используется паттерн смешивания поведения через миксины.
1// Simple mixin function
2const canEat = {
3 eat() { return `${this.name} eats`; }
4};
5const canWalk = {
6 walk() { return `${this.name} walks`; }
7};
8
9function createPerson(name) {
10 const person = { name };
11 return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
- Миксины гибки, но нужно внимательно относиться к конфликтам имён свойств и вопросам тестируемости.
Распространённые ошибки и рекомендации
Вот некоторые частые ошибки и лучшие практики.
-
Изменяемость Объекты по умолчанию изменяемые. В приложениях с управлением состоянием рассмотрите использование неизменяемых структур данных с помощью
Object.freezeили библиотеки для работы с иммутабельными данными. -
Загрязнение прототипа Объединение внешних данных с объектом напрямую через
Object.assignили циклы может вызвать неожиданные побочные эффекты благодаря специальным свойствам вроде__proto__илиconstructor, что создаёт риски для безопасности. Фильтруйте пользовательские данные перед непосредственным объединением. -
Подводные камни
for...infor...inперебирает также свойства из прототипа, поэтому используйте проверку черезhasOwnProperty. ИспользованиеObject.keysобычно нагляднее. -
Ошибки при поверхностном копировании Задумайтесь, не требуется ли вам глубокое копирование, чтобы избежать влияния изменений вложенных объектов на исходный объект.
Практический пример: паттерн неизменяемого обновления объекта
Паттерны, возвращающие новый объект вместо прямого изменения состояния, широко используются в React и похожих библиотеках.
1// Immutable update example
2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
3
4// Toggle todo done immutably
5function toggleTodo(state, todoId) {
6 return {
7 ...state,
8 todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
9 };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
- Этот код является примером создания нового объекта состояния без прямого изменения исходного объекта
state. ФункцияtoggleTodoкопирует массивtodosи возвращает новый объект, в котором изменён только целевой элемент, поэтому исходный объектstateостаётся неизменным. - Неизменяемое обновление уменьшает побочные эффекты и облегчает управление состоянием.
Практический пример: безопасное объединение (остерегайтесь загрязнения прототипа)
При объединении внешнего JSON игнорируйте __proto__, чтобы избежать загрязнения прототипа.
1// Safe merge ignoring __proto__ keys
2function safeMerge(target, source) {
3 for (const key of Object.keys(source)) {
4 if (key === "__proto__" || key === "constructor") continue;
5 target[key] = source[key];
6 }
7 return target;
8}
9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
- Такая защита важна и в библиотеках, и во фреймворках.
Вопросы производительности
С точки зрения производительности учтите следующее:.
- Избегайте частых изменений прототипа (
Object.setPrototypeOf) и динамического добавления/удаления свойств — это мешает оптимизации движка. - При создании большого количества небольших объектов оптимизация становится более эффективной, если использовать объекты с одинаковой структурой (тем же набором свойств).
- Глубокое копирование требует много ресурсов. Минимизируйте его использование или переключитесь на обновления на основе сравнения изменений (diff).
Резюме
Object — центральный элемент JavaScript, обеспечивающий такие функции, как создание объектов, контроль свойств, наследование, копирование и управление изменяемостью. Важно понимать такие API, как Object.defineProperty, Object.assign и Object.freeze, и тщательно проектировать, чтобы избежать подводных камней, таких как загрязнение прототипа и неглубокое копирование.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.