JavaScript对象详细信息

该材料的作者(我们今天将其翻译发表)说JavaScript对象包含很多东西,您在日常工作中都会使用它们,甚至您都无法怀疑它们的存在。 JavaScript中的对象很容易创建,易于使用,看起来易于理解且具有灵活性,并且许多程序员根本没有想到对象实际上并非如此简单。


注意:实践中的出版物中的信息应非常谨慎地应用,并应在经验丰富的同事的监督下进行。

在这里,我们讨论隐藏在对象深处的内容,并讨论使用它们的复杂性。
掌握了这些材料之后,您将知道以下问题的答案:

  • 如何使对象的属性不可删除?
  • 访问方法的属性是什么,它们的功能是什么?
  • 如何使属性不变或隐藏?
  • 为什么某些属性在for-in循环中或Object.keys()方法的结果中不可见,而某些是可见的?
  • 如何“保护”对象免受修改?
  • 如何理解类似于以下内容的一段代码:

 obj.id = 5; console.log(obj.id) // => '101' (5    ) 

对象属性的类型


▍数据存储属性


您可能创建了无数类似的对象:

 const obj = { name: 'Arfat', id: 5 } obj.name // => 'Arfat' 

obj对象的nameid属性称为数据obj属性或“数据属性”。 这些是在JavaScript代码中经常发现的熟悉的属性。 对象可以具有哪些其他类型的属性?

access具有访问方式的属性


这些属性也称为getter和setter;它们还可以在其他编程语言(例如C#或Python)中找到。 具有Accessor属性的属性是两个函数的组合getset

声明此类属性时,使用以下语法代替使用传统的: 类型构造:

 const accessorObj = { get name() {   return 'Arfat'; } }; accessorObj.name; // => 'Arfat' const dataObj = { name: 'Arfat', }; dataObj.name; // => 'Arfat' 

看一下accesorObj对象,并将其与dataObj对象进行比较。 显然,现在它们显示出相同的行为。 描述第一个对象时,我们使用了get关键字,然后是函数的声明。 为了访问相似的属性,尽管它由一个函数表示,但您无需在属性名称后加上括号即可调用此函数。 也就是说,类似accessorObj.name();的设计accessorObj.name(); 不正确

当您尝试访问accessorObj.name属性时,即,当您尝试读取它时,将执行相应的函数,并且返回给它的值将成为name属性的值。

get函数称为getter;它们负责获取值。 如果继续我们的示例,尝试通过运行命令accessorObj.name = 'New Person';来更改accessorObj对象的name属性的值accessorObj.name = 'New Person'; ,那么事实证明什么也不会发生。 这里的重点是设置器功能与name键没有关联。 此类函数使您可以自定义将新值分配给使用getter进行组织的对象访问属性的顺序。

这是带有getter和setter的对象声明的样子:

 const accessorObj = { _name: 'Arfat', get name() {   return this._name; }, set name(value) {   this._name = value; } }; 

setter函数接收他们试图分配给对象属性的参数。 现在,您可以在对象的属性中保存一些内容。 在这种情况下,我们将创建_name对象的“ private”属性。 这样的属性名称的第一个字符是下划线,它仅是对程序员的提示,表明该属性用于对象的内部需求。 此外,当访问name对象的属性时,我们将使用它,该访问由getter和setter来控制。

同时,在getter函数中,在返回_name属性的值之前,我们可以对其进行修改。

可能是这样的:

 const obj = { get name() {   return this._name.toUpperCase(); }, set name(value) {   this._name = value; }, get id() {   return this._id.toString(2); //        }, set id(value) {   this._id = value; } } obj.name = 'Arfat'; obj.name; // => 'ARFAT' obj.id = 5; obj.id; // => '101 

顺便说一下,该程序包含对本文开头给出的一个问题的答案,该问题涉及乍一看对代码难以理解的分析。

如果可以安全地使用普通属性,为什么有人需要使用访问方法的属性? 例如,可能需要它们来记录有关属性读取的信息,或存储属性值的更改历史记录。 具有访问方法的属性为我们提供了使用函数处理数据的所有可能性,以及使用普通属性的简单性。 在此处阅读有关使用此类属性的更多信息。

JavaScript如何区分使用访问方法存储数据的普通属性和属性? 找出这个。

对象属性描述符


乍看起来,键和存储在对象中的值之间似乎存在直接对应关系。 但是,这并非完全正确。

▍属性属性


对象的每个键与一组属性关联,这些属性确定与此键关联的值的特征。 这些属性也可以视为描述: 对的元数据。

属性用于设置和描述对象属性的状态。 属性属性集称为描述符。 有六个属性属性:

  • [[Value]]
  • [[Get]]
  • [[Set]]
  • [[Writable]]
  • [[Enumerable]]
  • [[Configurable]]

为什么此列表中的属性属性名称包含在[[]]构造中? 双括号表示这些是语言内部机制所使用的实体。 JS程序员无法直接访问这些属性。 为了影响它们,使用了适当的方法。

考虑以下图像,该图像是从此处获取的 ,在该图像上您可以看到该对象及其属性的属性。


对象及其属性的属性

我们的对象有2个键xy 。 此外,一组属性与每个属性相关联。

与上图所示类似,如何使用JavaScript获取有关对象的信息? 您可以为此使用Object.getOwnPropertyDescriptor()函数。 它接受一个对象及其属性的名称,然后返回一个包含此属性的属性的对象。 这是一个例子:

 const object = { x: 5, y: 6 }; Object.getOwnPropertyDescriptor(object, 'x'); /* { value: 5, writable: true, enumerable: true, configurable: true } */ 

应该注意的是,特定属性的属性组成取决于其类型。 找不到同一属性的所有六个属性。

  • 如果我们谈论的是数据属性,那么它们将仅具有[[Value]][[Writable]][[Enumerable]][[Configurable]]属性。
  • 具有访问方法的属性具有[[Get]][[Set]]属性,而不是[[Value]][[Writable]] [[Set]]属性。

▍[[值]]


此属性存储尝试获取对象的属性值时返回的内容。 也就是说,如果在前面的示例中使用了object.x表单的object.x ,我们将得到存储在[[Value]]属性中的内容。 当您尝试使用方括号读取对象的属性时,也会发生同样的事情。

▍[[获取]]


此属性存储对函数的引用,该函数是getter属性。 尝试读取属性值时,不带参数调用此函数。

▍[[设定]]


这是在创建setter属性时声明的函数的链接的存储位置。 通过一个参数表示该参数,该参数表示他们尝试分配给该属性的值,也就是说,在为属性分配新值的每个操作过程中都会调用该参数。

 const obj = { set x(val) {   console.log(val)   // => 23 } } obj.x = 23; 

在此示例中,表达式的右侧作为val参数传递给setter函数。 下面的代码演示了setter和getter的用法。

▍[[可写]]


此属性包含一个布尔值。 它指示属性值是否可以覆盖。 如果将false存储在此处,则更改属性值的尝试将失败。

▍[[可枚举]]


逻辑值也存储在这里。 该属性控制for-in循环中属性的发布。 如果将其设置为true ,则可以使用此类循环使用该属性。

▍[[可配置]]


此属性也由布尔值表示。 如果将false存储在其中,则会发生以下情况:

  • 该属性无法删除。
  • 您不能使用访问方法将存储数据的属性转换为属性,反之亦然。 尝试执行此类转换将不会有任何结果。
  • 不允许更改属性属性值。 也就是说,属性[[Enumerable]][[Configurable]][[Get]][[Set]]的当前值将保持不变。

将此属性设置为false的效果还取决于属性的类型。 除了上述对属性的影响之外,此属性还对它们起作用,因此:

  • 如果这是一个存储数据的属性,则[[Writable]]属性只能从true更改为false
  • [[Writable]]属性设置为false ,可以更改[[Value]]属性。 但是在[[Writable]][[Configurable]]属性设置为false ,该属性将变为不可写,不可删除和不可变。

使用描述符


现在我们已经熟悉了属性,我们将问自己如何影响属性。 JavaScript具有用于处理属性描述符的特殊功能。 让我们谈谈他们。

▍方法Object.getOwnPropertyDescriptor()


我们已经遇到了这种方法。 它采用一个对象及其属性名称,返回undefined ,或者返回带有属性描述符的对象。

▍方法Object.defineProperty()


这是一个静态的Object方法,允许您向对象添加属性或更改现有属性。 它带有三个参数-一个对象,一个属性名称和一个带有描述符的对象。 此方法返回修改后的对象。 考虑一个例子:

 const obj = {}; // #1 Object.defineProperty(obj, 'id', { value: 42 }); // #2 console.log(obj); // => { } // #3 console.log(obj.id); // => 42 // #4 Object.defineProperty(obj, 'name', { value: 'Arfat', writable: false, enumerable: true, configurable: true }); // #5 console.log(obj.name); // => 'Arfat' // #6 obj.name = 'Arfat Salman' // #7 console.log(obj.name); // => 'Arfat' // (  'Arfat Salman') Object.defineProperty(obj, 'lastName', { value: 'Salman', enumerable: false, }); console.log(Object.keys(obj)); // => [ 'name' ] // #8 delete obj.id; // #9 console.log(obj.id); // => 42 //#10 Object.defineProperties(obj, { property1: {   value: 42,   writable: true }, property2: {} }); console.log(obj.property1) // => 42 

它可以在Node.js中运行。 该代码原来很大,但是实际上很简单。 我们将分析它,重点放在// #n形式的注释上。

在片段#1我们使用defineProperty函数,向它传递obj对象, id属性名称和仅包含value属性的描述符对象,指示将42写入[[Value]]属性。 请记住,如果没有在此对象中传递[[Enumerable]][[Configurable]]类的属性的值,则默认情况下会将其设置为false 。 在这种情况下, id属性的属性[[Writable]][[Enumerable]][[Configurable]]设置为false

在标记为#2 ,我们试图在控制台中显示对象的字符串表示形式。 由于其id属性不可枚举,因此不会显示。 此外,该属性存在,通过命令#3证明其成功完成。

创建一个对象(片段#4 ),我们定义了完整的属性列表。 特别是,将[[Writable]]false

使用#5#7命令#7我们将显示name属性的值。 但是在它们之间(片段#6 ),我们试图更改此值。 此操作未更改属性的值,因为其[[Writable]]属性设置为false 。 结果,这两个命令将相同的内容输出到控制台。

命令#8试图删除id属性。 回想一下它的[[Configurable]]属性设置为false ,这意味着它不能被删除。 这由#9队证明。

片段#10显示了Object.defineProperties()函数的使用。 它的工作方式与defineProperty()函数相同,但是一次调用它就可以影响对象的多个属性,而defineProperty()仅对对象的一个​​属性起作用。

对象保护


开发人员有时需要保护对象免受外界干扰。 例如,鉴于JavaScript的灵活性,很容易错误地更改不应更改的对象的属性。 保护对象有三种主要方法。

▍方法Object.preventExtensions()


Object.preventExtensions()方法可防止对象扩展,即为其添加新属性。 它接受一个对象并使它不可扩展。 请注意,您可以从此类对象中删除属性。 考虑一个例子:

 const obj = { id: 42 }; Object.preventExtensions(obj); obj.name = 'Arfat'; console.log(obj); // => { id: 42 } 

若要确定对象是否不可扩展,可以使用Object.isExtensible()方法。 如果返回true ,则可以向对象添加新属性。

▍方法Object.seal()


seal()方法似乎可以“密封”对象。 这是我们正在谈论的:

  • 它的使用可防止将新属性添加到对象(在这种情况下,它类似于Object.preventExtensions() )。
  • 它使对象的所有现有属性不可配置。
  • 如果现有属性的[[Writable]]属性未设置为false ,则可以对其进行更改。

结果,事实证明,此方法可防止向对象添加新属性以及防止删除对象中现有的属性。

考虑一个例子:

 const obj = { id: 42 }; Object.seal(obj); delete obj.id // ( ) obj.name = 'Arfat'; // ( ) console.log(obj); // => { id: 42 } Object.isExtensible(obj); // => false Object.isSealed(obj); //=> true 

若要检查对象是否被“密封”,可以使用Object.isSealed()方法。

▍方法Object.freeze()


freeze()方法使您可以“冻结”对象,并在JavaScript中为它们提供尽可能高的保护级别。 运作方式如下:

  • 使用Object.seal()密封对象。
  • 完全禁止修改对象的任何现有属性。
  • 禁止修改属性描述符。

这是一个例子:

 const obj = { id: 42 }; Object.freeze(obj); delete obj.id // ( ) obj.name = 'Arfat'; // ( ) console.log(obj); // => { id: 42 } Object.isExtensible(obj); // => false Object.isSealed(obj); //=> true Object.isFrozen(obj); // => true 

您可以使用Object.isFrozen()方法检查对象是否“冻结”。

▍用于保护对象的方法概述


重要的是要注意,上述用于保护对象的方法只会影响不是对象的属性。

这是有关保护对象的方法的摘要表,摘自此处
财产创造
物业阅读
属性覆盖
财产转移
Object.freeze()
--+
----
Object.seal()
--+
+
--
Object.preventExtensions()
--+
+
+

总结


考虑到对象在JavaScript中的使用频率,对于每个开发人员来说,了解对象的排列方式都很重要。 我们希望您通过阅读本材料中学到的知识对您有所帮助。 此外,现在您知道了本文开头列出的问题的答案。

亲爱的读者们! 您如何保护JavaScript对象?

Source: https://habr.com/ru/post/zh-CN438794/


All Articles