Details about javascript objects

Original author: Arfat Salman
  • Transfer
  • Tutorial
The author of the material, the translation of which we are publishing today, says that there are many things in JavaScript objects that one could not even suspect about the existence of using them in their daily work. Objects in JavaScript are very easy to create, it is convenient to work with them, they seem to be understandable and flexible entities, and many programmers simply do not think that objects are actually arranged is not so simple.


NB: Information from the publication in practice should be applied very carefully and under the supervision of more experienced colleagues.

Here we will talk about what is hidden in the depths of objects and discuss the subtleties of working with them.
Having mastered this material, you will know the answers to the following questions:

  • How to make a property of the object to be deleted?
  • What are the properties with access methods and what are their features?
  • How to make a property immutable or hidden?
  • Why are some properties not visible in cycles for-inor in the results of the method Object.keys(), and some - visible?
  • How to “protect” an object from modification?
  • How to understand a code snippet like this:

obj.id = 5;
console.log(obj.id)
// => '101' (5 в двоичной системе счисления)

Types of properties of objects


▍Properties storing data


You have probably created countless objects resembling this one:

const obj = {
  name: 'Arfat',
  id: 5
}
obj.name 
// => 'Arfat'

Properties nameand idobjects objare called properties that store data, or “data properties” (Data Properties). These are familiar properties that are constantly found in JavaScript code. What other kinds of properties can objects have?

▍Properties with access methods


Such properties are also known as getters and setters, they are found in other programming languages ​​like C # or Python. A property with access methods (Accessor Property) is a combination of two functions - getand set.

When declaring such properties instead of using the traditional construction of the form ключ: значение, the following syntax is used:

const accessorObj = {
  get name() {
    return 'Arfat';
  }
};
accessorObj.name;
// => 'Arfat'
const dataObj = {
  name: 'Arfat',
};
dataObj.name;
// => 'Arfat'

Take a look at the object accesorObjand compare it with the object dataObj. As you can see, they are now showing the same behavior. In describing the first object, we used the keyword get, followed by the function declaration. In order to access such a property, although it is represented by a function, it is not necessary to put parentheses after the name of the property to call this function. That is the design like is accessorObj.name();incorrect.

When an attempt is made to access a property accessorObj.name, that is, when an attempt is made to read it, the corresponding function is executed and the value returned to it becomes the value of the property name.

Functions getare called getters, they are responsible for getting values. If we continue our example and try to change the value of the propertynamean object accessorObj, say, by executing a command accessorObj.name = 'New Person’;, it turns out that nothing will happen. The point here is that the namesetter function is not associated with the key . Such functions allow you to customize the order in which new values ​​are assigned to properties of objects, access to which is organized using getters.

Here’s what an object declaration with a getter and setter looks like:

const accessorObj = {
  _name: 'Arfat',
  get name() {
    return this._name;
  },
  set name(value) {
    this._name = value;
  }
};

The setter function gets what it is trying to assign to the property of the object, as a parameter. Now you can save something in the object property. In this case, we create a "private" property of the object _name. The first symbol of the name of such a property is an underscore, which is nothing more than a hint for the programmer, indicating that this property is intended for the internal needs of the object. Further, we work with it when referring to the property of an object name, access to which is regulated by a getter and a setter.

At the same time, in the getter function, before returning the value of the property _name, we can modify it.

Here is what it might look like:

const obj = {
  get name() {
    return this._name.toUpperCase();
  },
  set name(value) {
    this._name = value;
  },
  get id() {
    return this._id.toString(2); // Преобразуем десятичное число в его двоичное представление
  },
  set id(value) {
    this._id = value;
  }
}
obj.name = 'Arfat';
obj.name;
// => 'ARFAT'
obj.id = 5;
obj.id;
// => '101

This program, by the way, contains the answer to one of the questions given at the beginning of the article, which concerns the analysis of a code that is incomprehensible at first glance.

Why would anyone need properties with access methods if you can safely work with ordinary properties? For example, they may be needed in order to log information about property read operations, or to keep a history of changes in property values. Properties with access methods give us all the possibilities of data processing with the help of functions and the simplicity characteristic of working with ordinary properties. More information about the use of such properties can be found here .

How does JavaScript distinguish regular properties that store data from properties with accessors? Let's figure it out.

Object Property Descriptors


At first glance it may seem that there is a direct correspondence between the keys and the values ​​stored in the objects. However, this is not quite true.

Свойств Property Attributes


Associated with each object key is a set of attributes that define the characteristics of the value associated with a given key. These attributes can also be viewed as metadata describing the pair ключ: значение.

