JavaScript метод Object.freeze()
Определение и применение
Object.freeze() запрещает добавлять собственные свойства и удалять или перенастраивать существующие.
Собственные свойства данных становятся недоступны для записи. Свойства доступа сохраняют свои геттеры и сеттеры;
вложенные объекты не замораживаются.
Под ненастраиваемыми свойствами стоит понимать те, атрибут configurable которых имеет
значение false, в этом случае удаление такого свойства из содержащего его объекта, а большинство
последующих изменений дескриптора запрещено. Атрибуты в свою очередь описываются
дескрипторами свойств – это обычные объекты JavaScript, которые описывают атрибуты и
значение свойства. Подробную информацию о дескрипторах свойств и атрибутах вы можете получить в описании метода
defineProperty(), который позволяет определить новое или изменить существующее свойство объекта, описывая его дескрипторами.
Если в цепочке прототипов объекта на котором был вызван метод freeze() содержится расширяемый и
настраиваемый объект, то в этом случае остается возможность добавлять и удалять наследуемые им свойства.
В отличие от seal(), метод freeze() устанавливает writable: false для каждого
собственного свойства данных. У свойства доступа атрибута writable нет, поэтому вызов его сеттера
остаётся возможным.
Чтобы проверить является ли объект нерасширяемым с недоступными для настройки и изменения свойствами, вы можете
воспользоваться методом
isFrozen().
Сравнение подобных методов
| Метод | Объект становится нерасширяемым |
Атрибут configurable для каждого собственного свойства данных имеет значение
false
|
Атрибут writable для каждого собственного свойства данных имеет значение
false
|
|---|---|---|---|
preventExtensions()
|
Да | Нет | Нет |
seal()
|
Да | Да | Нет |
freeze() |
Да | Да | Да |
Заморозка поверхностная: вложенные объекты и внутреннее состояние, например содержимое Map, не
замораживаются. У свойств доступа нет атрибута writable; геттеры и сеттеры продолжают работать.
Неизменяемость значений проверяется только для собственных свойств данных.
Поддержка браузерами
Спецификация: ecma262
Поддерживается во всех основных браузерах с 2015 года.
| Возможность | Компьютеры | Мобильные | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
freeze | 6 | 12 | 4 | 12 | 5.1 | 18 | 4 | 12 | 5 | 1.0 | 4.4 | 5 |
поддерживаетсяс ограниченияминет поддержкинет данных≤31 — не позднее версии 31
JavaScript синтаксис
Object.freeze( obj );
Спецификация
Значения параметров
| Параметр | Описание |
|---|---|
obj |
Целевой объект, который "замораживается". Обязательное значение. |
Исключения
| Тип исключения | Описание |
|---|---|
TypeError |
Примитивы, включая null и undefined, возвращаются без изменений. Операции с объектом
могут выбросить TypeError, например при нарушении ограничений прокси. Для
Object.freeze() исключение также возникает у типизированного массива с элементами.
|
Примеры, попробуйте сами
В примере «Ограничения замороженного объекта» показан результат вызова метода.
Ограничения замороженного объекта
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Ограничения замороженного объекта</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменную, содержащую объект
let obj = {
a: 1,
b: 2,
}
try {
obj.c = 3
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // добавляем новое свойство в объект
// "замораживаем" объект
try {
Object.freeze(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // {a: 1, b: 2, c: 3}
try {
delete obj.c
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // пытаемся удалить свойство (false)
try {
obj.c = 100
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // пытаемся изменить значение свойства
try {
obj.d = 4
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // пытаемся добавить новое свойство
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // {a: 1, b: 2, c: 3}
// пытаемся изменить в объекте свойство данных
try {
Object.defineProperty(obj, 'c', {
// устанавливаем, что удаление свойства допускается, а также допускается изменение дескриптора этого свойства
configurable: true, // TypeError
})
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
В этом примере мы инициализировали переменную, содержащую объект, и с помощью метода freeze() мы
"заморозили" объект. После этого мы пытаемся удалить, изменить, добавить новое свойство, или изменить дескриптор, в
результате чего все наши попытки заканчиваются неудачей.
В следующем примере мы рассмотрим как "заморозить" массив:
В примере «Замороженный массив» показан результат вызова метода.
Замороженный массив
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Замороженный массив</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменную, содержащую массив
let arr = [1, 2, 3]
// "замораживаем" массив
try {
Object.freeze(arr)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
try {
arr.shift()
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // пытаемся удалить первый элемент массива
try {
arr[0] = 5
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // пытаемся изменить первый элемент массива
try {
arr.push(4)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // пытаемся добавить элемент к массиву
try {
console.log(arr)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // [1, 2, 3]
В этом примере мы инициализировали переменную, содержащую массив, и с помощью метода freeze() мы его
"заморозили". После этого мы пытаемся с помощью метода
shift()
удалить первый элемент массива, изменить первый элемент массива и с помощью метода
push()
добавить новый элемент в конец массива. Как вы можете убедиться все наши попытки закончились неудачей.
Замораживаемый объект является неизменяемым (иммутабельным), но это утверждение не всегда верно, в следующем примере мы рассмотрим почему замороженный объект не является константой:
В примере «Изменение вложенного объекта» показан результат вызова метода.
Изменение вложенного объекта
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Изменение вложенного объекта</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменную, содержащую объект
let myObj = {
nestedObj: {},
}
// "замораживаем" объект
try {
Object.freeze(myObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
// задаем вложенному объекту новое свойство и значение
try {
myObj.nestedObj.newProp = 'ooops'
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
try {
console.log(myObj.nestedObj.newProp)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // "ooops"
В этом примере мы инициализировали переменную, содержащую объект, и с помощью метода freeze() мы его
"заморозили". После этого мы пытаемся добавить новое свойство и его значение во вложенный объект, что у нас собственно
и получается. Это связано с тем, что замораживание является неглубоким (shallow).
Для глубокой заморозки дерева обычных объектов и массивов можно рекурсивно замораживать вложенные значения. Такой
алгоритм нужно отдельно адаптировать для циклов, символьных ключей и функций; он не запрещает изменение внутренних
данных Map, Set или Date. Пример ниже рассчитан на простое дерево объектов:
В примере «Рекурсивная заморозка простого объекта» показан результат вызова метода.
Рекурсивная заморозка простого объекта
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Рекурсивная заморозка простого объекта</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// создаем функцию для глубокой "заморозки" объекта
function deepFreeze(obj) {
// возвращаем массив, состоящий из всех имен собственных (неунаследованных) свойств
let propNames = Object.getOwnPropertyNames(obj)
// "замораживаем" все свойства переданного объекта
propNames.forEach(function (name) {
let prop = obj[name]
// "замораживаем" свойство если оно является объектом и не равно null
if (typeof prop == 'object' && prop !== null) {
deepFreeze(prop)
}
})
// "замораживаем" переданный объекта
return Object.freeze(obj)
}
// инициализируем переменную, содержащую объект
let myObj = {
nestedObj: {},
}
// "замораживаем" объект и его свойства с помощью созданной функции
try {
deepFreeze(myObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
// задаем вложенному объекту новое свойство и значение
try {
myObj.nestedObj.newProp = 'ooops'
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
}
try {
console.log(myObj.nestedObj.newProp)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // undefined
В этом примере мы рассмотрели как произвести глубокую "заморозку" объекта, но учтите тот момент, что объекты могут
быть сложнее и, например, содержать ссылки друг на друга, что может привести к бесконечным циклам при попытке их
"заморозить", или привести к заморозке таких объектов как window. В каждом конкретном случае необходимо
подходить отдельно к решению той, или иной задачи.
Что заморозка не запрещает
Измените значение в сеттере и запись Map. freeze() ограничивает собственные свойства, но не запрещает эти операции.
index.html
main.js
let saved = 1
const settings = Object.freeze({
get value() {
return saved
},
set value(next) {
saved = next
},
})
settings.value = 2
console.log('Сеттер работает:', settings.value) // 2
console.log('Объект заморожен:', Object.isFrozen(settings)) // true
const map = Object.freeze(new Map([['theme', 'light']]))
map.set('theme', 'dark')
console.log('Данные Map изменились:', map.get('theme')) // dark
console.log('Map заморожен:', Object.isFrozen(map)) // true