第3章:对象(Objects)
一、导读
1.1 本章学习目标
第3章深入讲解 JavaScript 中最重要的数据类型——对象。在 JavaScript 中,几乎所有东西都是对象。理解对象的本质,是掌握 JavaScript 的关键。
本章的核心学习目标包括:
- 理解对象的本质:对象是属性的集合,属性是键值对。
- 掌握对象字面量:学会用简洁的方式创建对象。
- 理解原型链:掌握 JavaScript 的继承机制。
- 学会使用 Object.create:掌握原型继承的正确方式。
- 理解 delete 操作符:知道如何正确删除属性。
1.2 为什么第三章如此重要
对象是 JavaScript 的核心。函数是对象,数组是对象,正则表达式是对象,甚至原始值在需要时也会被包装为对象。理解对象的创建、访问、继承和删除,是写出高质量 JavaScript 代码的基础。
1.3 核心问题引导
JavaScript 对象与 Java 对象有什么本质区别?
原型链是如何工作的?__proto__ 和 prototype 有什么区别?
Object.create 和 new 有什么区别?
如何安全地检查对象是否拥有某个属性?
delete 操作符能删除什么?不能删除什么?
二、核心概念详解
3.1 对象字面量
// 对象字面量
var empty = {};
var point = {x: 0, y: 0};
var person = {
name: "Alice",
age: 30,
greet: function() {
return "Hello, I'm " + this.name;
}
};
// 属性访问
person.name; // "Alice"
person["name"]; // "Alice"
person.greet(); // "Hello, I'm Alice"
// 动态属性名
var key = "name";
person[key]; // "Alice"
// 属性赋值
person.email = "alice@example.com";
person["phone"] = "123-456-7890";3.2 原型(Prototype)
每个对象都有一个原型对象,对象从原型继承方法和属性。
// 原型链
var animal = {
type: "animal",
speak: function() {
return "I am a " + this.type;
}
};
var dog = Object.create(animal);
dog.type = "dog";
dog.bark = function() { return "Woof!"; };
dog.speak(); // "I am a dog"(继承自 animal)
dog.bark(); // "Woof!"(自己的方法)
// 原型链查找
// dog.speak → dog 自身没有 → 查找 dog.__proto__(即 animal)→ 找到
// 验证原型
Object.getPrototypeOf(dog) === animal; // true
animal.isPrototypeOf(dog); // true3.3 Object.create
Object.create 是创建新对象并指定其原型的标准方式。
// Object.create 创建对象
var base = {
init: function(name) {
this.name = name;
return this;
},
getName: function() {
return this.name;
}
};
var person = Object.create(base).init("Alice");
person.getName(); // "Alice"
// 带属性描述符的 Object.create
var obj = Object.create(Object.prototype, {
name: {
value: "Alice",
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 30,
writable: false // 不可修改
}
});
obj.name = "Bob"; // 成功
obj.age = 40; // 严格模式下报错,非严格模式静默失败3.4 属性查找与 hasOwnProperty
// 属性查找
var obj = {a: 1};
"a" in obj; // true(包括原型链)
obj.hasOwnProperty("a"); // true(仅自身属性)
obj.hasOwnProperty("toString"); // false(原型链上的)
// Object.keys 只返回自身可枚举属性
Object.keys(obj); // ["a"]
// for in 遍历所有可枚举属性(包括原型链)
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key + ": " + obj[key]);
}
}
// Object.getOwnPropertyNames 返回所有自身属性(包括不可枚举的)
Object.getOwnPropertyNames(obj);3.5 delete 操作符
var obj = {a: 1, b: 2, c: 3};
delete obj.a; // true
obj.a; // undefined
"a" in obj; // false
// delete 的局限
delete obj.toString; // false(不能删除继承的属性)
var global_var = 1;
delete global_var; // false(不能删除 var 声明的变量)
global_prop = 2;
delete global_prop; // true(可以删除隐式全局变量)3.6 函数式继承
Crockford 提出了一种函数式的继承方式,避免了 new 关键字的问题。
// 函数式继承
function mammal(spec) {
var that = {};
that.getName = function() {
return spec.name;
};
that.says = function() {
return spec.saying || "";
};
return that;
}
function cat(spec) {
spec.saying = spec.saying || "meow";
var that = mammal(spec);
that.purr = function() {
return "purr";
};
that.getName = function() {
return spec.name + " (cat)";
};
return that;
}
var myCat = cat({name: "Henrietta"});
myCat.getName(); // "Henrietta (cat)"
myCat.says(); // "meow"
myCat.purr(); // "purr"3.7 多态
// 通过方法覆盖实现多态
var coolcat = cat({name: "Bix"});
var speak = function(name) {
return name + " says " + name.says();
};
speak(coolcat); // "Bix says meow"
// 差异化继承
var myCat2 = cat({name: "Henrietta"});
myCat2.says = function() { return "squeak!"; };
myCat2.getName = function() { return "Lady " + this.name; };
speak(myCat2); // "Lady Henrietta says squeak!"三、实战案例
3.1 对象工厂模式
// 对象工厂
function createVehicle(spec) {
var that = {};
that.getMake = function() { return spec.make; };
that.getModel = function() { return spec.model; };
that.getYear = function() { return spec.year; };
that.describe = function() {
return that.getYear() + " " + that.getMake() + " " + that.getModel();
};
return that;
}
function createCar(spec) {
spec.doors = spec.doors || 4;
var that = createVehicle(spec);
that.getDoors = function() { return spec.doors; };
that.describe = function() {
return createVehicle(spec).describe() + " with " + spec.doors + " doors";
};
return that;
}
function createTruck(spec) {
spec.bedLength = spec.bedLength || "standard";
var that = createVehicle(spec);
that.getBedLength = function() { return spec.bedLength; };
that.describe = function() {
return createVehicle(spec).describe() + " truck with " + spec.bedLength + " bed";
};
return that;
}
var myCar = createCar({make: "Toyota", model: "Camry", year: 2023});
var myTruck = createTruck({make: "Ford", model: "F-150", year: 2023, bedLength: "long"});
console.log(myCar.describe()); // "2023 Toyota Camry with 4 doors"
console.log(myTruck.describe()); // "2023 Ford F-150 truck with long bed"3.2 深拷贝
// 深拷贝对象
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map(deepClone);
}
if (obj instanceof Object) {
var copy = Object.create(Object.getPrototypeOf(obj));
Object.keys(obj).forEach(function(key) {
copy[key] = deepClone(obj[key]);
});
return copy;
}
return obj;
}
var original = {
name: "Alice",
address: {city: "NYC", zip: "10001"},
hobbies: ["reading", "coding"]
};
var cloned = deepClone(original);
cloned.address.city = "LA";
cloned.hobbies.push("swimming");
console.log(original.address.city); // "NYC"(未被修改)
console.log(original.hobbies); // ["reading", "coding"](未被修改)四、本章小结
第3章深入讲解了 JavaScript 对象的核心知识:
对象是属性的集合:属性是键值对,值可以是任何类型。
原型链:对象通过原型链继承属性和方法。
Object.create:是创建对象并指定原型的标准方式。
hasOwnProperty:用于区分自身属性和继承属性。
函数式继承:避免 new 关键字的问题,使用闭包实现私有状态。
delete:只能删除对象自身的属性。
对象是 JavaScript 的灵魂。理解对象的创建、访问、继承和删除,是掌握 JavaScript 的关键。
五、思考与练习
思考题
JavaScript 对象和 Java 对象有什么本质区别?
原型链的查找过程是怎样的?
Object.create(null) 和 {} 有什么区别?
函数式继承相比构造器+原型有什么优势?
如何实现一个不可变的对象?
编程练习
实现对象工厂:创建一个 Shape 层级结构(Shape → Circle/Rectangle)。
实现深拷贝:处理循环引用。
实现 mixin:将多个对象的能力合并到一个对象上。