Use object Defineproperty implements constant declarations under es5

Purpose of this article: es5 implements the const constant declaration of es6

catalogue

1.defineProperty usage

1.1 Object.defineProperty(obj,  prop,  descriptor)

1.2 modification parameters

1.2.1 configurable

1.2.2 enumerable

1.2.3 value

1.2.4 writable

1.2.5 get

1.2.6 set

1.3 usage of modification parameters

1.4 default values requiring vigilance

1.4.1 display creation object properties

1.4.2 creating object properties with defineproperty

2 implement constant declaration with defineProperty

2.1 implementation of data descriptor

2.2 implementation of access descriptor

3. Unavoidable defects and optimization as much as possible

3.1 all constants are global

3.2 variable lift

3.3 optimize as much as possible

1.defineProperty usage

1.1 Object.defineProperty(obj,  prop,  descriptor)

Method is used to modify the properties of an object. Three parameters are passed in. The first parameter is the object to be decorated; The second parameter is the attribute to be modified in the modified object; The third parameter is the modified content of the attribute, which is passed in as an object and has a fixed writing method.

Take a chestnut for a brief explanation

var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    writable: false    // example. The value of test cannot be modified
})
example.test = 'change'    // Errors will be reported directly in strict mode
console.log(example.test)    // test

1.2 modification parameters

Let's explain what its modifications are. There are six attributes in total.

1.2.1 configurable

The descriptor of the attribute can be changed and the attribute can be deleted from the corresponding object only when and only when the key value of "configurable" of the attribute is "true".
The default is false. (this paragraph is interpreted from MDN)

    var example = {}
    Object.defineProperty(example, 'test', {
        configurable: false
    })
    Object.defineProperty(example, 'test', {
        configurable: true
    })    // An error is reported. Because configurable is false, the descriptor can no longer be changed
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    configurable: false
})
delete example.test // Errors will be reported in strict mode
console.log(example.test)  // test

1.2.2 enumerable

This property appears in the enumeration property of an object only if and only if the enumerable key value of this property is true.
The default is false. (this paragraph is interpreted from MDN)

        var example = {
            one: 1,
            two: 2,
            three: 3,
            four: 4
        }
        Object.defineProperty(example, 'three', {
            enumerable: false
        })
        for(var k in example){
            console.log(k, example[k])
        }
        // one 1
        // two 2
        // four 4

1.2.3 value

The value corresponding to this attribute. Can be any valid JavaScript value (value, object, function, etc.).
Default to undefined . (this paragraph is interpreted from MDN)

var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    value: 'change'
})
console.log(example.test)    // change

1.2.4 writable

If and only if the , writable , key value of the attribute is , true , the value of the attribute, that is, the , value above, can be changed by the assignment operator.
The default is false. (this paragraph is interpreted from MDN)

var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    writable: false    // example. The value of test cannot be modified
})
example.test = 'change'    // Errors will be reported directly in strict mode
console.log(example.test)    // test

1.2.5 get

