“This is the 11th day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021.”

Data invariance has always been important in programming languages, and it is also true in JavaScript. Here, there are two JavaScript methods that can partially guarantee immutability: Object.freeze and object.seal. This article summarizes what can be done with both methods. What’s the difference? What are the shortcomings?

Object defineProperty

Before we look at Freeze and SEAL, let’s look at what the defineProperty method in Object is. When an object is created by the engine during initial processing, JavaScript assigns basic properties to the newly created object to handle requests from outside, such as accessing or deleting properties.

The following properties can be modified or set:

  • value: The value of the property
  • enumerable: if fortrue, the attribute can passfor-inLoop orObject.keys()Search, default isfalse
  • writable: if forfalse, this property cannot be modified and raises an error in strict mode, which defaults tofalse
  • Configurable: Iffalse, which makes the attributes of the object non-enumerable, writable, deletable, and configurable. The default isfalse
  • get: The function that is called ahead of time when trying to access the property, default toundefined.
  • set: The function that is called ahead of time when trying to set a value for a propertyundefined.

Let’s look at some simple code:

Can be enumerated

const obj = {}; Object.defineProperty(obj, "a", { value: 100, enumerable: false, }); for (const key in obj) { console.log(key); } // Undefined object.keys (obj); / / []Copy the code

Can write

const obj = {}; Object.defineProperty(obj, "a", { value: 100, writable: false, }); obj.a = 200; obj.a === 100; // true (() => {"use strict"; obj.a = 100; // Type error in strict mode})();Copy the code

configurable

const obj = {}; Object.defineProperty(obj, "a", { value: 100, configurable: false, }); // 1. non-enumerable for (const key in obj) { console.dir(key); } // undefined Object.keys(obj); // [ // 2. non-writable (() => { "use strict"; obj.a = 200; // TypeError in the strict mode })();  // 3. non-deletable delete obj.a; obj.a === 100; // trueCopy the code

However, when writable or Enumerable is true, configure:false is ignored.

Object.Seal

In JavaScript, object.seal does the same thing as sealing. Object.seal makes all attributes of an Object passed to it non-configurable. It can be used to prevent new attributes from being added to an Object and deleted, but allows existing attributes to be changed and updated. Consider the following example:

const obj = { author: "DevPoint" }; console.log(Object.getOwnPropertyDescriptors(obj)); /* { author: { value: 'DevPoint', writable: true, enumerable: true, configurable: true } } */ Object.seal(obj); console.log(Object.getOwnPropertyDescriptors(obj)); /* {file: {value: 'DevPoint', writable: true, enumerable: true, 64x: false}} */ obj. console.log(obj.author); // delete obj.author; console.log(obj.author); // obj. City = "Shenzhen"; console.log(obj.city); // undefinedCopy the code

The above code defines an object obj with an attribute author, where the value is DevPoint. The initial description attribute is as follows:

{
    author: {
      value: 'DevPoint',
      writable: true,
      enumerable: true,
      configurable: true
    }
}
Copy the code

Then seal the Object with Object.seal to see again which descriptors have changed and which have not. Only configurable changes are false.

{
    author: {
      value: 'DevPoint',
      writable: true,
      enumerable: true,
      configurable: false
    }
}
Copy the code
Obj. author = "Tianxing Wuji ";Copy the code

Even though the configurability after Object.seal is now false, you can code its property value to be unwritable. As explained earlier, setting configurability to false makes the property unwritable, but writable explicitly true does not work. When creating an object and setting a new property, it defaults to Writable :true.

delete obj.author;
Copy the code

Object.seal prevents each property from being deleted by making it unconfigurable. From the above code, after executing object.seal on the Object, delete obj.author; Will become invalid.

obj.city = "Shenzhen";
Copy the code

When either Object.seal or Object.freeze is called, the executed Object becomes an unextensible Object, meaning that no properties can be removed from it or added to it.

Object.freeze

This limits the objects passed than Object.seal. Modify the above code as follows:

const obj = { author: "DevPoint" }; console.log(Object.getOwnPropertyDescriptors(obj)); /* { author: { value: 'DevPoint', writable: true, enumerable: true, configurable: true } } */ Object.freeze(obj); console.log(Object.getOwnPropertyDescriptors(obj)); /* {file: {value: 'DevPoint', writable: false, Enumerable: true, 64x: false}} */ obj. console.log(obj.author); // DevPoint delete obj.author; console.log(obj.author); // DevPoint obj.city = "Shenzhen"; console.log(obj.city); // undefinedCopy the code

This differs from Object.seal in that the writable property also changes to false after executing Object.freeze. Therefore, subsequent code updates to its attributes are invalid. Also like Object.seal, object.freeze makes the Object unconfigurable, which makes each attribute of the Object unconfigurable.

In common

  1. The object becomes unextensible after execution, which means that the object cannot add new properties.
  2. Every element in the executed object becomes unconfigurable, meaning that attributes cannot be deleted.
  3. Both methods can raise errors if an action is invoked in “Use strict” mode, such as executing in strict modeObj. author = ""Errors will occur.

different

Object properties can be modified by executing Object.seal, but not by executing Object.freeze.

insufficient

Object.freeze and Object.seal are not “practical”; they are only valid for the first layer of objects.

const obj = { author: "DevPoint", detail: { view: 100 } };
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
  view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
  view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/

obj.detail.view = 500;
console.log(obj.detail.view); // 500
delete obj.detail.view;
console.log(obj.detail); // {}
obj.detail.hits = 666;
console.log(obj.detail.hits); // 666

Object.freeze(obj);
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
  view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
Copy the code

If you want to avoid being valid for deeper object properties, like deep copy, you need to write some code to do this (deepFreeze) :

const obj = { author: "DevPoint", detail: { view: 100 } };
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
  view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
const deepFreeze = (object) => {
    const propNames = Object.getOwnPropertyNames(object);

    for (const name of propNames) {
        const value = object[name];
        if (value && typeof value === "object") {
            deepFreeze(value);
        }
    }
    return Object.freeze(object);
};
const freezeObj = deepFreeze(obj);
console.log(Object.getOwnPropertyDescriptors(freezeObj.detail));
/*
{
  view: { value: 100, writable: false, enumerable: true, configurable: false }
}
*/

obj.detail.view = 500;
console.log(obj.detail.view); // 100
delete obj.detail.view;
console.log(obj.detail); // {view:100}
obj.detail.hits = 666;
console.log(obj.detail.hits); // undefined
Copy the code

If you want to implement object. seal on nested objects, you also need to write code to do this (deepSeal) :

const obj = { author: "DevPoint", detail: { view: 100 } };
console.log(Object.getOwnPropertyDescriptors(obj.detail));
/*
{
  view: { value: 100, writable: true, enumerable: true, configurable: true }
}
*/
const deepSeal = (object) => {
    const propNames = Object.getOwnPropertyNames(object);

    for (const name of propNames) {
        const value = object[name];
        if (value && typeof value === "object") {
            deepSeal(value);
        }
    }
    return Object.seal(object);
};
const freezeObj = deepSeal(obj);
console.log(Object.getOwnPropertyDescriptors(freezeObj.detail));
/*
{
  view: { value: 100, writable: true, enumerable: true, configurable: false }
}
*/

obj.detail.view = 500;
console.log(obj.detail.view); // 500
delete obj.detail.view;
console.log(obj.detail); // {view:500}
obj.detail.hits = 666;
console.log(obj.detail.hits); // undefined
Copy the code

conclusion

Object. Freeze and Object. Seal are very useful methods in modern front-end development. If you want to work with deep layers, you can use the methods deepFreeze and deepSeal above.