Attributes are used to set and describe the state of object properties. The set of property attributes is called a descriptor. There are six property attributes:

  • [[Value]]
  • [[Get]]
  • [[Set]]
  • [[Writable]]
  • [[Enumerable]]
  • [[Configurable]]

Why are property attribute names in this list enclosed [[]]? Double parentheses indicate that these are entities used by the internal mechanisms of the language. The JS programmer cannot access these properties directly. In order to influence them, appropriate methods are used.

Consider the following image taken from here , where you can see the object and the attributes of its properties.


Object and attributes of its properties

. Our object has 2 keys -xandy. In addition, each of them is associated with a set of attributes.

How can JavaScript get object information like those shown in the previous figure? To do this, you can use the functionObject.getOwnPropertyDescriptor(). It takes an object and the name of its property, and then returns an object containing the attributes of this property. Here is an example:

const object = {
  x: 5,
  y: 6
};
Object.getOwnPropertyDescriptor(object, 'x');
/* 
{ 
  value: 5, 
  writable: true, 
  enumerable: true, 
  configurable: true 
}
*/

It should be noted that the composition of the attributes of a particular property depends on its type. All six attributes of the same property are not found.

  • If we are talking about the properties of the data, then they will only attributes [[Value]], [[Writable]], [[Enumerable]]and [[Configurable]].
  • Properties with access methods, instead of attributes [[Value]]and [[Writable]], have attributes [[Get]]and [[Set]].

▍ [[Value]]


This attribute stores what is issued when trying to get the value of an object property. That is, if in the previous example we use the view construct object.x, we get what is stored in the attribute [[Value]]. The same thing will happen when trying to read the properties of an object using square brackets.

▍ [[Get]]


This attribute stores a link to the function, which is a getter property. This function is called without arguments when trying to read the value of a property.

▍ [[Set]]


Here is a reference to the function declared when creating the setter property. It is called with an argument representing the value that the property was attempted to assign, that is, it is called with each assignment operation to the property of a new value.

const obj = {
  set x(val) {
    console.log(val) 
    // => 23
  }
}
obj.x = 23;

In this example, the right side of the expression is passed as the argument valto the setter function. Here is the code that demonstrates the use of setters and getters.

▍ [[Writable]]


This attribute stores a boolean value. It indicates whether the value of the property can be rewritten or not. If a value is stored here false, then attempts to change the value of a property will not succeed.

▍ [[Enumerable]]


The logical value is also stored here. This attribute governs the output of a property in cycles for-in. If it is set to a value true, then it will be possible to work with the property using such cycles.

▍ [[Configurable]]


This attribute is also represented by a logical value. This is what happens if a value is stored in it false:

  • The property cannot be deleted.
  • It will not be possible to convert properties that store data into properties with access methods, and vice versa. Attempts to perform such transformations will not lead to anything.
  • It will be forbidden to change property attribute values. That is immutable will be the current values of attributes [[Enumerable]], [[Configurable]], [[Get]]and [[Set]].

The effect of setting this attribute to a value falsealso depends on the type of property. This attribute, in addition to the above effects on the properties, acts on them and so:

  • If we have a property that stores data, then the attribute [[Writable]]can only be changed from trueto false.
  • As long as the attribute is [[Writable]]not set false, you can change the attribute [[Value]]. But after the falseattributes are set in [[Writable]]and [[Configurable]], the property will be unrecordable, undeletable and immutable.

Work with Handles


Now that we are familiar with the attributes, let us ask ourselves how we can influence them. In JavaScript, there are special functions designed to work with property descriptors. Let's talk about them.

Object Method Object.getOwnPropertyDescriptor ()


We have already met with this method. It, taking the object and the name of its property, returns either undefined, or an object with a property descriptor.

Object Method Object.defineProperty ()


This is a static method Objectthat allows you to add properties to objects or change existing properties. It takes three arguments — an object, a property name, and an object with a handle. This method returns a modified object. Consider an example:

const obj = {};
// #1
Object.defineProperty(obj, 'id', {
  value: 42
});
// #2
console.log(obj);
// => { }
// #3
console.log(obj.id);
// => 42
// #4
Object.defineProperty(obj, 'name', {
  value: 'Arfat',
  writable: false,
  enumerable: true,
  configurable: true
});
// #5
console.log(obj.name);
// => 'Arfat'
// #6
obj.name = 'Arfat Salman'
// #7
console.log(obj.name);
// => 'Arfat' 
// (а не 'Arfat Salman')
Object.defineProperty(obj, 'lastName', {
  value: 'Salman',
  enumerable: false,
});
console.log(Object.keys(obj));
// => [ 'name' ]
// #8
delete obj.id;
// #9
console.log(obj.id);
// => 42
//#10
Object.defineProperties(obj, {
  property1: {
    value: 42,
    writable: true
  },
  property2: {}
});
console.log(obj.property1)
// => 42

