JavaScript悟道读书笔记 - 对象

208 阅读4分钟

在 JavaScript 中,除了 null 和 undefined, 万物皆对象。对象即一系列属性或成员的容器,各属性都由键名和键值组成,其中键名为字符串,键值可以为任意类型。我们可以通过对象字面量来新建对象。对象字面量可以被存储于变量、对象或者数组中,也可以被传入函数,还可以被函数作为返回值。

const bar = 'a long rod or rigid piece of wood or metal.'
const my_little_object = {
  '0/0': 0,
  foo: bar,
  bar,
  my_little_method() {
    return 'So small.'
  }
}

我们可以通过带命名的点表示法来访问对象的某个属性,当然也可以通过方括号表示法来访问对象中那些属性名不合法或者需要计算的属性。

my_little_object.foo === my_little_object.bar
// true
my_little_object['0/0'] === 0
// true

可以通过赋值语句为对象新增或修改属性。

my_little_object.age === 26
my_little_object.name = 'Sugary'

若属性不存在,则返回 undefined。访问对象中不存在的属性并不会被视为异常,返回 undefined 是常规操作。建议不要在对象中存储 undefined 这个值。尽管这样做合法,但是当对象中某个属性不存在时,JavaScript 也是返回 undefined, 这会产生二义性。为某个属性赋值为 undefined 意在删除该属性,然而 JavaScript 并没有这么做。如果要删除某个属性,正确的做法是使用 delete 运算符。

delete my_little_object['0/0']
// true

当对一个对象执行 typeof 操作符时,返回值是字符串 "object"

typeof my_little_object === 'object'
// true

区分大小写

对象属性的键名是区分大小写的。在匹配键名的时候,JavaScript 是用 === 运算符对两个字符串进行匹配。my_little_object.cat 和 my_little_object.CAT 是不一样的属性。

复制

Object.assign() 函数可以将一个对象中的属性复制到另一个对象中。你可以通过这个函数来将一个对象复制到一个空对象上。

const my_copy = Object.assign({}, my_little_object)
console.log(my_copy.age) // 26
my_copy.age += 1
console.log(my_copy.age) // 27
delete my_copy.age
console.log(my_copy.age) // undefined

继承

在 JavaScript 中,一个对象可以从另一个对象中继承而来。Object.create(prototype) 可以将一个已有的对象继承到一个新对象中。该已有对象将作为新对象的原型。同理,这个新对象还可以作为另一个新对象的原型。JavaScript 没有限制原型链的长度,但不建议让整条原型链过长。当访问某个对象不存在的属性时,JavaScript 在返回 undefined 之前会尝试在该对象的原型链上查找对应的属性,然后是原型的原型......如果在原型链上找到该属性,则返回该属性,好像它就在我们正在访问的对象上。反之,则返回 undefined。

当我们对一个对象的内容赋值时,被修改的只是原型链顶端的对象,原型链中间的属性并不会改变。

const my_clone = Object.create(my_little_object)
console.log(my_clone.age) // 26
my_clone.age += 1
console.log(my_clone.age) // 27
delete my_clone.age
console.log(my_clone.age) // 26

原型链最大的作用就是用来存储函数。用对象字面量创建的对象默认继承自 Object.prototype。类似地,数组方法继承自 Array.prototype; 数值方法继承自 Number.prototype; 字符串方法继承自 String.prototype; 函数方法继承自 Function.prototype。

因为这种继承模式的存在,JavaScript 对象有两种类型的属性:自有属性和继承属性。大多数对象继承的 hasOwnProperty(string) 方法可以检测一个属性是否是对象的自有属性。但该方法其实并不可靠,比如当某个对象存在自有属性 hasOwnProperty 并改写了 hasOwnProperty(string) 方法,那么我们调用的其实是自有的 hasOwnProperty。

Object.create() 的优势在于比 Object.assign() 使用的内存更少,但在大多数场景下,这点内存差异并不会带来多大的影响。原型带来的怪异性比带来的好处多多了。

JavaScript 的继承模式还会带来意外继承的问题。你可以使用 Object.create(null),创建一个没有任何继承属性的对象。

键名

Object.keys(object) 方法返回的是一个将传入对象的所有自有属性(不包括继承属性)的键名作为字符串的数组。

冻结

Object.freeze(object) 可以将对象冻结,使其成为不可变对象。不可变增强了程序的可靠性。需要注意的是,该操作并不是深度冻结,只有对象最顶层的属性会被冻结。如果某个对象的原型被冻结,那么该对象将无法拥有同名的自有属性。

WeakMap

WeakMap 的键名是对象,不能是字符串(与 JavaScript 中的对象刚好相反)。WeakMap 具有安全性和内存泄漏的防护机制,如果 WeakMap 中的一个键名在外没有了任何副本,那么这个键名所对应的属性会被自动删除。 如何使用 WeakMap 实现一套安全的封箱机制:

function sealer_factory() {
  const weakmap = new WeakMap()
  return {
    sealer(object) {
      const box = Object.freeze(Object.create(null))
      weakmap.set(box, object)
      return box
    },
    unsealer(box) {
      return weakmap.get(box)
    }
  }
}