From me Name = 'forceddd' start

Posted by Valkrin on Wed, 15 Dec 2021 22:08:41 +0100

Like me The assignment statement with name = 'forceddd' can be seen everywhere in JavaScript. But do we really understand what this code does? Did you create a new property? Did you modify the value of the original attribute? Was this operation successful or failed? This is just a simple assignment statement, but if we think about it carefully, we will find that this detail is not so simple.

Everything can be classified and discussed. First, there are two types of cases: the name attribute already exists on the object me and the name attribute does not exist.

Of course, we all know that there is a prototype chain mechanism in JavaScript, so when the name attribute does not exist in the object me, it can also be divided into two cases: the name attribute and the name attribute do not exist on the prototype chain of me. That is, it can be divided into three categories.

  1. The name attribute already exists in me

    const me = {
        name: 'me',
    };
    me.name = 'forceddd';
    
    console.log(me.name); //forceddd

    In this case, our goal is to reset the value of the name attribute, and the result seems obvious, me Name was changed to 'forceddd'.

    But don't forget that the properties of an object also have its own properties, This includes whether it is writable (the object usually used to describe various characteristics of an object's attribute is called an attribute descriptor or an attribute description object), so when the Writeability of the name attribute What is the result when (writable) is false? What is the result when the name attribute defines a getter or setter function and becomes an access descriptor?

    • When the writable of the name attribute is false

      const me = {
          name: 'me',
      };
      Object.defineProperty(me, 'name', {
          writable: false,
          value: me.name,
      });
      
      me.name = 'forceddd';
      // The attribute value of name is still 'me'
      console.log(me.name); //me

      Because the name attribute is read-only, the assignment operation me Name = 'forceddd' failed, it's a beaver. However, it should be noted that this operation failure is silent. If we do not verify the name value after the operation, it is difficult to find it. Using strict mode can turn this silent failure into an explicit TypeError.

      'use strict';//Use strict mode
      const me = {
          name: 'me',
      };
      
      Object.defineProperty(me, 'name', {
          writable: false,
          value: me.name,
      });
      me.name = 'forceddd';
      //TypeError: Cannot assign to read only property 'name' of object '#<Object>'
    • When the name attribute is an access descriptor (with a getter or setter defined)

      const me = {
          _name: 'me',
          get name() {
              return this._name;
          },
          set name(v) {
              console.log('call setter,set up name');
              this._name = v;
          },
      };
      
      me.name = 'forceddd';//Call setter to set name
      
      console.log(me.name);//forceddd

      At this time, there is a setter function for the name attribute. The setter function will be called during assignment. Generally speaking, when we define access descriptors, getters and setters appear in pairs, but there is no problem defining only one of them. However, some unexpected situations may occur. For example, when name defines only one getter function, there is no setter function, and the assignment operation is meaningless.

      In non strict mode, because there is no setter function, the assignment silence fails.

      const me = {
          _name: 'me',
          get name() {
              return this._name;
          },
      };
      
      me.name = 'forceddd';
      
      console.log(me.name); //me

      In strict mode, a TypeError will appear when the access descriptor without setter function is assigned, which is also very beaver.

      'use strict';
      const me = {
          _name: 'me',
          get name() {
              return this._name;
          },
      };
      
      me.name = 'forceddd';
      //TypeError: Cannot set property name of #<Object> which has only a getter

    To sum up, when the name attribute exists in me, there are three situations when performing assignment:

    1. When the property is an access descriptor, if there is a setter, call the setter; If there is no setter, it will fail silently in non strict mode, and a TypeError will be generated in strict mode.
    2. When the Writeability in the property descriptor of the property is false, it will fail silently in the non strict mode, and a TypeError will be generated in the strict mode
    3. When the attribute does not belong to the above two cases, set the value to the value of the attribute and the assignment is successful.
  2. The name attribute does not exist on me or its prototype chain

    At this time, a new attribute name with the value 'forceddd' will be created on the object me. This is also a way we often use.

    const human = {};
    const me = {};
    Object.setPrototypeOf(me, human);
    me.name = 'forceddd';
    
    console.log({ me, human }); //{ me: { name: 'forceddd' }, human: {} }

    Of course, the attribute created in this way can be modified by assignment, which means that the writable in the attribute descriptor of this attribute is true. In fact, the configurability and enumerability of properties are also true at this time, which is different from the default value of adding properties through defineProperty. The default values of writable, configurable and enumerable in the property descriptor added by defineProperty method are false. This is also a noteworthy point.

    console.log(Object.getOwnPropertyDescriptor(me, 'name'));
    // {
    //   value: 'forceddd',
    //   writable: true,
    //   enumerable: true,
    //   configurable: true
    // }

    Add a property through the defineProperty method

    Object.defineProperty(me, 'prop', { value: 'forceddd' });
    console.log(Object.getOwnPropertyDescriptor(me, 'prop'));
    // {
    //   value: 'forceddd',
    //   writable: false,
    //   enumerable: false,
    //   configurable: false
    // }
  3. The name attribute does not exist in me, but exists on its prototype chain

    At this time, the name attribute exists on the prototype object human of me. In many cases, we use me Assignment statements such as name = 'forceddd' are only used to modify the object me and do not want to involve other objects. However, in JS, when accessing an object property, if the property does not exist on the object, it will be searched on the prototype chain, so it is difficult to bypass the prototype chain of the object.

    const human = {};
    const me = {};
    Object.setPrototypeOf(me, human);

    As mentioned earlier, there are also three cases for the name attribute on the human object:

    • writable in the attribute descriptor of the name attribute is false

      //Set the name property of the human object
      Object.defineProperty(human, 'name', {
          writable: false,
          value: 'human',
      });
      me.name = 'forceddd';
      console.log(me);//{}

      At this point, when we perform the assignment operation and check the me object, we will find that it has no name attribute. WTF? It's hard to understand. Because the name attribute in human is read-only, objects that prototype human objects cannot add a name attribute through the = assignment operation.

      In fact, this is to imitate the inheritance behavior of a class. If the name attribute in the parent class is read-only, the name attribute in the subclass that inherits it should also be read-only. This strange phenomenon is caused by the inheritance behavior of the class simulated through the prototype chain in JS.

      Similarly, if it is in strict mode, it will not fail silently, but will generate a TypeError.

    • The name attribute is an access descriptor

      Object.defineProperty(human, 'name', {
          set() {
              console.log('Called human of setter');
          }
      });
      me.name = 'forceddd';//'human setter called'
      console.log(me);//{}

      At this time, the name attribute will not be created on the me object, but the setter function of the name attribute on human will be called. Similarly, when there are only getter s and no setters, TypeError will be generated in strict mode and silent failure will be generated in non strict mode.

    • The name attribute is not the first two cases

      This is the simplest and most consistent with our expectation. A name attribute will be created on the me object.

    To sum up, when the name attribute does not exist in me, but the name attribute exists on its prototype chain, there are three cases:

    1. If the attribute is an access descriptor on the prototype chain and there is a setter, this setter will be called; If only getter s exist, silent failure will occur in non strict mode, and TypeError will occur in strict mode.
    2. If this attribute is a read-only attribute with writable false in the prototype chain, it will fail silently in non strict mode and TypeError will appear in strict mode.
    3. If the attribute is not the first two cases on the prototype chain, but a common attribute with writable true, the attribute will be created on the me object.

If you have to modify or set the name property of me, you can use the defineProperty method to bypass these restrictions of = assignment.

Object.defineProperty(human, 'name', {
    set() {
        console.log('Called human of setter');
        // me.name = 'forceddd';
    },
});
Object.defineProperty(me, 'name', {
    value: 'forceddd',
    enumerable: true,
    writable: true,
    configurable: true,
});
console.log(me); //{ name: 'forceddd' }

The description in words is always not so intuitive. Finally, it is summarized into a picture for easy understanding.

Topics: Javascript