It can be run in the Node.js environment. The code is quite large, but, in fact, it is quite simple. We will analyze it, focusing on the comments of the form // #n.

In the snippet, #1we use a function defineProperty, passing it an object obj, the name of a property, idand a descriptor object containing only a property valueindicating that the [[Value]]value will be written to the attribute 42. Remember that if you do not pass in this object values ​​for attributes like [[Enumerable]]or [[Configurable]], then they will, by default, be set to a value false. In this case, attributes [[Writable]], [[Enumerable]]and [[Configurable]]the properties idare set in false.

In a place marked as#2, we are trying to display a string representation of the object in the console. Since its property idis non-enumerable, it will not be inferred. At the same time, the property exists, which proves its successful output by the command #3.

Creating an object (fragment #4), we set the complete list of attributes. In particular, set [[Writable]]to value false.

Teams #5and #7we display the value of the property name. But between them (fragment #6) we tried to change this value. This operation did not change the value of the property because its attribute is [[Writable]]set to false. As a result, both commands output the same to the console.

The command #8is an attempt to delete a property.id. Recall that its attribute is [[Configurable]]set to a value false, which means that it cannot be removed. This is proved by the team #9.

The fragment #10shows the use of the Object.defineProperties () function . It works in the same way as a function defineProperty(), but allows, in one call, to affect several properties of an object, while it defineProperty()works with only one property of an object.

Protection of objects


Periodically, the developer needs to protect objects from outside interference. For example, given the flexibility of JavaScript, it is very easy to mistakenly change the properties of an object, which should not change. There are three main ways to protect objects.

Object Method Object.preventExtensions ()


The method Object.preventExtensions()prevents the object from expanding, that is, adding new properties to it. It takes an object and makes it non-expandable. Note that you can delete properties from such an object. Consider an example:

const obj = {
  id: 42
};
Object.preventExtensions(obj);
obj.name = 'Arfat';
console.log(obj);
// => { id: 42 }

To find out if an object is non-expandable, you can use the method Object.isExtensible(). If it returns true, then new properties can be added to the object.

Object Method Object.seal ()


The method seal()“seals” the objects. This is what it is about:

  • Its use prevents the addition of new properties to an object (in this it is similar Object.preventExtensions()).
  • It makes all existing properties of the object non-configurable.
  • Values ​​of existing properties, if their attribute is [[Writable]]not set to false, can be changed.

As a result, it turns out that this method prevents the addition of new properties to the object and the deletion of existing properties in it.

Consider an example:

const obj = {
  id: 42
};
Object.seal(obj);
delete obj.id 
// (не работает)
obj.name = 'Arfat';
// (не работает)
console.log(obj);
// => { id: 42 }
Object.isExtensible(obj);
// => false
Object.isSealed(obj);
//=> true

To check whether the object is “sealed” or not, you can use the method Object.isSealed().

Object Method Object.freeze ()


The method freeze()allows you to "freeze" objects, equipping them with the highest possible level of protection in JavaScript. Here is how it works:

  • “Seals” an object with Object.seal().
  • Completely prohibits the modification of any existing object properties.
  • Forbids modification of property descriptors.

Here is an example:

const obj = {
  id: 42
};
Object.freeze(obj);
delete obj.id 
// (не работает)
obj.name = 'Arfat';
// (не работает)
console.log(obj);
// => { id: 42 }
Object.isExtensible(obj);
// => false
Object.isSealed(obj);
//=> true
Object.isFrozen(obj);
// => true

You can check whether the object is “frozen” by using the method Object.isFrozen().

▍ Overview of methods used to protect objects


It is important to note that the methods described above, used to protect objects, affect only their properties, which are not objects.

Here is a summary table on the considered methods of protection of objects, which is taken from here .
Property creation
Reading property
Property rewrite
Property removal
Object.freeze()
- +
- -
Object.seal()
- +
+
-
Object.preventExtensions()
- +
+
+

Results


Considering how often objects are used in JavaScript code, it is important for each developer to know how they work. We hope that what you have learned by reading this material will be useful to you. In addition, now you know the answers to the questions listed at the beginning of the article.

Dear readers! How do you protect JavaScript objects?


Also popular now: