Object.defineProperty

更新日期: 2019-08-20阅读: 1.6k标签: object

1. 前言

想到Object.defineProperty,首先不得不提到对象,对象是JavaScript的基础,有一种常见的说法“JavaScript中万物皆是对象”。

这种说法其实并不那么准确,根据 JavaScript 对语言类型的分类,就可以得出JavaScript并不是万物皆对象,这次就不对这个问题进行展开,感兴趣可以点击JavaScript 万物皆对象。但是足以证明对象对于JavaScript这门语言的重要性。

Object.defineProperty这个方法最常用的场景应该是在面试的时候,每当面试官问起vue双向绑定的原理,很多朋友可能破口而出就是这个方法,好了不开玩笑了,进入正题。

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys方法),这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的

以上是MDN对于这个方法的描述


2. 属性描述符

在ES5之前,JavaScript语言本身并没有提供可以直接检测属性特性的方法,但是从ES5开始,所有的属性都具备了属性描述符,有两种主要形式:

  • 数据描述符

    • 数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的
  • 访问描述符

    • 访问描述符是由getter-setter函数对描述的属性。

描述符必须是这两种形式之一,不能同时是两者,为什么后面会具体讨论。

2.1 Writable

writable决定是否可以修改属性的值。

let myObject = {}

Object.defineProperty(myObject, 'a', {
    value: 2,
    writable: false, // 不可写
    configurable: true,
    enumerable: true
})

myObject.a = 3

console.log(myObject.a) // 2

如果在严格模式下,会抛出TypeError错误表示我们无法修改一个不可写的属性。

2.2 Configurable

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语句失败了,因为属性不可配置。

2.3 Enumerable

从名字就可以看出,这个描述符控制的是属性是否出现在对象的属性枚举中,比如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) // 3
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()也会返回一个数组,包含所有属性,无论它们是否可枚举,这两个方法都只会查找对象直接包含的属性。

2.4 Getter和Setter

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操作都会被忽略。


3. 补充(对象的不变性)

有时候你会希望属性或者对象是不可改变的,在ES5中有很多方法可以实现。很重要的一点,所有的方法创建的都是浅不可变性,也就是说,它们只会影响目标对象和它的直接属性,如果目标对象引用了其他对象,其他对象的内容仍然是可变的。

3.1 对象常量

结合writable:false和configurable:false就可以创建一个真正的常量属性,不可修改、重新定义或者删除。

3.2 禁止扩展

如果你希望得到一个对象,它禁止添加新属性并且保留已有属性,可以使用Object.preventExtensions():

let myObject = {
  a: 2
}

Object.preventExtensions(myObject)

myObject.b = 3
console.log(myObject.b) // undefined

3.3 密封

Object.seal()会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用Object.preventExtensions()并把所有现有属性标记为configurable:false。

密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性,但是可以修改属性的值。

3.4 冻结

Object.freeze()会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal()并把所有“数据访问”属性标记为writable:false,这样就不可修改它们的值。

这个方法是可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改。

以上的几个方法都要慎用,很有可能带来意想不到的行为。

链接: https://www.fly63.com/article/detial/4859

Js中Object对象的理解,Object常用的属性和方法有哪些?

在JavaScript中的所有对象都是继承Object对象而来的, 我们可以理解为Object是所有js对象通用的功能,讲解Object的prototype属性、Object.prototype属性和方法?Object.prototype.constructor属性等

Object.create()的使用总汇:创建对象,参数说明,兼容性的实现

JavaScript中Object.create()的含义和语法,使用它创建null原型的对象 ,创建一个普通的空对象,Object.create()第二个参数说明。

Javascript中的object相等

相等是JavaScript中起初最让人困惑的部分。==和===的比较、强制类型的顺序等等,都使得这个问题变得复杂。今天,我们会聚焦另一个方面:object相等是如何实现的。

js 中 Object.defineProperty 的用法

还能通过 Object.defineProperty() 方法,添加或修改对象的属性。更重要的是,除了目标对象 obj,属性名称 prop 外,方法能传入属性描述符 descriptor,以实现更复杂的性质。属性描述符是一个对象,有两种形式:一种是数据描述符,另一种是存取描述符。

理解js中Object的defineProperty()和defineProperties()

Object的defineProperty和defineProperties这两个方法在js中的重要性十分重要,主要功能就是用来定义或修改这些内部属性,与之相对应的getOwnPropertyDescriptor和getOwnPropertyDescriptors就是获取这行内部属性的描述。

js中的Object.defineProperty()和defineProperties()

Object.defineProperty()该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,Object.defineProperties()该方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象

Object.keys方法

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。

Js中Object.freeze()方法

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。

Js Object.freeze和Object.seal

本文实例讲述了JS Object.preventExtensions(),Object.seal()与Object.freeze()用法。分享给大家供大家参考,Object.preventExtensions 只能阻止一个对象不能再添加新的自身属性,仍然可以为该对象的原型添加属性。

Object.assign实现浅拷贝的原理

什么是浅拷贝?浅拷贝就是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。浅拷贝Object.assign()是什么?主要将所有可枚举属性的值从一个或者多个数据源对象复制到目标对象,同时返回目标对象。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!