likes
comments
collection
share

JavaScript花样百出的属性定义

作者站长头像
站长
· 阅读数 12

前言

Object.defineProperty

JavaScript中最简单直接的给对象定义属性的方式为对象.属性名=属性值,但有时候需要对对象的属性做一些更深层次的定义,那么可以通过Object.defineProperty的方式给对象定义属性

简单示例如下

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse'
})

console.log(obj)
console.log(Object.getOwnPropertyDescriptor(obj, 'name'))

输出

{name: 'Bruse'}
{value: 'Bruse', writable: false, enumerable: false, configurable: false}

属性描述符

在定义属性的时候,可以通过配置不同的属性描述符来定义属性是否可以被删除、被修改等。[和Java中的属性修饰符private、public、statis、final等有点像,都控制了外部访问以及修改对象某个属性的权限,但Java中并不能很灵活地删除属性]

configurable 是否可配置

配置configurable描述符,可以决定该对象的该属性是否可以被删除,被修改。

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse',
  configurable: false // 不可配置
})
delete obj.name
console.log(obj)

当属性不可配置时,尝试进行删除该属性,运行会抛出异常

Uncaught TypeError: Cannot delete property 'name' of #<Object>

同理,修改属性值也是一样

obj.name = '123'

运行抛出异常

Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'

但只要在定义属性的时候定义为configurable: false,以上操作则都不会产生问题

configurablewritable

有一种情况是configurablefalse,但是writabletrue的话,那么还是可以修改属性的值

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'bruse',
  configurable: false,
  writable: true
})
obj.name = 'bat'
console.log(obj)

输出{name: 'bat'}

如果再把writable改为false,那么在对属性赋值时将会抛出异常

Object.defineProperty(obj, 'name', {
  writable: false
})
obj.name = 'wa'
console.log(obj)

抛出异常

Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'

Tips:若configurablefalse,那么writable可以由true变为false,但无法从false改为true

configurable: false 不可逆

当属性一开始就定义了configurable: false时,无法再通过Object.defineProperty重新修改

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse',
  configurable: false
})

Object.defineProperty(obj, 'name', {
  configurable: true
})

console.log(Object.getOwnPropertyDescriptor(obj, 'name'))

运行会抛出异常,也就是说无法从configurable: false转为configurable: true

Uncaught TypeError: Cannot redefine property: name
    at Function.defineProperty
configurable: true可逆

与之相反的是,若一开始配置属性configurable: true,反而倒是可以将其修改为configurable: false

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse',
  configurable: true
})

Object.defineProperty(obj, 'name', {
  configurable: false
})

console.log(Object.getOwnPropertyDescriptor(obj, 'name'))

运行正常,最后输出

{value: 'Bruse', writable: false, enumerable: false, configurable: false}

enumerable 是否可枚举

enumerabletrue时,对象的该属性可以被for...in...Object.keys获取

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse',
  configurable: true,
  enumerable: true
})

console.log(Object.keys(obj))

输出['name']

enumerablefalse时,对象的该属性则不可被for...in...Object.keys获取

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse',
  configurable: true,
  enumerable: false
})

console.log(Object.keys(obj))

输出[]

writable 是否可更改

const obj = {}
Object.defineProperty(obj, 'name', {
  value: 'Bruse',
  writable: true
})

obj.name = 'bat'
console.log(obj)

输出{name: 'bat'}

若将writable修改为false,则无法再修改name的值

Object.defineProperty(obj, 'name', {
  writable: false
})
obj.name = 'wa'
console.log(obj)

抛出异常,obj.name变成了只读属性,只能读取无法修改

Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'

value 属性值

value其实就是定义属性时,该属性的默认值。即便属性没有默认值,只要writabletrue,那么也可以在定义了属性之后为该属性赋值

const obj = {}
Object.defineProperty(obj, 'name', {
  writable: true
})
obj.name = 'bat'
console.log(obj)

输出{name: 'bat'}

Tips:若在Object.defineProperty()定义属性时,没有设置value的值,那么默认为undefined

get访问器函数

get函数其实跟Javaget/set方法差不多,get函数可以让我们在获取对象属性值时,做一些额外的操作