Property. If there is no getter, it is {undefined. This function is called when the property is accessed. No parameters will be passed in during execution, but the {this} object will be passed in (due to inheritance, this here is not necessarily the object that defines this attribute). The return value of the function is used as the value of the property.
Default to undefined . (this paragraph is interpreted from MDN)

        var example = {}
        example.test = 'test'
        Object.defineProperty(example, 'test', {
            get: function (){
                return 'getTest'
            }
        })
        console.log(example.test)    // getTest

1.2.6 set

Property. If there is no setter, it is {undefined. This function is called when the property value is modified. This method accepts a parameter (i.e. the new value assigned) and passes in the {this} object at the time of assignment.
Default to undefined . (this paragraph is interpreted from MDN)

        var example = {}
        example.test = 'test'
        Object.defineProperty(example, '_test', {
            set: function () {
                this.test = 'setTest'
            }
        })
        var arr = ['test1', 'test2', 'test3']
        for (var k in arr) {
            example._test = arr[k]
            console.log(example.test)    // setTest
        }

        // Why is it used here_ Test assignment is to prevent infinite recursion when calling set when assigning the test attribute itself

1.3 usage of modification parameters

After reading the above 6 modified attributes, you may feel very messy. It can be found that the functions of value and get are very similar, and the functions of writable and set are very similar. So how should we use it to avoid conflict?

The answer is simple: separate.

Modifiers can be written in two different ways: data descriptors and access descriptors (these two nouns are derived from MDN).

Data descriptors include: configurable,enumerable,value,writable

Access descriptors include: configurable,enumerable,get,set

When writing modification parameters, value and writable unique to the data descriptor and get and set unique to the access descriptor cannot appear at the same time, otherwise an error will be reported.

1.4 default values requiring vigilance

1.4.1 display creation object attributes

When an object property is explicitly created, the modifier of the property will use the data descriptor by default, and the value will be given the property value (if there is no property value, the default value will be given undefined, which is why the error prompt Cannot read properties of undefined) and the values of enumerable and writable will be given true. configurable can be divided into two cases: one is declared through var, and the other is created directly as an object attribute

    // Object attribute declared by var (this is equivalent to declaring under window object)    
    var test='test'
    var descriptor = Object.getOwnPropertyDescriptor(window,'test')    // Get modifier
    console.log(descriptor)
    // {configurable: false, enumerable: true, value: 'test', writable: true}
    test = 'test'; // As a window object property
    var descriptor = Object.getOwnPropertyDescriptor(window,'test')
    console.log(descriptor)
    // {configurable: true, enumerable: true, value: 'test', writable: true}

    var example = {}
    example.test='test'
    var descriptor1 = Object.getOwnPropertyDescriptor(example,'test')    // Get modifier
    console.log(descriptor1)
    // {configurable: true, enumerable: true, value: 'test', writable: true}

You can see that in the above two codes, the difference of modifiers is mainly reflected in configurable

It can be understood here that configurable has the function of whether it can be deleted by delete. The variable declared by var in the first part of the code above exists as a variable within the scope. Of course, there is no such writing method as delete variable. The second part of the code just adds an attribute to the object, so it should support delete to delete

1.4.2 creating object properties with defineproperty

However, if you use defineProperty to create a property, the unassigned modifier will become the default value (see 1.2 for the default value)

    var example = {}
    Object.defineProperty(example, 'test', {
        value: 'test'
    })
    var descriptor = Object.getOwnPropertyDescriptor(example, 'test')
    console.log(descriptor)
    // { configurable: false, enumerable: false, value: "test", writable: false }

2 implement constant declaration with defineProperty

2.1 implementation of data descriptor

    function setConst(key, value){
        Object.defineProperty(window, key, {
            configurable: true,
            enumerable: true,
            value: value,
            writable: false
        })
    }
    setConst('test', 'test')
    test = 'change'  // No error will be reported in non strict mode
    console.log(test)  // test

That is, a non modifiable variable is declared under the window object

However, there is a disadvantage in this way, that is, if it is not in the strict mode, there will be no error message when assigning a value to a constant

So we need another implementation -- using accessor properties

2.2 implementation of access descriptor

    function setConst(key, value){
        Object.defineProperty(window, key, {
            configurable: true,
            enumerable: true,
            get: function (){
                return value
            },
            set: function (){
                console.error(`${key} is a constant. Can't be modified`)
            }
        })
    }
    setConst('test', 'test')
    test = 'change'  // test is a constant. Can't be modified
    console.log(test)  // test

This can also achieve the effect of declaring constants. And prompt will be given when the constant is assigned twice

3. Unavoidable defects and optimization as much as possible

Compared with the const of es6, our current method undoubtedly has many defects

3.1 all constants are global

The variables declared by const have block level scope, but all constants generated by setConst are hung on the window global scope

    function setConst(key, value){
        Object.defineProperty(window, key, {
            configurable: true,
            enumerable: true,
            get: function (){
                return value
            },
            set: function (){
                console.error(`${key} is a constant. Can't be modified`)
            }
        })
    }
    setConst('test', 'test')
    console.log(window.test)  // test

3.2 variable lift

const does not allow variable promotion. However, if we use or declare this variable before calling setConst, we will explicitly declare a variable in the scope because var allows variable promotion.

If the scope is global, it is equivalent to creating a global variable in advance and being given a modifier. (refer to 1.4.1 for the modifier content at this time) it is easy to conflict with the defineProperty in setConst

If the scope is a function scope, the variable has nothing to do with the global variable generated by setConst

3.3 optimize as much as possible

For the above problem, you can declare an empty object in the function scope, and then maintain all constants in this object

    use()

    function use() {
        var constant = {}
        setConst('test', 1, constant)
        console.log(constant.test)  // 1
        // ...
    }

    // Method adds an input parameter to specify the target object where the attribute is located
    function setConst(key, value, obj){
        obj= obj? obj: window
        Object.defineProperty(obj, key, {
            configurable: true,
            enumerable: true,
            get: function (){
                return value
            },
            set: function (){
                console.error(`${key} is a constant. Can't be modified`)
            }
        })
    }

In this way, constants can at least be limited to local scope. However, there is still no way to deal with variable promotion

Therefore, to sum up, do not use es5 (QAQ) if you can use es6

Keywords: Javascript Front-end

Added by jedney on Thu, 06 Jan 2022 13:24:15 +0200