《JS 对象知识地图:10 个小节,从字面量到原型链全覆盖》

27 阅读7分钟

《JS 对象知识地图:10 个小节,从字面量到原型链全覆盖》

笔记整理自《JavaScript语言精粹》,带你彻底搞懂JS对象的一切

1. 什么是对象?

1.1 简单类型 vs 对象

JavaScript 里有几种简单类型(也叫原始类型):数字、字符串、布尔值(true/false)、nullundefined
其中数字、字符串、布尔值有点特殊——它们“貌似”对象,因为可以调用方法(比如 'abc'.toUpperCase())。但它们是不可变的。

而真正的对象,在 JS 里是可变的键控集合

1.2 除了简单类型,剩下的全是对象

数组、函数、正则表达式、对象本身……都是对象。

1.3 对象的本质

每个对象都是一个属性容器

  • 属性名只能是字符串(包括空字符串 ""
  • 属性值可以是除 undefined 以外的任何值

1.4 关键特征:无类别

什么叫无类别?就是对象不依附于某个预先定义好的“类”(Class)。
你可以在任何时候给它添加新属性,没有任何约束。

而且对象可以包含其他对象,所以很容易表示成树形或图结构。


2. 对象字面量

对象字面量就是一对花括号{},里面可以放零个或多个“名/值”对。它能出现在任何允许表达式的地方。

属性名如果是合法的标识符并且不是保留字,就可以省略引号;否则必须用引号包起来,比如"first-name"

什么是合法的标识符?

  • 首字符必须是字母(a-z, A-Z)、下划线(_)或美元符号($)
  • 后续字符可以是字母、数字、下划线、美元符号
  • 不能是保留字

对象字面量还可以嵌套,也就是说值本身可以是另一个对象字面量,这样就形成了树形/图形结构。

var flight = {  
airline: "Oceanic",  
number: 815,  
departure: {  
IATA: "SYD",  
time: "2004-09-22 14:55",  
city: "Sydney"  
}  
};

3. 检索

3.1 两种检索方式
  • 点表示法(推荐):flight.departure.IATA,更紧凑、可读性更好
  • 方括号表示法stooge["first-name"],当属性名含有非法字符(比如"first-name")时必须用这个
3.2 undefined 的陷阱

如果你检索一个不存在的属性,会返回undefined。比如flight.equipment就是undefined

但问题是,如果你继续往undefined上面检索属性,比如flight.equipment.model,就会抛出TypeError

防御技巧:用&&短路运算

var model = flight.equipment && flight.equipment.model; // undefined

默认值(||)

||运算符有个妙用:它会返回第一个真值操作数,而不是布尔值。

var status = flight.status || "unknown";

但是要注意0""falsenullundefinedNaN都是假值。如果你需要保留这些值(比如0本身是有意义的),就不能用这个技巧。


4. 更新

对象的属性可以通过赋值来修改或新增:

  • 如果属性名已经存在,赋值就会替换掉原有的值
  • 如果属性名不存在,赋值就会把这个属性新增到对象中
stooge['first-name'] = 'Jerome'; // 替换  
stooge['middle-name'] = 'Lester'; // 新增(方括号)  
stooge.nickname = 'Curly'; // 新增(点语法)  
flight.equipment = { model: 'Boeing 777' }; // 新增嵌套对象  
flight.status = 'overdue'; // 新增普通属性

5. 引用

对象通过引用来传递,它们永远不会被拷贝。

当你赋值时,只是复制了引用,两个变量指向内存中的同一个对象。

var x = stooge;  
x.nickname = "Curly";  
var nick = stooge.nickname; // 结果是'Curly',因为x和stooge指向同一个对象

修改任何一个引用的属性,都会影响所有引用。

对比一下:

var a = {}, b = {}, c = {}; // 创建三个独立的对象  
a = b = c = {}; // 三个变量共享同一个对象

6. 原型

6.1原型链机制

每个对象都隐藏连接到一个原型对象(内部[[Prototype]])。通过对象字面量创建的对象,会连接到Object.prototype

当你检索属性时,如果对象自身没有这个属性,就会沿着原型链向上查找,直到Object.prototype。找不到就返回undefined

6.2原型的动态性

原型连接只在检索时起作用。更新或添加属性只影响当前对象本身,不会修改原型。

但是,如果你给原型添加新属性(比如stooge.profession = "actor"),所有继承这个原型的对象都会立即看到这个新属性。

6.3 构造原型关系(Object.beget 方法)
if (typeof Object.beget !== 'function') {  
Object.beget = function (o) {  
var F = function () {};  
F.prototype = o;  
return new F();  
};  
}  
var another_stooge = Object.beget(stooge);
6.4 原型链的深层理解

原型链不是拷贝链,而是委托链——对象通过委托共享行为,这样更节省内存。这种机制比基于类的继承更灵活:你可以在运行时动态改变原型,影响所有派生对象。不过要注意,原型链不宜过深,否则检索性能会下降。


7. 反射

typeof操作符可以帮助你确定属性的类型:

typeof flight.number // 'number'
7.1 typeof 的局限

typeof也会检测到原型链上的方法:

typeof flight.toString // "function"(来自原型)  
typeof flight.constructor // "function"

如果只关心数据属性,就需要过滤掉这些函数。

7.2 两种处理方法
  1. 让程序检查并剔除函数值
  2. 使用hasOwnProperty

object.hasOwnProperty(name)只有在属性属于对象自身(不在原型链上)时才返回true

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

8. 枚举

8.1 for in 的三个问题
  • 会遍历所有可枚举属性,包括原型链上的
  • 会包含函数(方法),而我们通常只想要数据
  • 顺序不确定,依赖具体实现
8.2 推荐的过滤方式
for (name in another_stooge) {  
if (typeof another_stooge[name] !== 'function') {  
// 处理数据属性  
}  
}

或者用hasOwnProperty:

if (another_stooge.hasOwnProperty(name)) { ... }
8.3 最可靠的方式

干脆不用for in:

var i;  
var properties = [  
'first-name',  
'middle-name',  
'last-name',  
'profession'  
];  
for (i = 0; i < properties.length; i += 1) {  
document.writeln(properties[i] + ': ' +  
another_stooge[properties[i]]);  
}

这样做的好处:完全控制顺序,不会意外包含原型链属性,性能通常也更好(尤其在大型对象上)。


9. 删除

delete运算符只删除对象自身的属性。

删除自有属性后,如果原型链上有同名属性,它就会变得可见。

another_stooge.nickname // 'Moe'

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

another_stooge.nickname // 'Curly'

10. 减少全局变量污染

10.1 为什么全局变量是糟粕
  • 全局变量可以被程序的任何部分、在任何时候修改 → 程序行为难以预测
  • 会降低可靠性,阻碍独立子程序组合
  • JavaScript还强制使用全局变量(没有链接器,所有编译单元共享一个全局对象)
10.2 推荐模式

只暴露一个全局变量,把所有的功能都挂载在这个变量下面:

var MYAPP = {};  
MYAPP.stooge = { "first-name": "Joe" };  
MYAPP.flight = { ... };

这样做能显著降低与第三方库冲突的可能性,代码结构也更清晰——MYAPP.stooge一看就知道是顶层结构。


总结

以上就是 JavaScript 对象的全部核心内容:

  • 对象是可变键控集合,属性名为字符串,属性值不能是 undefined,且无类别、无约束,可随时增删属性。
  • 对象字面量 {} 是最常用的创建方式,可嵌套。
  • 检索用点号或方括号,不存在的属性返回 undefined,连续检索需用 && 防御;|| 可提供默认值但要注意假值陷阱。
  • 更新属性:存在则替换,不存在则新增。
  • 引用传递:对象永不拷贝,赋值只复制引用,修改会影响所有指向同一对象的变量。
  • 原型链:每个对象连接 Object.prototype,检索时沿链委托;动态添加原型属性会立即影响所有派生对象;用 Object.beget 可创建指定原型的对象。
  • 反射typeof 可查类型,用 hasOwnProperty 过滤掉原型链上的属性。
  • 枚举for in 会遍历原型链且顺序不定,推荐用 hasOwnProperty + typeof 过滤,或直接使用数组手动枚举。
  • 删除delete 只删自身属性,可能暴露原型链上的同名属性。
  • 减少全局污染:只创建一个全局变量(如 MYAPP),所有对象挂载其下,避免冲突。

理解这些规则,你就能灵活、安全地使用 JavaScript 对象。