Exploring JavaScript Symbols. Symbol - New JavaScript Data Type

  • Tutorial
This is the first part about characters and their use in JavaScript.

The new ECMAScript specification (ES6) introduces an additional data type - the symbol. It will replenish the list of already available primitive types (string, number, boolean, null, undefined). An interesting feature of a symbol compared to other primitive types is that it is the only type that does not have a literal.

Why was an additional data type needed?

In JavaScript, it is not possible to declare an object property as private. To hide the data, you can use closures, but then all properties must be declared in the constructor (since there is no way to declare them in the prototype), in addition, they will be created for each instance, which will increase the size of the used memory. ECMAScript 5 provided the ability to specifyenumerable: falsefor the property, which allows you to hide the property from the enumeration in for-inand it will not be visible in Object.keys, but for this you need to declare it through the construction Object.defineProperty.

    var user = {};
    Object.defineProperty( user, 'role', {
        enumerable: false,
        value: 'admin'
    });

This design of the declaration still does not make it impossible to get the value of the property if you directly access it:

    var userRole = user.role; // 'admin'

In other languages, for example, you can add a method modifier to determine its visibility (protected, private, public). But in the new JavaScript specification, they took a different approach and decided not to introduce modifiers, but to determine the behavior depending on the type of property identifier. The property name used to be a string, but now it can be either a string or a character. This approach allows you to not change the very concept of declaring objects:

    var role = Symbol();
    var user = {
        id: 1001,
        name: 'Administrator',
        [role]: 'admin'
    };

In this example, an object is declared userin which two properties are declared through string identifiers ( id, name) and one property through a symbol ( role).
The property is roledeclared in square brackets so that it is not interpreted as a string, but is obtained by evaluating the expression. This object can also be declared as follows in order to better understand this construction:

    var role = Symbol();
    var user = {
        ['id']: 1001,
        ['name']: 'Administrator',
        [role]: 'admin'
    };

In this case, all three expressions will be calculated and their results will be property names. The ability to use dynamic (resulting from evaluating an expression) property names for object literals are added in ES6.

The key feature of a symbol, which differs from a string, is that you can only access a property that is declared through a symbol by reference to this symbol. For example, if useryou need to get a username from an object, you need to write this code:

    var userName = user.name;    // 'Administrator'
    // OR
    var userName = user['name']; // 'Administrator'

In this way we cannot get the user role:

    var userRole = user.role;    // undefined
    // OR
    var userRole = user['role']; // undefined

In order to get a role, you need to access the property by reference to the symbol:

    var role = Symbol();
    var user = {
        id: 1001,
        name: 'Administrator',
        [role]: 'admin'
    };
    var userRole = user[role]; // 'admin'

Announced property of the symbol will not be seen in for-in, Object.keys, Object.getOwnPropertyNames, it will also not be added when used JSON.stringify.

Consider the features of the characters.

As already shown in the example above, to create a character you need to call the function Symbol:

    var score = Symbol();

The function Symbolalso takes an optional parameter - a string that serves to describe the character:

    var score = Symbol('user score');
    console.log( score ); // Symbol(user score)

The description of the symbol is only to help with debugging, it does not change the behavior of the symbol and it is impossible to access the symbol through the description, there is also no method to get or change the description of the symbol.

The ES6 specification no longer supports the explicit creation of primitive objects, so the following construct will throw an error:

    var score = new Symbol('score'); // TypeError

For backward compatibility for String, Numberand Boolean- the error will not be thrown (but it is better not to use obsolete behavior). If you need to work not with the primitive, but with its object, you can use the function by Objectpassing it the primitive as a parameter:

    var symbol = Symbol('symbol');
    var string = 'string';
    var number = 5;
    var symbolObj = Object( symbol );
    var stringObj = Object( string );
    var numberObj = Object( number );
    console.log( symbol );     // Symbol(symbol)
    console.log( string );     // 'string'
    console.log( number );     // 5
    console.log( symbolObj );  // Symbol {}
    console.log( stringObj );  // String { 0: 's', 1: 't', 2: 'r', 3: 'i', 4: 'n', 5: 'g', length: 6, [[PrimitiveValue]]: 'string' }
    console.log( numberObj );  // Number { [[PrimitiveValue]]: 5 }

An important feature of a symbol is also that its meaning is unique:

    var firstScore = Symbol('score');
    var secondScore = Symbol('score');
    firstScore === secondScore; // false

This behavior opens up more opportunities for us when working with objects, for example, several modules can expand an object with new properties without worrying about possible name conflicts.

You can use to determine the character typeof, if the value is a character, the string will be returned symbol:

    function isSymbol( value ) {
        return typeof value === 'symbol';
    }
    var firstScore = Symbol('score');
    var secondScore = 'score';
    isSymbol( firstScore );   // true
    isSymbol( secondScore );  // false

There are many nuances in the current JavaScript type casting system, and characters add another feature in that, unlike other primitive values, a character cannot be converted to a string or number. If you try to convert to a number or string, an error will be thrown TypeError. This behavior was chosen so as not to accidentally create a string value, which will eventually be used as the name of the property:

    var userObject = {};
    var role = Symbol() + 'type';
    var id = 10001;
    userObject.id = id;
    userObject[ role ] = 'admin';

In this example, it is not unambiguous what should be stored as a result in a variable role, if a string, then the property userObject[ role ] = 'admin'will be declared through the string and it will be directly accessible (but since the symbol was used, there was most likely a desire to hide the value of the property). On the other hand, if the result of the expression is a character, and since it is impossible to get the values ​​of a character, it is impossible to determine the presence of a string in typeit, and this is no longer an obvious behavior and you need to inform the developer in situations when he deliberately tries to create a string value from a character, because such a design does not make sense.

In order not to have such ambiguity, and the behavior was chosen that when trying to convert a character there will be an error.

This is basic information about characters as a data type. In the next part, we will continue to consider the symbol and study the methods of the symbol (how to create a global symbol, how it works Object.getOwnPropertySymbols), and also look at possible examples of using the symbol.

Also popular now: