JavaScript对象属性

JavaScript对象属性

1.属性类型与数据描述符

Javascript中,对象的属性有两种类型:

  1. 数据属性Data property
  2. 访问器属性Accessor property

属性的类型取决于在定义时,传入的属性描述符对象。

1.1 数据属性的属性描述符

在通过Object.defineProperty()定义数据属性时,它独有的属性描述符有两个:

  1. value: 属性的值,默认为undefined
  2. writable: 是否可写(修改),默认为false
javascript 复制代码
const obj = {}
Object.defineProperty(obj, 'a', {})
console.log(obj.a) // undefined

obj.a = 'Hello JavaScript'
console.log(obj.a) // undefined

在上面的代码中,给obj对象定义了一个名为a的属性,提供了一个空的属性描述符对象,所以,valuewritable都取默认值。 因此,valueundefinedwritablefalse。这就导致了无法修改obj.a的值,所以,第二次也是输出undefined

这里没有使用严格模式,修改obj.a的值失败时,不会收到任何警告提示,在写代码时,需要特别注意。 如果想要在读取只读属性时报错,则需要使用严格模式。(在文件开头添加"use strict"

在非严格模式下,如果想要让一个属性只读,在修改只读属性时要给出一定的提示,可以使用另一种类型的属性,即访问器属性

1.2 访问器属性的属性描述符

在通过Object.defineProperty()定义访问器属性时,它独有的属性描述符有两个:

  1. get():访问器getter,在访问属性的时候调用
  2. set():设置器setter,在设置属性的时候调用
javascript 复制代码
let data = 'Hello World'
const obj = {}
Object.defineProperty(obj, 'a', {
  get() {
    return data
  },
  set(value) {
    data = value
  }
})

console.log(obj.a) // Hello world
obj.a = 'Hello JavaScript'
console.log(obj.a) // Hello JavaScript
console.log(obj.a == data) // true

上面的代码中,使用defineProperty()定义了一个访问器属性,当使用obj.a获取属性的值时,就会调用getter ; 当obj.a出现在赋值号左边时,就会调用setter ,且把赋值号右边的表达式的值作为参数传递给setter

现在,回过头想一下上面的问题,在非严格模式下,对于writable: false的数据属性,在修改它的值时,没有任何的提示,现在,通过访问器属性,我们可以解决这个问题:

javascript 复制代码
let data = 'Hello World'
const obj = {}
Object.defineProperty(obj, 'a', {
  get() {
    return data
  },
  set(value) {
    console.warn('The property a of obj is readonly')
  }
})

obj.a = 'Hello JavaScript' // WARN: The property a of obj is readonly

对于上面的代码 ,也不是说obj.a绝对不可变,只是说我们无法通过obj.a = ...的方式修改。 换句话说,我们创建了一个变量data的代理,我们没办法通过代理修改data,但是还是可以直接修改data

javascript 复制代码
let data = 'Hello World'
const obj = {}
Object.defineProperty(obj, 'a', {
  get() {
    return data
  },
  set(value) {
    console.warn('The property a of obj is readonly')
  }
})

data = 'Hello JavaScript'
console.log(obj.a) // Hello JavaScript

1.3 通用的属性描述符

上面两小节,分别介绍了两种不同类型属性各自特有的属性描述符。 下面,介绍这两种属性共有的描述符。在通过Object.defineProperty()定义属性时,两种属性共有的属性描述符有两个:

  1. configurable:是否可配置。默认为false
  2. enumerable: 是否可枚举。默认为false

configurable

如果configurablefalse

  1. 无法修改属性的类型,即不能将数据属性重新定义为访问器属性,反之亦然。
  2. 对于访问器属性,无法重新定义get()set()函数。
  3. 对于数据属性:
    1. 如果writabletrue,则可以修改value,这符合writable的语义。
    2. 如果writabletrue,则可以将writable修改为false,反过来则不行。
  4. 无法删除属性

数据属性举例:

javascript 复制代码
const obj = {}
Object.defineProperty(obj, 'a', { value: 'Hello World', writable: true /**configurable默认为false */})

// 尝试修改value
Object.defineProperty(obj, 'a', { value: 'Hello JavaScript' })
console.log(obj.a) // Hello JavaScript

// 尝试修改writable
Object.defineProperty(obj, 'a', { writable: false })
obj.a = '666' // writable是false 修改失败 没有提示信息
console.log(obj.a) // Hello JavaScript

// 尝试删除属性
delete obj.a // 删除失败 没有提示信息
console.log(obj.a)  // Hello JavaScript


// 尝试修改writable
Object.defineProperty(obj, 'a', { writable: true }) // TypeError: Cannot redefine property: a
// configurable为false 不能将writable从false 改成true


// 尝试修改属性类型
Object.defineProperty(obj, 'a', { get() { return 'Hello JavaScript' } }) // TypeError: Cannot redefine property: a
// configurable为false 不能改变属性类型

// 尝试删除属性
delete obj.a

访问器属性举例:

javascript 复制代码
const obj = {}
Object.defineProperty(obj, 'a', { get() { return 'Hello World' } })

// 尝试修改getter
Object.defineProperty(obj, 'a', { get() { return 'Hello JavaScript' } }) // TypeError: Cannot redefine property: a

// 尝试修改属性类型
Object.defineProperty(obj, 'a', { value: 'Hello JavaScript' }) // TypeError: Cannot redefine property: a

enumerable

如果enumerablefalse

  1. Object.assign()函数会忽略该属性。
  2. 展开语法会忽略该属性。
  3. for...in循环会忽略该属性。
  4. Object.keys()会忽略该属性。
javascript 复制代码
const obj = {}

Object.defineProperty(obj, 'a', { value: 1 /**enumerable默认为false */})
Object.defineProperty(obj, 'b', { value: 2, enumerable: 'true' })

console.log(Object.assign({}, obj)) // {b: 2}

console.log({...obj}) // {b: 2}

for(const key in obj) {
  console.log(key)
}
// b

console.log(Object.keys(obj))// ['b']

1.4 小结

在上面我介绍描述符的默认值时候,我都会强调在使用Object.defineProperty()函数时 。 别忘了,在一开始学习JavaScript时,我们使用更简单的方式定义属性:

javascript 复制代码
const obj = {}
obj.a = 'Hello World'

在使用.运算符定义属性时,会定义一个数据属性,且configurableenumerablewritable均为true

javascript 复制代码
const obj = {}
obj.a = 'Hello World'
console.log(Object.getOwnPropertyDescriptor(obj, 'a')) // {"value":"Hello World","writable":true,"enumerable":true,"configurable":true}

还有另一个细节,对于已经存在 的数据属性来说,如果通过Object.defineProperty()修改value,会调用内部方法[[DefineOwnProperty]]; 然而,如果使用obj.pro = ...的方式修改属性的值,会调用内部方法[[Set]]

我们可以通过代理拦截内部方法

javascript 复制代码
const obj = {}

const proxy = new Proxy(obj, {
  set(target, key, newValue) {
    console.log('[[Set]]')

    return Reflect.set(target, key, newValue)
  },

  defineProperty(target, key, descriptor) {
    console.log('[[DefineProperty]]')
    
    return Reflect.defineProperty(target, key, descriptor)
  }
})


Object.defineProperty(proxy, 'a', { value: 'Hello World', writable: true }) // [[DefineProperty]]
proxy.a = '-> Hello JavaScript' // [[Set]]

Object.defineProperty(proxy, 'b', { value: 'Hello Proxy' }) // [[DefineProperty]]
Object.defineProperty(proxy, 'b', { value: 'Hello new value' }) // [[DefineProperty]]

最后,推荐使用严格模式,否则很多错误信息会被忽略。

2.其他属性相关函数

2.1 阻止添加新属性 Object.preventExtensions()

这个方法可以将一个对象变成一个不可扩展的对象,即,不可添加新属性(但可以删除)。

javascript 复制代码
"use strict"
const obj = {}
console.log(Object.isExtensible(obj)) // true
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj)) // false

