5.9 作为关联数组的对象
JavaScript 的对象和 Java 的映射(Map)类似。
如果将 JavaScript 对象的属性名看作键,属性值看作值,我们会发现它与 Java 中的映射非常相似。JavaScript 的对象还具有 Java 的映射所不具备的附加功能(例如方法或原型继承等),但也可以不理会这些功能,直接将其作为映射来使用。
5.9.1 关联数组
首先对与关联数组相关的术语进行整理。将数值作为键的值的数据结构通常称为数组。数组是绝大多数程序设计语言都支持的一种基本的数据结构。
由于数组的键是连续的数值,因此可以将其看作具有顺序的值的集合。除了数值以外大多都会使用字符串作为键值。不过键的类型也可以不限于字符串,对任意类型的键与值的集合进行操作的数据结构称为关联数组。在有些语言中,关联数组也被称为映射或字典。也有根据内部实现而将其称为散列的语言。虽然用词不同,但其数据结构是相同的,使用何种称法都可以。
关联数组最主要的用途是执行通过键来读取值的操作。在其他程序设计语言,特别是一些脚本语言中,关联数组被设计为一种语言本身的功能,不过在 JavaScript 中,必须通过对象来实现关联数组。
请注意,并没有专门用于关联数组的对象,这仅仅是对对象的一种不同的用法。
关联数组的操作方式
关联数组是元素的集合,其元素为键与值的配对。关联数组的基本操作有通过键来获取值、元素的设定、元素的删除这 3 种。由于其实体是 JavaScript 的对象,所以这里的元素只不过是属性的另一种说法,而键与值分别是属性名与属性值的别称。
可以通过点运算符或中括号运算符来实现按键取值。严格地说,是将该值作为右值来使用。
对于元素的设定,可以将点运算符或是中括号运算符作为左值写入赋值表达式。
对象的删除可以通过 delete 运算符。用对象的术语来说就是删除属性。使用方法如下。
// 删除关联数组的元素的例子(属性的删除)
var hzh = { x:3, y:4 };
console.log("删除前,输出hzh关联数组的x键值:");
console.log(hzh.x);
console.log("");
console.log("如果删除成功,则返回true:");
console.log(delete hzh.x); // 也可以使用delete hzh['x']
console.log("");
console.log("删除后,输出hzh关联数组的x键值:");
console.log(hzh.x);
[Running] node "e:\HMV\Babel\hzh.js"
删除前,输出hzh关联数组的x键值:
3
如果删除成功,则返回true:
true
删除后,输出hzh关联数组的x键值:
undefined
[Done] exited with code=0 in 0.172 seconds
在 C++ 语言中也有 delete 这个关键字,不过其功能却全然不同。在 C++ 中 delete 的功能是释放所引用的对象的内存,而在 JavaScript 中 delete 只用于删除对象中的属性。用映射中的术语来说就是,仅仅从映射中删除键,使其对应的值(对于对象来说也就是属性值)与该键不再有对应关系。虽然失去了引
用的对象最终可能会因为垃圾回收机制而消失,不过这并不是 delete 运算的直接功能。
对不存在的元素进行访问得到的结果是 undefined 型。需要注意的是,这与 Java 中映射返回的 null 是不同的。由于可以显式地将值设定为 undefined 值,因此无法通过将键与 undefined 值作等值比较来实现对键是否存在的检验。
5.9.2 作为关联数组的对象的注意点
作为关联数组的对象有一些和原型继承相关的注意点。原型继承指的是一种对象继承其他对象的属性并将其作为自身的属性一样来使用的做法。
如下所示,从形式上来说,对象 obj 的属性并不是其直接属性,而是通过原型继承而得到的属性。
function huangzihan() {}
huangzihan.prototype.z = 5; // 在原型链上设定属性z
var hzh = new huangzihan(); // 属性z继承了原型
console.log(hzh.z);
[Running] node "e:\HMV\Babel\hzh.js"
5
[Done] exited with code=0 in 0.868 seconds
for in 语句将枚举通过原型继承而得到的属性。
function huangzihan() {}
huangzihan.prototype.z = 5; // 在原型链上设定属性z
var hzh = new huangzihan(); // 属性z继承了原型
console.log(hzh.z);
for (var key in hzh) {
console.log(key); // for in 语句也会被枚举通过原型继承得到的属性
}
[Running] node "e:\HMV\Babel\hzh.js"
5
z
[Done] exited with code=0 in 0.261 seconds
请注意,通过原型继承而得到的属性无法被 delete。
function huangzihan() {}
huangzihan.prototype.z = 5; // 在原型链上设定属性z
var hzh = new huangzihan();
console.log("属性z继承了原型:");
console.log(hzh.z);
console.log("");
console.log("for in 语句也会被枚举通过原型继承得到的属性:");
for (var key in hzh) {
console.log(key);
}
console.log("");
console.log("尽管没有被delete,但还是会返回true......");
console.log(delete hzh.z);
console.log("尝试把关联数组hzh的属性z输出:");
console.log(hzh.z); //无法 delete 通过原型继承而得到的属性
[Running] node "e:\HMV\Babel\hzh.js"
属性z继承了原型:
5
for in 语句也会被枚举通过原型继承得到的属性:
z
尽管没有被delete,但还是会返回true......
true
尝试把关联数组hzh的属性z输出:
5
[Done] exited with code=0 in 0.852 seconds
在将对象作为关联数组使用时,通常都会使用对象字面量来生成。不过需要注意的是,即使视图通过使用空的对象字面量以创建一个没有元素的关联数组,也仍然会从 Object 类中继承原型的属性。可以通过 in 运算对此进行检验。
var hzh = {}; // 通过空的对象字面量生成关联数组
console.log("看看是否在Object类中原型继承了属性toString:");
console.log('toString' in hzh);
[Running] node "e:\HMV\Babel\hzh.js"
看看是否在Object类中原型继承了属性toString:
true
[Done] exited with code=0 in 0.214 seconds
但是,通过 for in 语句对元素进行枚举不会有任何效果。这是由于 enumerable 属性的缘故。
var hzh = {}; // 通过空的对象字面量生成关联数组
console.log("看看是否在Object类中原型继承了属性toString:");
console.log('toString' in hzh);
for(var key in hzh) {
console.log("在key前面设置标志位");
console.log(key);
console.log("在key后面设置标志位");
}
// 没有元素会被枚举
[Running] node "e:\HMV\Babel\hzh.js"
看看是否在Object类中原型继承了属性toString:
true
[Done] exited with code=0 in 0.174 seconds
通过 in 运算符检测关联数组的键是否存在,就会发生与原型继承而来的属性相关的问题。因此,像下面这样通过 hasOwnProperty 来对其进行检测,是一种更安全的做法。
var hzh = {};
console.log(hzh.hasOwnProperty('toString')); // 由于toString不是直接属性,因此结果是 false
console.log("");
hzh['toString'] = 1;
console.log(hzh.hasOwnProperty('toString'));
console.log("");
delete hzh['toString'];
console.log(hzh.hasOwnProperty('toString'));
[Running] node "e:\HMV\Babel\hzh.js"
false
true
false
[Done] exited with code=0 in 0.244 seconds