js对象的那些事...可扩展特性(extensible)和对象的属性的四个特性(value、writable、enumerable、configurable)
对象相信大家已经非常熟悉了,作为javascript中最最基本的类型,今天我们来谈谈其中奥秘!
let obj = {a:0,b:1} console.log(obj.a) // 0 console.log(obj['b']) // 1 obj.a = 2 obj['b'] = 2 console.log(obj) // {a:2,b:2} obj.c = 2 obj['d'] = 2 console.log(obj) // {a:2,b:2,c:2,d:2}
这里用对象字面量的方式创建了一个简单的对象,我们可以通过对象调用属性的方式(对象名.属性名)或者数组下标的方式(对象名['属性名'])来获取对象或者修改对象的属性值、和添加新的属性。那有没有可能这些操作会被受到限制呢?这主要和对象的一些特性有关
对象的特性
可扩展性(extensible):是否可以给对象添加新属性(是对象的特性,不属于对象属性的特性)
对象属性的特性
属性值(value):该属性的值;
可写(writable):是否可以修改该属性的值;
可枚举(enumerable):是否可以通过for/in循环或者Obeject.keys()方法枚举该属性;
可配置(configurable):是否可以删除该属性、是否可以修改该属性的特性;
默认情况下我们所用代码所创建的对象,其的属性都是可写、可枚举、可配置的,所以我们才可以实现上述代码的操作。我们还可以通过一些操作对对象的属性进行一些限制。
对象属性特性的相关操作
Object.getOwnPropertyDescriptor() 查询自有属性的特性
Object.defineProperty() 设置属性的特性(如果没有该属性则创建)
Object.defineProperties() 创建或者修改多个属性的特性
let obj = {a:0,b:1} console.log(Object.getOwnPropertyDescriptor(obj,'a')) // { value: 0, writable: true, enumerable: true, configurable: true }
可以看到我们通过Object.getOwnPropertyDescriptor查看obj对象的'a'属性的特性是,值为0、可写、可枚举、可配置
let obj = {a:0,b:1} Object.defineProperty(obj,'a',{ value: 1 }) console.log(obj) // { a: 1, b: 1 } Object.defineProperty(obj,'a',{ writable: false }) obj.a = 100 obj.b = 100 console.log(obj) // { a: 1, b: 100 }
可以看到我们通过Object.defineProperty将obj的'a'属性的value特性修改为1,可以起到修改属性值的效果;设置'a'属性特性为writable:false使得'a'属性不可写,我们就不能通过obj.a的方式或者obj['a']的方式修改a属性的值(不报错但修改失败)。但我们可以在不可写的情况下修改'a'属性的value特性来实现修改'a'属性的值
let obj = {a:0,b:1} Object.defineProperty(obj,'a',{ writable: false }) obj.a = 100 obj.b = 100 console.log(obj) // { a: 1, b: 100 } Object.defineProperty(obj,'a',{ value: 100 }) console.log(obj) // { a: 100, b: 100 }
我们修改属性的enumerable特性使属性不可枚举
let obj = {a:0,b:1} for(let prop in obj){ console.log(prop) } // a // b Object.defineProperty(obj,'a',{ enumerable: false }) for(let prop in obj){ console.log(prop,obj[prop]) } // a
不可枚举的属性无法被for/in循环检测到,就和对象继承而来的属性一样(继承的属性默认不可枚举)。
设置属性特性不可配置(configurable:false)的时后要注意,不可配置和其他特性搭配有着不同的效果
属性不可配置时,不能再将该属性该为可配置(所以不可配置是不可逆的)
let obj = {a:0,b:1} Object.defineProperty(obj,'a',{ configurable: false }) Object.defineProperty(obj,'a',{ // 抛出异常 TypeError: Cannot redefine property: a configurable: true })
属性不可配置时不能删除该属性(不报错但不成功)
let obj = {a:0,b:1} Object.defineProperty(obj,'a',{ configurable: false }) delete obj['a'] console.log(obj) // { a: 0, b: 1 }
属性不可配置时不能修改该属性的enumerable值
属性不可配置但可写时,可以修改value特性、可以设置不可写(writable从true改为false)
let obj = {a:0,b:1} Object.defineProperty(obj,'a',{ configurable: false, enumerable: true, writable: true }) Object.defineProperty(obj,'a',{ value: 100, writable: false }) console.log(obj) // { a: 100, b: 1 }
属性不可配置且不可写时,不可修改value特性,不可修改writable特性(从false该为true)
let obj = {a:0,b:1} Object.defineProperty(obj,'a',{ configurable: false, enumerable: true, writable: false }) Object.defineProperty(obj,'a',{ // 抛出异常 TypeError: Cannot redefine property: a value: 100, }) Object.defineProperty(obj,'a',{ // // 抛出异常 TypeError: Cannot redefine property: a writable: true, })
Object.defineProperties就是一次可以修改多个属性,像这样,没什么好说的。
let obj = {a:0,b:1} Object.defineProperties(obj,{ a: { value: 100}, b: { value: 200} }) console.log(obj) // { a: 100, b: 200 }
接下来讲讲对象的扩展特性(extensible),和对象的几种状态(封存、冻结)
对象不可扩展:不可给对象添加新属性、不可修改对象原型
对象被封存:不可扩展、不可配置
对象被冻结: 不可扩展、不可配置、不可写
判断对象状态的操作
Object.isExtensible(): 判断对象是否可扩展
Object.isSealed(): 判断对象是否被封存
Object.isFrozen():判断对象是否被冻结
修改对象状态的操作:(注意以下操作和设置对象属性不可配置一样都是不可逆的)
Object.preventExtensions():使对象不可扩展
Object.seal():封存对象
Object.freeze():冻结对象
// 不可扩展对象
let obj = {a:0,b:1} console.log(obj.__proto__) // [Object: null prototype] {} console.log(Object.isExtensible(obj)) // true Object.preventExtensions(obj) console.log(Object.isExtensible(obj)) // false obj.c = 1 console.log(obj) // { a: 0, b: 1 } let obj2 = {d: 100} obj.__proto__ = obj2 // 抛出异常 TypeError: #<Object> is not extensible
可以看到,对象不可扩展添加新属性不成功(严格模式抛出异常),不能修改对象原型
可以看到,对象被封存了之后,对象变得不可扩展,其所有自有属性变得不可配置
// 冻结对象 let obj = {a:0,b:1} console.log(Object.isFrozen(obj)) // false Object.freeze(obj) console.log(Object.isFrozen(obj)) // true console.log(Object.isSealed(obj)) // true console.log(Object.isExtensible(obj)) // false console.log(Object.getOwnPropertyDescriptor(obj,'a')) // { value: 0, writable: false, enumerable: true, configurable: false } console.log(Object.getOwnPropertyDescriptor(obj,'b')) // { value: 1, writable: false, enumerable: true, configurable: false }
可以看到,对象被冻结了之后,对象变得不可扩展,其所有自有属性变得不可配置、不可写;并且被冻结的对象也默认被封存(isSealed()返回true)
另外对象除了有普通的属性外还有一种叫访问器属性的东西(get、set),这种访问器属性没有value和writable特性
// 访问器属性
let obj = {a:0, get b() {return b}, set b(newValue) {b = newValue} } obj.b = 2 console.log(obj) // { a: 0, b: [Getter/Setter] } console.log(obj.b) // 2 console.log(Object.getOwnPropertyDescriptor(obj,'b')) // { // get: [Function: get b], // set: [Function: set b], // enumerable: true, // configurable: true // }
当然你也可以通过Object.defineProperty()修改访问器属性的get和set,还可以将访问器属性修改为普通属性(前提是对象该属性是可配置)
访问器属性修改为普通属性,默认value值为undefined,默认writable:false
let obj = {a:0, get b() {return b}, set b(newValue) {b = newValue} } Object.defineProperty(obj,'b',{ writable: true }) console.log(Object.getOwnPropertyDescriptor(obj,'b')) // { value: undefined,writable: true,enumerable: true,configurable: true}
let obj = {a:0, get b() {return b}, set b(newValue) {b = newValue} } Object.defineProperty(obj,'b',{ value: 100 }) console.log(Object.getOwnPropertyDescriptor(obj,'b')) // { value: 100, writable: false, enumerable: true, configurable: true }
同样的,普通属性也可以修改成访问器属性(默认的get/set为undefined)
let obj = {a:0, get b() {return b}, set b(newValue) {b = newValue} } Object.defineProperty(obj,'a',{ get() {return 2}, }) console.log(Object.getOwnPropertyDescriptor(obj,'a')) // { // get: [Function: get], // set: undefined, // enumerable: true, // configurable: true // } console.log(obj.a) // 2 obj.a = 100 console.log(obj.a) // 2
像这样只有get没有set的访问器属性相当于只读属性,不可修改该属性的值