用Object.defineProperty在es5下实现常量声明

本文目的:es5实现es6的const常量声明

目录

1.defineProperty用法

1.1 Object.defineProperty(obj,  prop,  descriptor)

1.2 修饰参数

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 修饰参数的用法

1.4 需要警惕的默认值

1.4.1 显示创建对象属性

1.4.2 defineProperty创建对象属性

2 用defineProperty实现常量声明

2.1 数据描述符实现

2.2 存取描述符实现

3.难以避免的缺陷以及尽可能的优化

3.1 所有常量都是全局的

3.2 变量提升

3.3 尽可能的优化


1.defineProperty用法

1.1 Object.defineProperty(obj,  prop,  descriptor)

方法用于对对象的属性进行修饰,三个传入参数。第一个参数是要修饰的对象;第二个参数是修饰对象中要被修饰的属性;第三个参数是对于该属性的修饰内容,以对象形式传入,有固定的写法。

举个栗子来简单的说明一下

var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    writable: false    // example.test的值无法修改
})
example.test = 'change'    // 严格模式下会直接报错
console.log(example.test)    // test

1.2 修饰参数

下面来解释下它的修饰内容有哪些,一共有六条属性。

1.2.1 configurable

当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为 false。(该段解释源自MDN)

    var example = {}
    Object.defineProperty(example, 'test', {
        configurable: false
    })
    Object.defineProperty(example, 'test', {
        configurable: true
    })    // 报错,因为configurable为false,所以描述符不能再被更改
var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    configurable: false
})
delete example.test // 严格模式下会报错
console.log(example.test)  // test

1.2.2 enumerable

当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
默认为 false。(该段解释源自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

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。(该段解释源自MDN)

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

1.2.4 writable

当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
默认为 false(该段解释源自MDN)

var example = {}
example.test = 'test'
Object.defineProperty(example, 'test', {
    writable: false    // example.test的值无法修改
})
example.test = 'change'    // 严格模式下会直接报错
console.log(example.test)    // test

1.2.5 get

属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为 undefined。(该段解释源自MDN)

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

1.2.6 set

属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined。(该段解释源自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
        }

        // 这里之所以用_test赋值,是为了防止test属性自身赋值时调用set产生无限递归

1.3 修饰参数的用法

        看完上面的6条修饰属性后,可能会感到很凌乱。可以发现,value和get的功能很类似,writable和set的功能很类似。那么应该怎么使用才能避开冲突呢?

        答案其实很简单:分开用。

        修饰符的写法可以分成互不共存的两类,分别名为,数据描述符和存取描述符(这两条名词源于MDN)。

        数据描述符包括:configurable,enumerable,value,writable

        存取描述符包括:configurable,enumerable,get,set

        在写修饰参数时,数据描述符独有的value,writable和存取描述符独有的get,set是不能同时出现的,否则会报错。

1.4 需要警惕的默认值

1.4.1 显示创建对象属性

在显式创建一个对象属性时,该属性的修饰词会默认使用数据描述符,并且给value赋予属性值(如果没有属性值的话会赋予默认值undefined,这也是为什么会报错提示 Cannot read properties of undefined 的原因)enumerable、writable的值赋予true。而configurable要分两种情况,一种是通过var声明的,一种是直接作为对象属性创建的

    // var声明的对象属性(这里相当于在window对象下声明)    
    var test='test'
    var descriptor = Object.getOwnPropertyDescriptor(window,'test')    // 获取修饰词
    console.log(descriptor)
    // {configurable: false, enumerable: true, value: 'test', writable: true}
    test = 'test'; // 作为window对象属性
    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')    // 获取修饰词
    console.log(descriptor1)
    // {configurable: true, enumerable: true, value: 'test', writable: true}

可以看到,上述两块代码中,修饰符的差别主要体现在configurable

这里可以理解为,因为configurable具有能否被delete删除的功能。上述第一部分代码 var 声明的变量是作为作用域内一个变量存在的,当然没有delete变量这样的写法。而第二部分代码就只是给对象增加了一个属性,所以理应会支持delete来删除

1.4.2 defineProperty创建对象属性

但如果使用defineProperty创建属性,那么未赋值修饰词就会变成默认值(默认值可参考1.2)

    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 用defineProperty实现常量声明

2.1 数据描述符实现

    function setConst(key, value){
        Object.defineProperty(window, key, {
            configurable: true,
            enumerable: true,
            value: value,
            writable: false
        })
    }
    setConst('test', 'test')
    test = 'change'  // 非严格模式不会报错
    console.log(test)  // test

即在window对象下声明了一个不可修改的变量

但这样做有一个缺点,就是如果不在严格模式下,则给常量赋值是不会有报错的提示的

所以就需要另一种实现方式——用访问器属性实现

2.2 存取描述符实现

    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

这样可以同样达到声明常量的效果。并且在对常量进行二次赋值时会进行提示

3.难以避免的缺陷以及尽可能的优化

相比于es6的const,我们现在的方法无疑是有很多缺陷的

3.1 所有常量都是全局的

const声明的变量是存在块级作用域的,但setConst产生的所有常量是挂在window全局作用域上的

    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 变量提升

const是不允许变量提升的。但我们的写法如果在调用setConst前使用或声明了该变量,则会因为var允许变量提升的原因,结果就会在作用域中显式声明了一个变量。

如果这个作用域为全局,则相当于提前创建了一个全局变量,并被赋予修饰符。(此时的修饰符内容可参考1.4.1)这样很容易与setConst中defineProperty产生冲突

如果这个作用域为函数作用域,则该变量与setConst生成的全局变量完全没有一点关系

3.3 尽可能的优化

对于上面的问题,可以在函数作用域中声明一个空对象,然后将常量全部维护在这个对象中

    use()

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

    // 方法新增一个入参用于规定属性所在的目标对象
    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`)
            }
        })
    }

通过这样的方式至少可以将常量限制在局部作用域内。但对于变量提升依然没有处理的办法

所以,综上所述,能用es6就不要用es5(QAQ)

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值