obj.a = 'Hello world' // TypeError: Cannot add property a, object is not extensible

虽然无法在对象本身添加属性,但可以在原型对象上添加属性,实现看起来像添加了新属性的效果。

javascript 复制代码
"use strict"
const proto = {}
const obj = Object.create(proto)
Object.preventExtensions(obj)
proto.a = 'Hello world'
console.log(obj.a) // Hello world

虽然不可扩展对象可以修改原型对象的内容,但不可以更改指向原型对象的引用。

javascript 复制代码
"use strict"
const proto = {}
const obj = {}
Object.preventExtensions(obj)
Object.setPrototypeOf(obj, proto) // TypeError: #<Object> is not extensible

2.2 Object.seal()

Object.seal()的行为:

  1. 将对象变标记为不可扩展(Object.isExtensible()返回false
  2. 将所有属性的属性描述符configurable修改为false
javascript 复制代码
"use strict"
const obj = {}
Object.defineProperty(obj, 'a', { value: 'Hello World' })

console.log(Object.isExtensible(obj)) // true
console.log(Object.issealed(obj)) // false

Object.seal(obj)

console.log(Object.isExtensible(obj)) // false
console.log(Object.issealed(obj)) // true

Object.defineProperty(obj, 'a', { writable: true }) // TypeError: Cannot redefine property: a

2.3 Object.freeze()

Object.freeze()的行为:

  1. Object.seal()
  2. 将所有数据属性的writable设置为false
javascript 复制代码
"use strict"
const obj = {}
Object.defineProperty(obj, 'a', { value: 'Hello World', writable: true })

console.log(Object.isExtensible(obj)) // true
console.log(Object.isSealed(obj)) // false
console.log(Object.isFrozen(obj)) // false

Object.freeze(obj)

console.log(Object.isExtensible(obj)) // false
console.log(Object.isSealed(obj)) // true
console.log(Object.isFrozen(obj)) // true

obj.a = 'Hello JavaScript'
相关推荐
小曲曲35 分钟前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS3 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架