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

注意:实践中的出版物中的信息应非常谨慎地应用,并应在经验丰富的同事的监督下进行。
在这里,我们讨论隐藏在对象深处的内容,并讨论使用它们的复杂性。
掌握了这些材料之后,您将知道以下问题的答案:
- 如何使对象的属性不可删除?
- 访问方法的属性是什么,它们的功能是什么?
- 如何使属性不变或隐藏?
- 为什么某些属性在
for-in
循环中或Object.keys()
方法的结果中不可见,而某些是可见的? - 如何“保护”对象免受修改?
- 如何理解类似于以下内容的一段代码:
obj.id = 5; console.log(obj.id)
对象属性的类型
▍数据存储属性
您可能创建了无数类似的对象:
const obj = { name: 'Arfat', id: 5 } obj.name
obj
对象的
name
和
id
属性称为数据
obj
属性或“数据属性”。 这些是在JavaScript代码中经常发现的熟悉的属性。 对象可以具有哪些其他类型的属性?
access具有访问方式的属性
这些属性也称为getter和setter;它们还可以在其他编程语言(例如C#或Python)中找到。 具有Accessor属性的属性是两个函数的组合
get
和
set
。
声明此类属性时,使用以下语法代替使用传统的
:
类型构造:
const accessorObj = { get name() { return 'Arfat'; } }; accessorObj.name;
看一下
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);
顺便说一下,该程序包含对本文开头给出的一个问题的答案,该问题涉及乍一看对代码难以理解的分析。
如果可以安全地使用普通属性,为什么有人需要使用访问方法的属性? 例如,可能需要它们来记录有关属性读取的信息,或存储属性值的更改历史记录。 具有访问方法的属性为我们提供了使用函数处理数据的所有可能性,以及使用普通属性的简单性。
在此处阅读有关使用此类属性的更多信息。
JavaScript如何区分使用访问方法存储数据的普通属性和属性? 找出这个。
对象属性描述符
乍看起来,键和存储在对象中的值之间似乎存在直接对应关系。 但是,这并非完全正确。
▍属性属性
对象的每个键与一组属性关联,这些属性确定与此键关联的值的特征。 这些属性也可以视为描述
:
对的元数据。
属性用于设置和描述对象属性的状态。 属性属性集称为描述符。 有六个属性属性:
[[Value]]
[[Get]]
[[Set]]
[[Writable]]
[[Enumerable]]
[[Configurable]]
为什么此列表中的属性属性名称包含在
[[]]
构造中? 双括号表示这些是语言内部机制所使用的实体。 JS程序员无法直接访问这些属性。 为了影响它们,使用了适当的方法。
考虑以下图像,该图像是
从此处获取的 ,在该图像上您可以看到该对象及其属性的属性。
对象及其属性的属性我们的对象有2个键
x
和
y
。 此外,一组属性与每个属性相关联。
与上图所示类似,如何使用JavaScript获取有关对象的信息? 您可以为此使用
Object.getOwnPropertyDescriptor()
函数。 它接受一个对象及其属性的名称,然后返回一个包含此属性的属性的对象。 这是一个例子:
const object = { x: 5, y: 6 }; Object.getOwnPropertyDescriptor(object, 'x');
应该注意的是,特定属性的属性组成取决于其类型。 找不到同一属性的所有六个属性。
- 如果我们谈论的是数据属性,那么它们将仅具有
[[Value]]
, [[Writable]]
, [[Enumerable]]
和[[Configurable]]
属性。 - 具有访问方法的属性具有
[[Get]]
和[[Set]]
属性,而不是[[Value]]
和[[Writable]]
[[Set]]
属性。
▍[[值]]
此属性存储尝试获取对象的属性值时返回的内容。 也就是说,如果在前面的示例中使用了
object.x
表单的
object.x
,我们将得到存储在
[[Value]]
属性中的内容。 当您尝试使用方括号读取对象的属性时,也会发生同样的事情。
▍[[获取]]
此属性存储对函数的引用,该函数是getter属性。 尝试读取属性值时,不带参数调用此函数。
▍[[设定]]
这是在创建setter属性时声明的函数的链接的存储位置。 通过一个参数表示该参数,该参数表示他们尝试分配给该属性的值,也就是说,在为属性分配新值的每个操作过程中都会调用该参数。
const obj = { set x(val) { console.log(val)
在此示例中,表达式的右侧作为
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 = {};
它可以在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);
若要确定对象是否不可扩展,可以使用
Object.isExtensible()
方法。 如果返回
true
,则可以向对象添加新属性。
▍方法Object.seal()
seal()
方法似乎可以“密封”对象。 这是我们正在谈论的:
- 它的使用可防止将新属性添加到对象(在这种情况下,它类似于
Object.preventExtensions()
)。 - 它使对象的所有现有属性不可配置。
- 如果现有属性的
[[Writable]]
属性未设置为false
,则可以对其进行更改。
结果,事实证明,此方法可防止向对象添加新属性以及防止删除对象中现有的属性。
考虑一个例子:
const obj = { id: 42 }; Object.seal(obj); delete obj.id
若要检查对象是否被“密封”,可以使用
Object.isSealed()
方法。
▍方法Object.freeze()
freeze()
方法使您可以“冻结”对象,并在JavaScript中为它们提供尽可能高的保护级别。 运作方式如下:
- 使用
Object.seal()
密封对象。 - 完全禁止修改对象的任何现有属性。
- 禁止修改属性描述符。
这是一个例子:
const obj = { id: 42 }; Object.freeze(obj); delete obj.id
您可以使用
Object.isFrozen()
方法检查对象是否“冻结”。
▍用于保护对象的方法概述
重要的是要注意,上述用于保护对象的方法只会影响不是对象的属性。
这是有关保护对象的方法的摘要表,摘自
此处 。
| 财产创造
| 物业阅读
| 属性覆盖
| 财产转移
|
Object.freeze()
| -- | +
| -- | -- |
Object.seal()
| -- | +
| +
| -- |
Object.preventExtensions()
| -- | +
| +
| +
|
总结
考虑到对象在JavaScript中的使用频率,对于每个开发人员来说,了解对象的排列方式都很重要。 我们希望您通过阅读本材料中学到的知识对您有所帮助。 此外,现在您知道了本文开头列出的问题的答案。
亲爱的读者们! 您如何保护JavaScript对象?