美文网首页Object常用方法
Object.defineProperty()讲解

Object.defineProperty()讲解

作者: liwuwuzhi | 来源:发表于2017-11-18 00:58 被阅读0次
    由可枚举属性引起的

    JavaScript属性分为可枚举属性和不可枚举属性?什么是“枚举”,它又有什么用呢?

    var apple = {
      name : "苹果",
      price :18
    }
    for(var prop in obj){
        console.log(prop)
    }
    // 输出:name ,price
    // 这里的可枚举性就是说for的这种写法可以得到这个对象的属性名
     
    var stu = {};
    Object.defineProperties(stu, {
        name: {
            value: "张三",
            enumerable: false
        },
        age: {
            value: 18,
            enumerable: true
        }
    });
     
    for(var prop in stu){
        console.log(prop);
    }
    // 输出: age
    //name属性不能便利出来,因为他的 “enumerable” 值为false
     
    //以上代码请在chrome或者火狐里面运行,IE9以下运行第二段代码会出错
    

    我们知道,for...in语句以任意顺序遍历一个对象的可枚举属性
    既 stu.age 可枚举,stu.name 不可枚举,而他们是否可枚举是通过 enumerable 来设置的。

    属性描述符

    我们通常会直接创建对象,然后设置对象的属性,例如上面的 obj 对象,设置了 name ,price 属性。其实这些属性也有一定的限定的,这些限定属性性质的我们称为“属性描述符”。
    这里拿 price 属性来说,我们能输出 fruit.price 等于 18 ,其实是通过 price 的描述符 value 来设置的。例如上面的stu.age 的设置;
    price 也有“enumerable”描述符,他之所以能被 for in 到 ,是因为对自身添加属性的 “enumerable” 默认为 true。

    除了上面说到过的 “value” 和 “enumerable” 还有什么描述符呢?

    每个属性都有
    Configurable描述符、Enumerable描述符、Writable描述符、Value描述符
    或者,每个属性都有
    Configurable描述符、Enumerable描述符、Get描述符、Set描述符
    前者称为 数据描述符,他们的值决定了该js对象的属性的某些性质及是否可读写。
    后者称为 读取描述符,他们的值决定了该js对象的属性的某些性质及读写的行为。

    描述符必须是这两种形式之一,不能同时是两者。

    那么这些属性是各代表着什么?
    数据描述符具有以下键值

    值(默认值) 作用
    configurable false --------------------- 为 true 时,该属性描述符才能够被改变,表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。
    enumerable false true时,该属性在对象中才是可枚举的
    value undefined 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。读取属性的时候就是通过这里开始读
    writable false 表示能否修改属性的值赋值运算符(assignment operator)基于右值(right operand)的值,给左值(left operand)赋值。")改变

    读取描述符具有以下键值

    值(默认值) 作用
    configurable false --------------------- 为 true 时,该属性描述符才能够被改变
    enumerable false true时,该属性在对象中才是可枚举的
    get undefined 在读取属性时调用的函数
    set undefined 在设置属性时调用的函数

    知道这些描述符所控制的性质,那又是什么时候去哪里设置的呢?这就关乎到Object.defineProperty() 了,它有三个参数,Object.defineProperty(obj, prop, descriptor),其中descriptor就是设置属性性质描述符。

    defineProperty 和 defineProperties
    1. 语法
      Object.defineProperty(obj, prop, descriptor)
      Object.defineProperties(obj, props)
    2. 定义:
      Object.defineProperty() 直接在一个对象上定义一个新属性,或者修改现有属性,并返回该对象。
      Object.definePropertys() 直接在一个对象上定义一个或多个新的属性,或者修改现有属性,并返回该对象。
    3. 参数
      obj 要在其上定义属性的对象。
      prop 要定义或修改的属性的名称。
      descriptor 将被定义或修改的属性描述符。
    4. 返回值
      返回被操作的对象,即返回obj参数
    5. 注意点
      1)当把configurable值设置为false后,就不能修改任何属性了,包括自己本身这个属性
      2)想用访问器属性模拟默认行为的话,必须得在里面新顶一个属性,不然的话会造成循环引用
      3)可枚举属性for/in, Object.keys(), JSON.stringify(), Object.assign() 方法才生效(for/in 是对所有可枚举属性,而其他三种是对自身可枚举属性)
    6. 用途
      1)vue通过getter-setter函数来实现双向绑定
      2)俗称属性挂载器
      3)专门监听对象数组变化的Object.observe()(es7)也用到了该方法



    知道了Object.defineProperty()这个东东是用来生成或修改一个对象属性,知道对象属性的性质是靠descriptor这个参数来设置之后,我们来看看他是怎么运用的。
    var person = {}为例,我们要怎样去修改默认的属性值呢?

    • 设置该属性为数据描述符
    var person = {}
    
    Object.defineProperty(person,'a',{
        configurable:true, //可修改默认属性
        enumerable:true, //可枚举
        writable:true, //可修改这个属性的值
        value:1 //定义一个初始的值为1
    })
    
    console.log(person) //{a: 1}
    person.a=2
    console.log(person) //{a: 2}
    
    for(var k in person){
        console.log(k) //a
    }
    

    现在我们来修改下enumerable和writable值

    Object.defineProperty(person,'a',{
        configurable:true,
        enumerable:false,
        writable:false,
        value:1
    })
    
    console.log(person) //{a: 1}
    person.a=2
    console.log(person) //{a: 1} 因为writable值被设置为false了,所以不可以写,严格模式下会报错
    
    for(var k in person){
        console.log(k)// 没有可枚举属性,因为a的enumerable的值被设置为false
    }
    

    我们试试吧configurable的值改为false

    Object.defineProperty(person,'a',{
        configurable:false,//为false的时候不允许修改默认属性了
    })
    ===============================
    # 改为false之后再试试修改其他属性
    Object.defineProperty(person,'a',{
        configurable:true,
        enumerable:true,
        writable:true,
        value:1
    })
    //woa,控制台直接报错了!连想把false值改回true都不行!也就是说,这个改动是一次性了!
    //也就是说,你可以使用Object.defineProperty()方法无限修改同一个属性,但是当把configurable改为false之后就什么都不能再修改了
    
    • 设置该属性为读取描述符
    var person = {
        a:1
    }
    Object.defineProperty(person,'a',{
        get(){
            return 3 //当访问这个属性的时候返回3
        },
        set(val){
            console.log(val)//当设置这个属性的时候执行,val是设置的值
        }
    })
    
    person.a// 3,我们明明写的是a:1,怎么返回的3呢?这就是get()的威力了
    person.a = 5// 5,相应的设置的时候执行了set()函数
    

    我们来模拟一个访问和设置的默认行为

    var person = {
        a:1
    }
    // 注:里面的this指向ogj(person)
    Object.defineProperty(person,'a',{
        get(){
            return this.a 
        },
        set(val){
            this.a = val 
        }
    })
    //我们想当然的这么写.
    person.a//Uncaught RangeError: Maximum call stack size exceeded
    

    什么,溢出了?这是为什么?
    哦~原来是这么写的话会造成循环引用,狂call不止
    我们看下流程:
    person.a → get.call(person) → this.a → person.a → get.call(person) → this.a......
    我们来修改下

    var person = {
        a:1
    }
    Object.defineProperty(person,'a',{
        get(){
            return this._a || 1 //定义一个新的属性和一个默认值
        },
        set(val){
            this._a = val 
        }
    })
    person.a// 1
    person.a=2// 2
    person.a// 2
    这样就好了
    

    这就是数据描述符和读取描述符的应用方式。在平时简单的开发中可能用不上,但是知道了这些之后对一些框架的封装的理解还是很有帮助的,例如vue数据双向绑定原理上利用的就是Object.defineProperty方法。


    拓展

    每个对象都有的一些方法:

    • Object.getOwnPropertyDescriptors(obj)
      定义:获取一个对象的所有自身属性(自身属性)的描述符
      使用:Object.getOwnPropertyDescriptors(apple)

    • Object.getOwnPropertyDescriptor(obj, prop)
      定义:返回指定对象上一个自有属性对应的属性描述符
      使用:Object.getOwnPropertyDescriptor(apple, 'price')

    Object.getOwnPropertyDescriptor 的应用:
    一般直接添加属性时,属性描述符默认值都为 true,当用 Object.defineProperty() 方法来添加对象属性时,此时的属性描述符默认值为false
    以文章开头的 apple 和 stu 的案例:

    // 直接添加属性
    Object.getOwnPropertyDescriptor(apple, 'name')
    //name:{value: "苹果", writable: true, enumerable: true, configurable: true}
    
    // 属性描述符添加属性
    Object.getOwnPropertyDescriptor(stu, 'name')
    //name:{value: "张三", writable: false, enumerable: false, configurable: false}
    
    
    // 又或者
    var stu2 = Object.create({}, { name: { value: '李四' } })
    Object.getOwnPropertyDescriptor(stu2, 'name')
    //name:{value: "李四", writable: false, enumerable: false, configurable: false}
    

    Object.create() 的第二个参数为 Object.defineProperties 的第二个参数,既设置属性及属性描述符, 详情移步到 Object.create()


    参考资料:
    1.MDN
    3.关于 Object.defineProperty() 小结

    相关文章

      网友评论

        本文标题:Object.defineProperty()讲解

        本文链接:https://www.haomeiwen.com/subject/xpoyvxtx.html