讲清楚之 javascript 对象属性描述符

对象属性描述符

当别人对你说起对象属性描述符,可能会蒙逼。而若是说起对象属性的 get/set 方法就秒懂了,标准描述和习惯表述在这里有些差异,可是指向的是同一个概念所涉及的东西。对象属性描述符在编程实践中是经过 Object 对象的defineProperty方法暴露给咱们。因此搞清楚Object.defineProperty是理解对象属性描述符的惟一途径。javascript

Object.defineProperty, define Property 翻译成中文就是定义属性,顾名思义就是为对象定义或修改属性的细节,即经过属性描述符来定义属性读写的细节。使用该方法容许精确添加或修改对象的属性,熟悉 vue 的朋友对 defineProperty 因该不陌生:vue

Object.defineProperty(obj, prop, descriptor)

defineProperty 接受3个参数, obj 表示要修改或者定义属性的对象,prop 是要定义或者修改属性的名称, descriptor 属性描述符用于定义该属性的特性。java

descriptor 是一个对象,对象里的属性描述符有两种类型:数据描述符存取描述符编程

数据描述符是一个具备值的属性,该值多是可写的,也可能不是可写的。浏览器

存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是二者。框架

数据描述符和存取描述符均具备一下可选键值(特性):函数

  • configurable: 若是为 false,则任未尝试删除目标属性或修改属性如下特性(writable, configurable, enumerable)的行为将被无效化,默认值为 false。
  • enumerable: 是否能枚举。也就是是否能被for-in遍历。默认值为 false
  • writable: 是否能修改值。默认为 false
  • value: 该属性的具体值是多少。默认为 undefined

存取描述符:this

  • get: 目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。默认为 undefined
  • set: 目标属性被赋值,就会调回此方法。默认为 undefined

描述符可同时具备的键值:翻译

configurable enumerable value writable get set
数据描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

若是一个描述符不具备value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。若是一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。因此 value、writable 与 get/set 不能同时设置。code

var obj = {}
obj.a = 123

Object.defineProperty(obj, "newDataProperty", {
    value: 101, // 设置值
    writable: true, // 值能够被修改
    enumerable: true, // 能够被枚举
    configurable: true // 属性能够被删除、特性能够修改
})

上面给对象 obj 添加一个新属性 'newDataProperty',而且设置了属性的特性。

在ES5以前对象的属性咱们只能设置一个字面量值或者一个引用,在浏览器支持Object.defineProperty方法以后,就像给了咱们一台显微镜,可以在更低的粒度层控制属性的行为和特性:定义属性的可访问行、值的读写规则等。

若是对象中不存在指定的属性,Object.defineProperty()会建立这个属性。若是属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。若是旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,而且没有属性能够被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换。

描述符中未显示设置的特性使用其默认值。

下面用几个栗子来演示这些特性的具体表现:

configurable

let foo = {
    a: 1
}
delete foo.a
Object.defineProperty(foo, 'b', {
    value: 2, // 默认值为2
    configurable: false // 不允许被删除和修改
})
delete foo.b // 没法删除
foo.b = 999 // 没法修改
console.log(foo.b) // 2

enumerable

let foo = {
    a: 1,
    b: 2,
    c: 3
}
for (let i in foo) {
    // a、b、c能够被枚举
    console.log(`key: ${i}, value: ${foo[i]}`)
}

Object.defineProperty(foo, 'a', {
    enumerable: false // 设置属性不能够被枚举
})
for (let i in foo) {
    // a没有被枚举
    console.log(`key: ${i}, value: ${foo[i]}`)
}

writable

let foo = {
    a: 1
}
// 修改 foo.a 的值
foo.a = 2 
console.log(foo.a) // 2

Object.defineProperty(foo, 'a', {
    writable: false // 设置值不能被修改
})
// 尝试修改 foo.a 的值
foo.a = 3 // 没法修改
console.log(foo.a) // 2

value

let foo = {}
Object.defineProperty(foo, 'a', {
    value: 1 // 设置属性的值为 1
})
console.log(foo.a) // 1

get/set

let foo = {
    a: 1
}
Object.defineProperty(foo, 'b', {
    get: function () {
        return `hi, ${this.value}`
    },
    set: function (value) {
        this.a = value // 将输入值保存在同对象下属性 a 里
        this.value = value + 1
    }
})
console.log(foo.b) // 'hi, undefined'
foo.b = 1
console.log(foo.a) // 1
console.log(foo.b) // hi, 2

注意: get没有参数,set接受实参为当前设置的值.。在get、set函数内部能够经过this.value访问value特性,从而经过该特性来获取或者着设置属性的值。get/set 经常使用于值依赖内部数据的场合。须要尽可能同时设置get、set。若是仅仅只设置了get,那么咱们将没法设置该属性值。若是仅仅只设置了set,咱们也没法读取该属性的值。

Object.defineProperty只能设置一个属性的描述符,当须要设置多个属性描述符时可使用Object.defineProperties

let foo = {}
Object.defineProperties(foo, {
    a: {
        value: 1,
        configurable: true
    },
    b: {
        get: function() {
            return this.value ? `hi, ${this.value}` : 0
        },
        set: function(value) {
            this.value = value + 1
        }
    }
})

console.log(foo.a) // 1
console.log(foo.b) // 0
foo.b = 2
console.log(foo.b) // 'hi, 3'

咱们能够经过Object.getOwnPropertyDescriptor获取某一属性的特性集合:

let foo = {
    a: 1
}
Object.defineProperty(foo, 'a', {
    value: 2, // 设置值为 2
    writable: false, // 值不可修改
    configurable: false // 设置属性不可删除,特性不可修改
})
let fooDescripter = Object.getOwnPropertyDescriptor(foo, 'a')

console.log(fooDescripter)
// 获取的特性以下
// {
//   configurable:false,
//   enumerable:true,
//   value:2,
//   writable:false
// }

这里须要注意,Object.defineProperty建立一个对象的新属性与修改一个已经存在属性的区别。建立一个新属性默认描述符的键值都是 false 或者 undefined。而修改一个已经存在的属性的描述符时,若是以前没有被设置过或过原始方式给对象添加的属性,则属性的 configurable、enumerable、writable 描述符都默认为 true。具体差别举个例子细细体会:

let foo = {}
Object.defineProperty(foo, 'a', {
    value: 2 // 设置值为 2
})
let fooDescripter = Object.getOwnPropertyDescriptor(foo, 'a')

console.log(fooDescripter)
// 获取的特性以下
// {
//   configurable:false, // 不允许被删除和修改
//   enumerable: false, // 不能被枚举
//   value:2,
//   writable:false // 值不可修改
// }

变量 a 是经过 Object.defineProperty方法建立的,默认全部属性描述符的值都为 false。 咱们能够经过最后两个代码示例体会一下区别:enumerable属性描述符在两个例子中都没有被事先设置,可是不一样情形下的值不同。

原则上这个系列不会去讲某个API,可是属性描述符可以加深咱们对 javascript 、框架底层的理解。

相关文章
相关标签/搜索