《JS 对象知识地图:10 个小节,从字面量到原型链全覆盖》
笔记整理自《JavaScript语言精粹》,带你彻底搞懂JS对象的一切
1. 什么是对象?
1.1 简单类型 vs 对象
JavaScript 里有几种简单类型(也叫原始类型):数字、字符串、布尔值(true/false)、null、undefined。
其中数字、字符串、布尔值有点特殊------它们"貌似"对象,因为可以调用方法(比如 'abc'.toUpperCase())。但它们是不可变的。
而真正的对象,在 JS 里是可变的键控集合。
1.2 除了简单类型,剩下的全是对象
数组、函数、正则表达式、对象本身......都是对象。
1.3 对象的本质
每个对象都是一个属性容器。
- 属性名只能是字符串(包括空字符串
"") - 属性值可以是除
undefined以外的任何值
1.4 关键特征:无类别
什么叫无类别?就是对象不依附于某个预先定义好的"类"(Class)。
你可以在任何时候给它添加新属性,没有任何约束。
而且对象可以包含其他对象,所以很容易表示成树形或图结构。
2. 对象字面量
对象字面量就是一对花括号{},里面可以放零个或多个"名/值"对。它能出现在任何允许表达式的地方。
属性名如果是合法的标识符并且不是保留字,就可以省略引号;否则必须用引号包起来,比如"first-name"。
什么是合法的标识符?
- 首字符必须是字母(a-z, A-Z)、下划线(_)或美元符号($)
- 后续字符可以是字母、数字、下划线、美元符号
- 不能是保留字
对象字面量还可以嵌套,也就是说值本身可以是另一个对象字面量,这样就形成了树形/图形结构。
vbnet
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、""、false、null、undefined、NaN都是假值。如果你需要保留这些值(比如0本身是有意义的),就不能用这个技巧。
4. 更新
对象的属性可以通过赋值来修改或新增:
- 如果属性名已经存在,赋值就会替换掉原有的值
- 如果属性名不存在 ,赋值就会把这个属性新增到对象中
ini
stooge['first-name'] = 'Jerome'; // 替换
stooge['middle-name'] = 'Lester'; // 新增(方括号)
stooge.nickname = 'Curly'; // 新增(点语法)
flight.equipment = { model: 'Boeing 777' }; // 新增嵌套对象
flight.status = 'overdue'; // 新增普通属性
5. 引用
对象通过引用来传递,它们永远不会被拷贝。
当你赋值时,只是复制了引用,两个变量指向内存中的同一个对象。
ini
var x = stooge;
x.nickname = "Curly";
var nick = stooge.nickname; // 结果是'Curly',因为x和stooge指向同一个对象
修改任何一个引用的属性,都会影响所有引用。
对比一下:
less
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 方法)
javascript
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操作符可以帮助你确定属性的类型:
csharp
typeof flight.number // 'number'
7.1 typeof 的局限
typeof也会检测到原型链上的方法:
csharp
typeof flight.toString // "function"(来自原型)
typeof flight.constructor // "function"
如果只关心数据属性,就需要过滤掉这些函数。
7.2 两种处理方法
- 让程序检查并剔除函数值
- 使用
hasOwnProperty
object.hasOwnProperty(name)只有在属性属于对象自身 (不在原型链上)时才返回true:
arduino
flight.hasOwnProperty('number') // true
flight.hasOwnProperty('constructor') // false
8. 枚举
8.1 for in 的三个问题
- 会遍历所有可枚举属性,包括原型链上的
- 会包含函数(方法),而我们通常只想要数据
- 顺序不确定,依赖具体实现
8.2 推荐的过滤方式
csharp
for (name in another_stooge) {
if (typeof another_stooge[name] !== 'function') {
// 处理数据属性
}
}
或者用hasOwnProperty:
less
if (another_stooge.hasOwnProperty(name)) { ... }
8.3 最可靠的方式
干脆不用for in:
ini
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运算符只删除对象自身的属性。
删除自有属性后,如果原型链上有同名属性,它就会变得可见。
arduino
another_stooge.nickname // 'Moe'
// 删除 another_stooge 的 nickname 属性,从而暴露出原型的 nickname
delete another_stooge.nickname;
another_stooge.nickname // 'Curly'
10. 减少全局变量污染
10.1 为什么全局变量是糟粕
- 全局变量可以被程序的任何部分、在任何时候修改 → 程序行为难以预测
- 会降低可靠性,阻碍独立子程序组合
- JavaScript还强制使用全局变量(没有链接器,所有编译单元共享一个全局对象)
10.2 推荐模式
只暴露一个全局变量,把所有的功能都挂载在这个变量下面:
ini
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 对象。