# 对象(object)

# 定义对象

定义对象有2种形式:

  1. 声明(文字)形式:
var myObj = {
  key: value
  // ...
}
1
2
3
4
  1. 构造形式:
var myObj = new Object()
myObj.key = value
1
2

# 访问对象

访问对象的属性有2种方式:

  1. . 操作符: 要求属性名满足标识符的命名规范。
  2. [] 操作符: 接受任意 UTF-8/Unicode 字符串作为属性名。

# 几个小知识

一、[".."] 语法使用字符串来访问属性,所以可以在程序中构造这个字符串:

var myObject = {
  a:2
};
var idx;
if (wantA) {
  idx = "a"
} else if (wantB) {
  idx = 'b'
}

// 之后
console.log( myObject[idx] ) // 2
1
2
3
4
5
6
7
8
9
10
11
12

这个特性,也有助于我们构建和访问一些带规律的变量:

var obj = {}

// 构建带规律的变量
for (var i = 0; i < 5; i++) {
  obj['variable' + i] = i
}

// 访问带规律的变量
for (var i = 0; i < 5; i++) {
  console.log( obj['variable' + i] )
}
1
2
3
4
5
6
7
8
9
10
11

二、在对象中,属性名永远都是字符串。如果你使用 string以外的其他类型作为属性名,那它首先会被转换为一个字符串。即使是数字也不例外,虽然在数组下标中使用的的确是数字,但是在对象属性名中数字会被转换成字符串,所以当心不要搞混对象和数组中数字的用法:

var myObject = { }
myObject.a = 'foo'
myObject[3] = 'bar'
myObject[myObject] = 'baz'
myObject['a'] // 'foo'
myObject['3'] // 'bar'
myObject['[object Object]'] // 'baz'
1
2
3
4
5
6
7

# 禁操对象

# Object.preventExtensions()

禁止一个对象添加新属性并且保留已有属性:

var myObject = {
  a:2
}
Object.preventExtensions( myObject )
myObject.b = 3
myObject.b // undefined
1
2
3
4
5
6

在非严格模式下,创建属性 b 会静默失败。在严格模式下,将会抛出 TypeError 错误。

# Object.seal()

该方法会创建一个“密封”的对象,这个方法实际上是在被操作的对象上调用Object.preventExtensions() 并把所有现有属性标记为 configurable:false
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(但可以修改属性的值)。

# Object.freeze()

该方法会创建一个冻结对象,这个方法实际上是在被操作的对象上调用Object.seal() 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们的值。 这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(不过就像我们之前说过的,这个对象引用的其他对象是不受影响的)。

你可以“深度冻结”一个对象,具体方法为,首先在这个对象上调用 Object.freeze(), 然后遍历它引用的所有对象并在这些对象上调用 Object.freeze()。但是一定要小心,因为这样做有可能会在无意中冻结其他(共享)对象。

# 获取所有属性

我们可以在不访问属性值的情况下判断对象中是否存在这个属性:

var myObject = {
  a:2
}
("a" in myObject) // true
("b" in myObject) // false
myObject.hasOwnProperty( "a" ) // true
myObject.hasOwnProperty( "b" ) // false
1
2
3
4
5
6
7
  • in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。
  • 相比之下,hasOwnProperty(propertyName) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。

注意:看起来 in 操作符可以检查容器内是否有某个值,但是它实际上检查的是某 个属性名是否存在。对于数组来说这个区别非常重要,4 in [2, 4, 6] 的结 果并不是你期待的 True,因为 [2, 4, 6] 这个数组中包含的属性名是 0、1、2,没有 4。

  • for..in 循环中,只会遍历对象的“可枚举”属性,会查找对象的原型链,但是遍历对象属性时的顺序是不确定的,在不同的 JavaScript 引擎中可能不一样。
var otherObj = {a: 3};
var myObj = Object.create(otherObj);
myObj.b = 4;
for (var k in myObj) {
  console.log( myObj[k] ); // 4 3
}
1
2
3
4
5
6
  • propertyIsEnumerable(propertyName) 会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true。
  • Object.keys(..) 会返回一个数组,包含所有可枚举属性,只会查找对象直接包含的属性。
  • Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举。只会查找对象直接包含的属性。

# 高级技巧

  1. 如何区分原生与非原生 JavaScript 对象?
    答: Object.prototype.toString.call(value),原生JavaScript对象构造的实例会返回相应的字符串,如:[object Array],[object Function],而非原生JavaScript对象构造的实例只会返回[object Object]
    参考资料:《JavaScript高级程序设计(第三版)》第22.1.1节
Last Updated: 11/9/2022, 6:38:52 AM