在了解什么是Object.defineProperty
前,我们先回忆下我们平常经常使用的 对象
1.对象的赋值
我们平常一般使用obj.prop=value或者obj['prop']=value
对对象进行赋值或修改,如
let test={}
test.a=1
test["b"]=2
console.log(test.a) //1
console.log(test.b) //2
那么Object.defineProperty又和上面的对象赋值是什么关系呢?从字面(defineProperty)意思上就可以知道--定义属性,所以Object.defineProperty也是一种对对象属性修改或赋值的方式,只不过我们可以进行更精确的控制.
2.Object.defineProperty的定义:
Object.defineProperty()
的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,语法如下,有三个参数:
Object.defineProperty(obj, prop, descriptor)
- obj:需要定义或修改属性的对象(必填)
- prop:需要定义的属性名称(必填)
- descriptor:要定义或修改的属性描述符,是一个对象.(必填)
看到这,可能有的人不理解什么是属性描述符号?我们上面说的 对对象属性进行更精确的控制,就是通过descriptor来操控的.descriptor是一个对象,具有以下属性:
属性名 | 描述 | 默认值 |
---|---|---|
value | 要定义的属性对应的值 | undefined |
writable | 要定义的属性值是否可以被改变 | false |
configurable | 要定义的属性是否配置(配置指的是重新定义),以及可否删除 | false |
enumerable | 要定义的属性是否会出现在对象的枚举属性中,如for in 或者 Object.keys()的遍历中 | false |
get | 要定义的属性的getter函数,当访问该属性时,会执行此函数 | undefined |
set | 要定义的属性的setter函数,当属性值被修改时,会调用此函数,此时该方法接受一个参数(也就是修改的新值) | undefined |
注意:这里说的要定义的属性指的是通过Object.defineProperty传递prop参数进行定义的属性(才可以控制是否删除、改变、监听等),如果对象本来就存在一些属性,但没有经过Object.defineProperty进行重新定义,是不受控制的,和我们最开始讲到的我们平常使用对象的方式,可以随便改变、删除,不能监听
下面我们将分别对这些属性描述符进行举例说明:
1.value
我们要在对象上定义属性,obj、prop必填就不用说了,descriptor也是必填的,否则会报错
let test={}
Object.defineProperty(test, "a")
console.log(test);
从报错信息我们可知,属性描述符必须是个对象,所以我们修改下就可以了
Object.defineProperty(test, "a",{})
console.log(test); // {a:undefined}
test.a=1
console.log(`修改后${test}`) // {a:undefined}
此时我们已经使用Object.defineProperty成功定义了一个属性,只不过没有值罢了.
但我们修改,打印出来发现值仍为undefined.这个后面会说,接下来我们先进行使用 value进行赋值操作.
Object.defineProperty(test, "a",{
value:1
})
console.log(test); // {a:1}
test.a=2
console.log(`修改后${test}`) // {a:1}
2.writable
我们可以看见,test对象已经有一个属性a,并且值为1.但是和上面一样,我们仍没有修改成功.接下来,我们使用 writable进行修改
Object.defineProperty(test, "a",{
value:1,
writable:true
})
console.log(test); // {a:1}
test.a=2
console.log(`修改后${test}`) // {a:2}
3.configurable
我们可以看见,属性值已经被修改成功.接下来我们来看 configurable属性.
Object.defineProperty(test, "a",{
value:1,
writable:true
})
delete test.a
console.log(test) //{a:1}
我们可以看见,此时我们无法删除掉属性a,我们修改代码,此时即可删除成功.
Object.defineProperty(test, "a",{
value:1,
writable:true,
configurable:true
})
delete test.a
console.log(test) //{}
我们上面说了configurable除下控制是否可以删除,还可以用来控制是否可以重新定义.我们来修改代码
Object.defineProperty(test, "a",{
value:1,
})
//重新定义
Object.defineProperty(test, "a",{
value:2,
})
发现报错,不能重新定义:
我们增加writable=true
,即可重新定义
Object.defineProperty(test, "a",{
value:1,
writable:true,
})
//重新定义
Object.defineProperty(test, "a",{
value:2,
})
console.log(test) //{a:2}
或者设置configurable=true
Object.defineProperty(test, "a",{
value:1,
writable:false, //也可以不写,默认false
configurable:true
})
//重新定义
Object.defineProperty(test, "a",{
value:2,
})
console.log(test) //{a:2}
4.enumerable
1.Object.keys,返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性).
Object.defineProperty(test, "a",{
value:1,
})
console.log(Object.keys(test)); //[]
2.for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
for(let i in test){
console.log(i);
} //不会输出,因为无法遍历,此时test相当于{}对象
3.Object.values(),返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)的值
console.log(Object.values(test)); //[]
4.Object.entries(),返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)以及对应值组成一个数组
console.log(Object.entries(test)); //[]
5.Object.getOwnPropertyNames(),返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性).
console.log(Object.getOwnPropertyNames(test)); //["a"]
6.Reflect.ownKeys(),返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举.
console.log(Reflect.ownKeys(test));["a"]
我们可以看见,前3种无法获取对象的属性,我们设置enumerable=true
,即可获取,代码就不贴了.
5.get
Object.defineProperty(test, "a",{
// value:1, //get和value不能同时使用
get(){
return 1
}
})
console.log(test.a) //1
console.log(test) //{}
我们从打印结果可以看出
- 当我们打印test.a时候,get函数会执行,此时a的值就是return的值;但我们直接打印test是不会执行get函数的.
- 如果我们把value注释打开,会发现报错
- get、set函数和value不能同时使用,都会报如下错误
6.set
Object.defineProperty(test, "a",{
get(){
return 1
},
set(newValue){
console.log(newValue) //2
}
})
console.log(test.a) //1
test.a=2
console.log(test.a) //1
上面例子中,我们对属性a重新赋值为2,set函数可以接收到最新的值为2,但是get函数返回永远是1,所以test.a的值永远是1,我们更改下代码:
let num=1
Object.defineProperty(test, "a",{
get(){
return num
},
set(newValue){
console.log(newValue) //2
num=newValue
}
})
console.log(test.a) //1
test.a=2
console.log(test.a) //2
3. 不同写法对比
let test={};
test.a=1
等价于
Object.defineProperty(test, "a",{
value:1,
configurable:true,
writable:true,
enumerable:true
})
Object.defineProperty(test, "a",{
value:1,
})
等价于
Object.defineProperty(test, "a",{
value:1,
configurable:false,
writable:false,
enumerable:false
})
4.拓展
1.创建对象常量
结合writable: false 和 configurable: false 就可以创建一个真正的常量属性(不可修改,不可重新定义或者删除),但可以添加新属性
Object.defineProperty(test, "a",{
value:1,
configurable:false,
writable:false
})
delete test.a;
test.a=2
console.log(test); //{a:1}
test.b=2
console.log(test); //{a:1,b:2}
Object.defineProperty(test, "a",{
value:2,
}) //报错 Cannot redefine property
2.禁止扩展 Object.preventExtensions
如果你想禁止一个对象添加新属性并且保留已有属性,就可以使用Object.preventExtensions()
let test={a:1}
Object.preventExtensions(test)
test.b=2
test.a=2
console.log(test); //{a:2}
Object.defineProperty(test, "a",{
value:3,
})
console.log(test); //{a:3}
Object.defineProperty(test, "b",{
value:2,
}) //报错:Uncaught TypeError: Cannot define property b, object is not extensible
我们可以发现,通过preventExtensions,我们无法再新增(拓展)属性,只能更改原来存在的属性.
3.密封对象 Object.seal
Object.seal()方法用于密封一个对象,这个方法实际上会在一个现有对象上调用object.preventExtensions()并把所有现有属性标记为configurable:false.即将对象设置为不可扩展,同时将对象的所有自有属性都设置为不可配置(包括Symbol值的属性)。也就是说,不能给对象添加新的属性和方法,也不能删除现有的属性和方法、不能修改现有属性和方法的配置。但如果对象的属性和方法是可写的,那该属性和方法仍然可以修改。
let test={a:1}
Object.seal(test)
test.a=2
test.b=3
console.log(test); //{a:2}
delete test.a
console.log(test); //{a:2}
Object.defineProperty(test, "a",{
value:3,
})
console.log(test); //{a:3}
Object.defineProperty(test, "b",{
value:3,
}) //报错:Uncaught TypeError: Cannot define property b, object is not extensible
4.冻结对象 Object.freeze
Object.freeze()会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal(),并把所有现有属性标记为writable: false,这样就无法修改它们的值。
let test={a:1}
Object.freeze(test)
test.a=2
test.b=3
console.log(test); //{a:1}
delete test.a
console.log(test); //{a:1}
Object.defineProperty(test, "b",{
value:3,
})
console.log(test); //报错:Uncaught TypeError: Cannot define property b, object is not extensible