想到Object.defineProperty
,首先不得不提到对象,对象是JavaScript
的基础,有一种常见的说法“JavaScript中万物皆是对象”。javascript
这种说法其实并不那么准确,根据 JavaScript 对语言类型的分类,就能够得出JavaScript
并非万物皆对象,此次就不对这个问题进行展开,感兴趣能够点击JavaScript 万物皆对象🤔。可是足以证实对象对于JavaScript
这门语言的重要性。java
Object.defineProperty
这个方法最经常使用的场景应该是在面试的时候,每当面试官问起Vue双向绑定的原理,不少朋友可能破口而出就是这个方法,好了不开玩笑了,进入正题。面试
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。数组该方法容许精确添加或修改对象的属性。经过赋值操做添加的普通属性是可枚举的,可以在属性枚举期间呈现出来(
for...in
或Object.keys
方法),这些属性的值能够被改变,也能够被删除。这个方法容许修改默认的额外选项(或配置)。默认状况下,使用Object.defineProperty()
添加的属性值是不可修改的函数以上是MDN对于这个方法的描述post
在ES5以前,JavaScript
语言自己并无提供能够直接检测属性特性的方法,可是从ES5开始,全部的属性都具有了属性描述符,有两种主要形式:ui
描述符必须是这两种形式之一,不能同时是二者,为何后面会具体讨论。this
writable
决定是否能够修改属性的值。spa
let myObject = {}
Object.defineProperty(myObject, 'a', {
value: 2,
writable: false, // 不可写
configurable: true,
enumerable: true
})
myObject.a = 3
console.log(myObject.a) // 2
复制代码
若是在严格模式下,会抛出TypeError
错误表示咱们没法修改一个不可写的属性。双向绑定
Configurable
决定对象属性是否可配置,只要属性是可配置的,就可使用defineProperty()
方法来修改属性描述符:
let myObject = {
a: 2
}
myObject.a = 3
console.log(myObject.a) // 3
Object.defineProperty(myObject, 'a', {
value: 4,
writable: true,
configurable: false, // 不可配置
enumerable: true
})
console.log(myObject.a) // 4
myObject.a = 5
console.log(myObject.a) // 5
Object.defineProperty(myObject, 'a', {
value: 4,
writable: true,
configurable: true, // 修改成可配置
enumerable: true
}) // TypeError
复制代码
不论是不是处于严格模式,尝试修改一个不可配置的描述符都会抛出错误,如你所见,把configurable
修改为false
是一个单向操做,没法撤销!
有一个小小的例外,即便configurable: false
,咱们仍是能够把writable
的状态由true
改成false
,可是没法由false
改成true
。
除了没法修改,configurable: false
还会禁止删除这个属性:
let myObject = {
a: 2
}
console.log(myObject.a) // 2
delete myObject.a
console.log(myObject.a) // undefined
Object.defineProperty(myObject, 'a', {
value: 2,
writable: true,
configurable: false, // 不可配置
enumerable: true
})
console.log(myObject.a) // 2
delete myObject.a
console.log(myObject.a) // 2
复制代码
最后一个delete
语句失败了,由于属性不可配置。
从名字就能够看出,这个描述符控制的是属性是否出如今对象的属性枚举中,好比for...in
循环。
let myObject = {
a: 2
}
Object.defineProperty(myObject, 'a', {
value: 2,
enumerable: true // 可枚举
})
Object.defineProperty(myObject, 'b', {
value: 2,
enumerable: false // 不可枚举
})
console.log(myObject.b) // 2
console.log('b' in myObject) // true
console.log(myObject.hasOwnProperty('b')) // true
for (var k in myObject) {
console.log(k, myObject[k])
} // 'a' 2
复制代码
能够看到,myObject.b
确实存在而且有访问值,可是却不会for...in
循环中,尽管它确实存在于myObject
对象中。
in
和hasOwnProperty
的区别在因而否查找[[Prototype]]
链,in会沿着原型链往上查找
再看一个实例:
let myObject = {
a: 2
}
Object.defineProperty(myObject, 'a', {
value: 2,
enumerable: true // 可枚举
})
Object.defineProperty(myObject, 'b', {
value: 2,
enumerable: false // 不可枚举
})
console.log(myObject.propertyIsEnumerable('a')) // true
console.log(myObject.propertyIsEnumerable('b')) // false
console.log(Object.keys(myObject)) // ['a']
console.log(Object.getOwnPropertyNames(myObject)) // ['a', 'b']
复制代码
propertyIsEnumerable()
会检查给定的属性名是否直接存在于对象中,而且知足enumerable: true
。
Object.keys()
会返回一个数组,包含全部的可枚举属性。Object.getOwnPropertyNames()
也会返回一个数组,包含全部属性,不管它们是否可枚举,这两个方法都只会查找对象直接包含的属性。
getter
和setter
能够改写默认操做,可是只能应用在单个属性上,没法应用在整个对象,getter
和setter
都是隐藏函数,getter
会在获取属性值时调用,setter
会在设置属性值时调用。好比Vue就会给全部的属性添加上getter
和setter
函数。
当你给一个属性定义getter
、setter
或者二者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。
对于访问描述符来讲,JavaScript
会忽略它们的value
和writable
特性,取而代之的是关心set
和get
(还有configurable
和enumerable
)特性。这就是为何数据描述符和访问描述符只会存在一个的缘由。
let myObject = {
get a() {
return 2
}
}
Object.defineProperty(myObject, 'b', {
get: function () {
return this.a * 2
}
})
console.log(myObject.a) // 2
console.log(myObject.b) // 4
复制代码
我经过两种方式建立了两个不包含值的属性,可是在访问这两个属性的时候,它们都会自动调用一个隐藏的get
函数,函数的返回值会被看成属性访问的返回值。
let myObject = {
get a() {
return 2
}
}
myObject.a = 3
console.log(myObject.a) // 2
复制代码
因为定义了a
属性的getter
,因此对a
的值进行设置时set
操做会忽略赋值操做。并且即使有合法的setter
,因为咱们自定义的getter
只会返回2,因此set
操做时没有意义的。
一般,getter
和setter
时成对出现的(只定义一个的话一般会产生意料以外的行为):
let myObject = {
get a() {
return this._a_
}, // 给a定义一个getter
set a(val) {
this._a_ = val * 2
} // 给a定义一个setter
}
myObject.a = 2
console.log(myObject.a) // 4
复制代码
当对目标对象的属性设置了writable:false
,至关于你定义了一个空操做setter
,你的全部set
操做都会被忽略。
有时候你会但愿属性或者对象是不可改变的,在ES5中有不少方法能够实现。很重要的一点,全部的方法建立的都是浅不可变性,也就是说,它们只会影响目标对象和它的直接属性,若是目标对象引用了其余对象,其余对象的内容仍然是可变的。
结合writable:false
和configurable:false
就能够建立一个真正的常量属性,不可修改、从新定义或者删除。
若是你但愿获得一个对象,它禁止添加新属性而且保留已有属性,可使用Object.preventExtensions()
:
let myObject = {
a: 2
}
Object.preventExtensions(myObject)
myObject.b = 3
console.log(myObject.b) // undefined
复制代码
Object.seal()
会建立一个“密封”的对象,这个方法实际上会在一个现有对象上调用Object.preventExtensions()
并把全部现有属性标记为configurable:false
。
密封以后不只不能添加新属性,也不能从新配置或者删除任何现有属性,可是能够修改属性的值。
Object.freeze()
会建立一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal()
并把全部“数据访问”属性标记为writable:false
,这样就不可修改它们的值。
这个方法是能够应用在对象上的级别最高的不可变性,它会禁止对于对象自己及其任意直接属性的修改。
以上的几个方法都要慎用,颇有可能带来意想不到的行为。
文中若是有问题和遗漏,欢迎在评论区指出,若是本文能给帮助到你,请给个点赞
👍和关注
本文到此结束,886🚀🚀