JavaScript метод Object.freeze()

Определение и применение

Object.freeze() запрещает добавлять собственные свойства и удалять или перенастраивать существующие. Собственные свойства данных становятся недоступны для записи. Свойства доступа сохраняют свои геттеры и сеттеры; вложенные объекты не замораживаются.

Под ненастраиваемыми свойствами стоит понимать те, атрибут configurable которых имеет значение false, в этом случае удаление такого свойства из содержащего его объекта, а большинство последующих изменений дескриптора запрещено. Атрибуты в свою очередь описываются дескрипторами свойств – это обычные объекты JavaScript, которые описывают атрибуты и значение свойства. Подробную информацию о дескрипторах свойств и атрибутах вы можете получить в описании метода defineProperty(), который позволяет определить новое или изменить существующее свойство объекта, описывая его дескрипторами.

Если в цепочке прототипов объекта на котором был вызван метод freeze() содержится расширяемый и настраиваемый объект, то в этом случае остается возможность добавлять и удалять наследуемые им свойства.

В отличие от seal(), метод freeze() устанавливает writable: false для каждого собственного свойства данных. У свойства доступа атрибута writable нет, поэтому вызов его сеттера остаётся возможным.

Чтобы проверить является ли объект нерасширяемым с недоступными для настройки и изменения свойствами, вы можете воспользоваться методом isFrozen().

Сравнение подобных методов

Object: справочные сведения
Метод Объект становится нерасширяемым Атрибут configurable для каждого собственного свойства данных имеет значение false Атрибут writable для каждого собственного свойства данных имеет значение false
preventExtensions() Да Нет Нет
seal() Да Да Нет
freeze() Да Да Да

Заморозка поверхностная: вложенные объекты и внутреннее состояние, например содержимое Map, не замораживаются. У свойств доступа нет атрибута writable; геттеры и сеттеры продолжают работать. Неизменяемость значений проверяется только для собственных свойств данных.

Поддержка браузерами

Спецификация: ecma262

Поддерживается во всех основных браузерах с 2015 года.

Показать браузеры
Совместимостьjavascript.builtins.Object.freeze
ВозможностьКомпьютерыМобильные
ChromeEdgeFirefoxOperaSafariChrome AndroidFirefox for AndroidOpera AndroidSafari on iOSSamsung BrowserWebView AndroidWebView on iOS
freeze✓6✓12✓4✓12✓5.1✓18✓4✓12✓5✓1.0✓4.4✓5

поддерживаетсяс ограниченияминет поддержкинет данных≤31 — не позднее версии 31

JavaScript синтаксис

JavaScript
Object.freeze( obj );

Спецификация

ECMAScript: Object.freeze()

Значения параметров

Object: справочные сведения
Параметр Описание
obj Целевой объект, который "замораживается". Обязательное значение.

Исключения

Object: справочные сведения
Тип исключения Описание
TypeError Примитивы, включая null и undefined, возвращаются без изменений. Операции с объектом могут выбросить TypeError, например при нарушении ограничений прокси. Для Object.freeze() исключение также возникает у типизированного массива с элементами.

Примеры, попробуйте сами

В примере «Ограничения замороженного объекта» показан результат вызова метода.

Ограничения замороженного объекта

Измените свойства исходного объекта и сравните результат до и после вызова метода.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <title>Ограничения замороженного объекта</title>
  </head>
  <body>
    <p>Результат выполнения выводится в консоль.</p>
  </body>
</html>
main.js
JavaScript
// инициализируем переменную, содержащую объект
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
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <title>Замороженный массив</title>
  </head>
  <body>
    <p>Результат выполнения выводится в консоль.</p>
  </body>
</html>
main.js
JavaScript
// инициализируем переменную, содержащую массив
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
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <title>Изменение вложенного объекта</title>
  </head>
  <body>
    <p>Результат выполнения выводится в консоль.</p>
  </body>
</html>
main.js
JavaScript
// инициализируем переменную, содержащую объект
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
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <title>Рекурсивная заморозка простого объекта</title>
  </head>
  <body>
    <p>Результат выполнения выводится в консоль.</p>
  </body>
</html>
main.js
JavaScript
// создаем функцию для глубокой "заморозки" объекта
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
HTML
main.js
JavaScript
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