Object
撰写时间:2024-02-11
修订时间:2024-12-01
概述
Object所定义的方法,往往用在prototype链路中,因此只有在涉及到prototype链路的环境中,才能深切体会其各个方法的确切含义。
本章第一部分先从具体用例出发,根据具体需求来调用、讲解其相应方法;第二部分再以参考的形式来列出其全部方法。
遍历对象属性
对象属性分类
JavaScript通过prototype链来访问、获取对象属性。这种方式,类似于其他面向对象编程语言中的类的继承的关系。例如:
obj只有一个foo属性。但我们可以调用:
将显示:
toString方法是由根对象Object通过其prototype属性来自动传递给obj的,因此,每个对象都可以调用此方法。
对象的属性,可分为两种类型:是否自身的属性(own property),以及是否可列举的属性(enumerable)。
上面的例子较好的说明了foo属性是obj自身的属性;而toString不是obj自身的属性,而是prototype链中存在的属性。
可以调用obj的hasOwnProperty方法来了解特定属性是否其自身属性:
一个对象的prototype链可能有多级层级,如果只是简单地一概列举prototype链中的所有属性,则会产生较长的列表。因此,如果将某些属性设为不可列举的属性,则当我们调用相应的列举属性的方法时,这些不可列举的属性将不会出现在结果中。而Object的prototype所提供的各个属性,属于不可列举的属性。这是对是否可以列举进行分类的意义所在。可通过调用propertyIsEnumerable方法查看特定属性是否可以列举:
尽管从方法名称上看不出来,但propertyIsEnumerable方法只有在特定属性同时满足2个条件时,才会返回true:1. 为自身属性;2. 为可列举属性。
有不同的遍历方法,其中一些方法只能遍历自身属性,一些方法只能遍历可列举属性。下面会分类详细说明这些列举方法。
而不管属性是否自身属性,或是否可列举属性,均可通过.操作符或[]操作符来直接访问这些属性。
遍历自身属性
Object.keys方法
设有以下代码:
其prototype链路示意图如下:
- obj
- name
- getName
- [[Prototype]]: MyObj
- greeting
即,name及getName这两个属性属于obj的自身属性,greeting属于其构造器MyObj的prototype中的属性。
则可通过调用Object的静态方法keys,来返回对象自身的、可列举的属性。
greeting方法虽然也属于可列举属性,但它不属于obj的自身属性,因此不会出现在结果集合中。
Object的静态方法entries的筛选标准与keys一样,但返回结果中同时包含了属性名以及属性值。
Object.getOwnPropertyNames方法
Object的静态方法getOwnPropertyNames返回对象的自身的所有属性,包括不可列举的属性。
仍取上面的例子,代码:
返回:
虽然这个例子说明了getOwnPropertyNames确实返回了自身属性,但不足以说明其对不可列举属性的支持情况。
我们取JavScript引擎自带的不可列举属性为例。Object.prototype属性值为一个对象,该对象有众多供子对象继承的属性。
在Safari中,上面的代码将显示:
一个什么都没有的空对象。
但若使用:
则终端显示为:
- object
- __defineGettor__: function()
- __defineSettor__: function()
- __lookupGetter__: function()
- __lookupSetter__: function()
- constructor: function()
- hasOwnProperty: function()
- isPrototypeOf: function()
- propertyIsEnumerable: function()
- toLocaleString: function()
- toString: function()
- valueOf: function()
没错,这些都是每个对象都会自动获得继承的方法,包括常用的toString方法。
但这些方法都是不可列举的。使用代码:
终端显示:
- object
- configurable: true
- enumerable: false
- value: function()
- writable: true
上面的enumerable属性值显示,toString方法是不可列举的。
我们也可以使用getOwnPropertyDescriptors一下子全部列出特定对象所有属性的情况:
Safari的理解是,既然这些属性都是不可列举的,因此对它们调用console.log
就返回一个空对象;只有对它们调用console.dir
,才会列出这些不可列举的属性。
而在Chrome中,对不可列举对象调用console.log
及console.dir
的效果一样,都会列出这些不可列举的属性。并且,Chrome所列出的属性中还多出以下属性:
- object
- ...
- __proto__: null
- get __proto__: __proto__()
- set __proto__: __proto__()
__proto__属性引用了prototype链路中父级的prototype对象,这里其值为null,说明prototype链路到此终止。
__proto__属性是一个getter
及setter
,对其调用getOwnPropertyDescriptors:
则显示:
- object
- configurable: true
- enumerable: false
- get: function()
- set: function()
即,属于访问器的属性不再有value属性,而是多了get属性及set属性,类型均为函数。因此在遍历属性值时需注意此点。
因为是不可列举的对象,如果我们使用for...in
语句来遍历:
则不管是Safari,还是Chrome,终端都不会显示任何内容。(for...in
语句详见下节)
现在,Object.getOwnPropertyNames就派上用场了:
终端显示:
- Array (12)
- " 0 toString"
- " 1 toLocaleString"
- " 2 valueOf"
- " 3 hasOwnProperty"
- " 4 propertyIsEnumerable"
- " 5 isPrototypeOf"
- " 6 __defineGettor__"
- " 7 __defineSettor__"
- " 8 __lookupGetter__"
- " 9 __lookupSetter__"
- "10 __proto__"
- "11 constructor"
此时,在Safari中,__proto__属性也出现了。
精准控制遍历流程
由于Object.getOwnPropertyNames返回自身所有属性名,我们往往需要精准控制整个流程。如,是否需要将不可列举的属性加入到结果集中,判断属性值类型,区别对待访问器属性等等。
上面,实例变量_age为不可列举的变量,我们为它定义了getter
及setter
访问器。
这样定义的好处是,使用for...in
语句将自动屏蔽已另行定义访问器的变量_age:
下面代码在遍历所有属性的过程中,对不可列举的属性、属性值为函数、属性值为访问器等情况均予以了区别对待。
终端显示:
通过调用Object.getOwnPropertyDescriptor方法,我们取得了每个属性的细节。
对于不可列举的属性,在输出时,在属性名后面加上non-enumerable
的标注。
如果有value属性,对于类型为非函数的属性,可直接打印其属性值;对于类型为函数的属性,则不打印其值,而是直接标注function
。
注意,判断是否存在value属性时,不要使用if (!propertyDescriptor.value)
,否则,当其值为0或false或""时,将出现误判。具体详见Falsy及Truthy。因此上面的代码调用了hasOwnProperty方法。
而对于访问器属性,如上所述,它没有value属性,而只有get属性及set属性。因此,也直接标注为get(), set()
。
遍历prototype链中所有属性
for...in
语句用于遍历对象的所有可遍历的属性名称,包括prototype链中的可遍历的属性名称。
所列出的属性名中,不仅有obj自有的name及getName属性名,还包括其prototype链中的greeting函数名。
- obj
- name
- getName
- [[Prototype]]: MyObj
- greeting
Object的prototype的toString等众多方法,虽也处在prototype链中,但由于它们属于不可列举的属性,因此不会被遍历。
如果我们希望在for...in
语句中只提取对象自身属性,可通过hasOwnProperty方法来筛选。
obj的hasOwnProperty方法,也可通过调用Object的静态方法来实现:
方法
entries
将一个对象的所有自有属性都打包为一个二维数组。
最里层的每一个数组由每个属性的属性名称与属性值构成,最外层的数组由最里层的数组组成。
语法
- Objectobj
- obj
- 包含属性及方法的对象。
例子
显示:
而对于嵌套的属性值,只提取第一级的属性名称,后续的属性名值均作为属性值:
显示:
因为数组也是Object的实例,因此entries方法也可用于数组:
显示:
数值为数组索引值的字符串将成为属性名称。
entries方法所返回的值,最外层是一个数组,因此可以使用for ... of
语句来遍历;最里层是一个只有2个元素、且元素数量固定的数组,因此可使用数组解包语句[key, value]
来直接解包。因此可以方便地编写如下代码:
参见
keys
将一个对象的所有自有属性的属性名称都打包为一个数组。
语法
- Objectobj
- obj
- 包含属性及方法的对象。
例子
显示:
应用
获取属性数量
当我们想知道一个对象有多少属性名称,不用keys方法时:
使用keys方法:
参见
values
将一个对象的所有自有属性的属性值都打包为一个数组。
语法
- Objectobj
- obj
- 包含属性及方法的对象。
例子
显示: