《JavaScript语言精粹》之对象篇

107 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

对于丑陋的事物,爱辉闭目无视。 —— 威廉 · 莎士比亚,《维洛那二绅士》(The Two Gentlemen of Verona)

  • JavaScript 中的对象是可变的键控集合( keyed collections)。

  • JavaScript 中,数组是对象,函数是对象,正则表达式是对象,对象自然也是对象。

  • 对象是属性的容器,其中每个属性都拥有名字和值。属性的名字可以包括空字符串在内的任意字符串。属性值可以是除undefined 值之外的任何值。

  • JavaScript 里的对象是无类型的。它对新属性的名字和属性的值没有限制。对象适合用于汇集和管理数据。

  • JavaScript 包含一种原型链的特性,允许对象继承另一个对象的属性。正确地使用它能减少对象初始化时消耗的时间和内存。

3.1 对象字面量

  • 对象字面量提供了一种非常方便地创建新对象值的表示法。
  • 一个对象字面量就是包围在一对花括号中的零或多个“名/值”对。
  • 对象字面量可以出现在任何允许表达式出现的地方。
var stooge = {
	"first-name": "Jerome",
	"lasr-name": "Howard"
}

属性名可以是包括空字符串在内的任何字符串。在对象字面量中,如果属性名是一个合法的 JavaSript 标识符且不是保留字,则并不强制要求用引号包括住属性名。所以用引号括住"first-name"是必须的,但是否括住first-name则是可选的。都好用来分隔多个“名/值”对。

3.2 检索

要检索对象里包含的值,可以采用在[ ] 后缀中括住一个字符串表达式的方式。如果字符串表达式是一个字符串字面量,而且它是一个合法的 JavaScript 标识符且不是保留字,那么也可以用 . 表示法代替。优先考虑使用 . 表示法,因为它更紧凑且可读性更好。

stooge["first-name"] // "Jerome"

如果你尝试检索一个并不存在的成员属性的值,将返回 undefined。

stooge["middle-name"] // undefined
flight.status // undefined

| | 运算符可以用来填充默认值

var middle = stooge["middle-name"] || "(none)"
var status = flight.status || "unknown";

尝试从 undefined 的成员属性中取值将会导致 TypeError 异常。这时可以通过 && 运算符来避免错误

flight.equipment // undefined
flight.equipment.model // throw "TypeError"
flight.equipment && flight.equipment.model // undefined

3.3 更新

  • 对象里的值可以通过赋值语句来更新。如果属性名已经存在于对象里,那么这个属性的值就会被替换。
stooge["first-name"] = "Jerome"

如果对象之前没有拥有那个属性名,那么该属性就被扩充到对象中。

stooge["middle-name"] = "lester";
stooge.nickname = "Curly";
flight.equipment = {
	model: "Boeing 777"
};
filght.status = "overdue";

3.4 引用

  • 对象通过引用来传递。他们永远不会被复制:
var x = stooge;
x.nickname = "Curly";
var nick = stooge.nickname; // 因为 x 和 stooge 是指向同一个对象的引用,所以 nick 为 “Curly”
var a = {}, b= {}, c = {}; // a、b 和 c 每个都引用一个不同的空对象。
a = b = c = {} // a、b 和 c 都引用同一个空对象。

3.5 原型

  • 每个对象都连接到一个原型对象,并且它可以从中继承属性。所有通过对象字面量创建的对象都连接到 Object.protootype,它是 JavaScript 中的标配对象。

  • 当你创建一个新对象时,你可以选择某个对象作为它的原型。JavaScript 提供的实现机制杂乱而复杂,但其实可以被明显地简化。我们将给 Object 增加一个 create 方法。这个方法创建一个使用原对象作为其原型的新对象。

if (typeof Object.beget !== 'function') {
	Object.create = function (o) {
		var F = function() {};
		F.prototype = o;
		return new F()
	}
}
var another_stooge = Object.create(stooge);

原型连接在更新时是不起作用的。当我们对某个对象做出改变时,不会触及该对象的原型:

another_stooge["first-name"] = "Harry";
anoter_stooge["middle-name"] = "Moss";
anoter_stooge.nickname = "Moe";

原型连接只有在检索值的时候才被用到。如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JavaScript 会试着从原型对象中获取属性值。如果那个原型对象也没该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点 Object.prototype。如果想要的属性完全不存在与原型链中,那么结果就是 undefined 值。这个过程称为 委托

原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

3.6 反射

  • 检查对象并确定对象有什么属性是很容易的事情,只要试着去检索该属性并验证取得的值。

  • typeof 操作符对确定属性的类型很有帮助。

typeof flight.number  // 'number'
typeof flight.status  // 'string'
typeof flight.arrival // 'object'
typeof flight.manifest // 'undefied'

请注意原型链中的任何属性都会产生值:

typeof flight.toString.   // 'function'
typeof flight.constructor // 'function'

有两种方法去处理掉这些不需要的属性:

  • 第一个是让你的程序做检查并丢弃值为函数的属性。

  • 另一个方法是使用 hasOwnProperty 方法,如果对象拥有独有的属性,它将返回 truehasOwnProperty 方法不会检查原型链。

flight.hasOwnProperty('number')  // true
flight.hasOwnProperty('constructor')  // false

3.7 枚举

for in 语句可用来遍历一个对象中的所有属性名。该枚举过程将会列出所有的属性——包括函数和你可能不关心的原型中的属性——所以有必要过滤掉那些你不想要的值。最为常用的过滤器是 hasOwnProperty 方法,以及使用 typeof 来排除函数:

var name;
for(name in another_stooge) {
    if(typeof another_stooge[name] !== 'function') {
        document.writeln(name + ': ' + anoter_stooge[name])
    }
}

如果你想要确保属性名以特定的顺序出现,最好的办法就是完全避免使用 for in 语句,而是创建一个数组,在其中以正确的顺序包含属性名;

通过使用 for 而不是 for in,可以得到我们想要的属性,而不用担心可能发掘出原型链中的属性,并且我们按正确的顺序取得了他们的值。

3.8 删除

  • delete 运算符可以用来删除对象的属性。如果对象包含该属性,那么该属性就会被移除。

  • 它不会触及原型链中的任何对象。

  • 删除的对象属性可能会让来自原型链中的属性透现出现:

another_stooge.nickname // 'Moe'
// 删除 another_stooge 的 nickname 属性,从而暴露出原型的 nickname 属性
delete another_stooge.nickname
another_stooge.nickname // 'Curly'

3.9 减少全局变量污染

  • JavaScript 可以随意地定义全局变量来容纳你的应用的所有资源。遗憾的是,全局变量削弱了程序的灵活性,应该避免使用。

  • 最小化使用全局变量的方法之一是为你的应用只创建一个唯一的全局变量;

  • 再下一章中,我们会看到使用必报来进行信息隐藏的方式,它是另一种有效减少全局污染的方法。