let name = 'Bruse'
const obj = {}
Object.defineProperty(obj, 'name', {
  configurable: true,
  enumerable: true,
  get() {
    return name
  }
})

console.log(obj.name) // 输出Bruse

我们也可以做一些额外操作装饰一下返回值

get() {
  return `My name is ${name}`  // 输出My name is Bruse
}

set访问器函数

set函数可以让我们在设置对象属性值时,做一些额外的操作

let name = 'Bruse'
const obj = {}
Object.defineProperty(obj, 'name', {
  configurable: true,
  enumerable: true,
  get() {
    return `My name is ${name}`
  },
  set(v) {
    if (v === undefined || v === null) {  // 做一下空判断,设置默认值
      name = 'secret'
    } else {
      name = v
    }
  }
})
obj.name = undefined
console.log(obj.name) // 输出My name is secret

obj.name = 'Bruse'
console.log(obj.name) // 输出My name is Bruse

Tips: 使用get/set访问函数的话,就不能再配置value或writable了,两者互斥

Object.preventExtensions

Object.preventExtensions()可以让对象变得无法扩展,即无法再新增新的属性。

const obj = {}
obj.age = 16    // 新增age属性
console.log(obj)

对象默认是可扩展的,所以当定义了一个对象后,还可以继续给这个对象动态新增一些属性,上边代码执行后输出{age: 16}

也可以通过Object.isExtensible()来查看对象是否可拓展

console.log(Object.isExtensible(obj)) // 返回true,表示可拓展

接下来使用Object.preventExtensions()使对象不可拓展

Object.preventExtensions(obj)

obj.phone = '121xxxxxx' // 新增属性

console.log(obj)

此时obj对象变得不可拓展,并且执行代码时会抛出异常

Uncaught TypeError: Cannot add property phone, object is not extensible

虽然对象不可再拓展,但原有的属性还是可以做修改或删除

Object.preventExtensions(obj)
obj.age = 123
console.log(obj)

输出{age: 123}

Object.preventExtensions(obj)
delete obj.age
console.log(obj)

输出{}

Object.seal

Object.seal可以看做是Object.preventExtensions的升级,除了让对象不可再新增属性外,还把对象已有的属性标记为不可配置

const obj = {}
obj.age = 16
console.log(Object.getOwnPropertyDescriptor(obj, 'age'))

输出

{value: 16, writable: true, enumerable: true, configurable: true}

接着调用Object.seal

Object.seal(obj)
console.log(Object.getOwnPropertyDescriptor(obj, 'age'))

再次输出

{value: 16, writable: true, enumerable: true, configurable: false}

此时若尝试删除obj.age属性的话,会产生异常

delete obj.age

执行错误

Uncaught TypeError: Cannot delete property 'age' of #<Object>

尝试添加属性也会产生异常

obj.phone = '12133'

抛出异常

Uncaught TypeError: Cannot add property phone, object is not extensible 

可以通过调用Object.isSealed()判断对象是否被封闭

console.log(Object.isSealed(obj))  // 输出true

Object.freeze

Object.freeze()则比Object.preventExtensionsObject.seal还要更近一步,它会让对象不能添加新属性,已有属性不可配置,且不能修改值

const obj = {}
obj.age = 16
console.log(Object.getOwnPropertyDescriptor(obj, 'age'))

输出

{value: 16, writable: true, enumerable: true, configurable: true}

使用Object.freeze

Object.freeze(obj)
console.log(Object.getOwnPropertyDescriptor(obj, 'age'))

输出

{value: 16, writable: false, enumerable: true, configurable: false}

此时尝试修改属性

obj.age = 18

抛出异常

Uncaught TypeError: Cannot assign to read only property 'age' of object '#<Object>'

尝试新增属性

obj.phone = '113444'

还是抛出异常

Uncaught TypeError: Cannot add property phone, object is not extensible

甚至无法再修改属性的定义了

Object.defineProperty(obj, 'age', {
  enumerable: false
})

抛出异常

Uncaught TypeError: Cannot redefine property: age
    at Function.defineProperty (<anonymous>)

Tips: 可以通过Object.isFrozen()判断对象是否被冻结