第6章:对象(Objects)
一、导读
1.1 本章学习目标
第6章深入讲解JavaScript的对象系统,这是JavaScript最核心的特性。本章核心目标:
- 掌握对象的创建和属性操作:字面量、点号、方括号
- 理解属性描述符:writable、enumerable、configurable
- 学会使用getter和setter:控制属性访问
- 深入理解原型链:
__proto__和prototype - 掌握Object的方法:create、assign、keys、values
1.2 为什么本章如此重要
JavaScript是"基于原型的语言",一切都是对象(除了基本类型)。理解对象系统和原型链,是掌握JavaScript的关键。原型继承是JavaScript独有的特性,与Java/C++的类继承有本质区别。
1.3 核心问题引导
JavaScript的对象和Java/C++的对象有什么本质区别?
原型链是如何工作的?属性查找的顺序是什么?
this 关键字在不同上下文中指向什么?
Object.create() 和 new 有什么区别?
二、核心概念详解
6.1 对象基础
javascript
// 对象字面量
let person = {
name: "Alice",
age: 20,
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
// 属性访问
console.log(person.name); // 点号
console.log(person["age"]); // 方括号(支持动态属性名)
// 动态属性名
let prop = "name";
console.log(person[prop]); // "Alice"
// 添加和修改属性
person.email = "alice@example.com";
person.age = 21;
// 删除属性
delete person.email;
// 检查属性
console.log("name" in person); // true
console.log(person.hasOwnProperty("age")); // true6.2 属性描述符
javascript
let obj = {};
// 定义属性(带描述符)
Object.defineProperty(obj, 'name', {
value: 'Alice',
writable: true, // 可修改
enumerable: true, // 可枚举
configurable: true // 可删除/重新定义
});
// 只读属性
Object.defineProperty(obj, 'PI', {
value: 3.14159,
writable: false
});
// obj.PI = 3; // 静默失败(严格模式报错)
// 不可枚举属性
Object.defineProperty(obj, 'secret', {
value: 'hidden',
enumerable: false
});
console.log(Object.keys(obj)); // ['name', 'PI'](不包含secret)
console.log(obj.secret); // 'hidden'(仍然可以访问)
// 批量定义
Object.defineProperties(obj, {
x: { value: 1, writable: true },
y: { value: 2, writable: true }
});6.3 Getter和Setter
javascript
let user = {
_name: "Bob", // 约定:下划线表示"私有"
get name() {
console.log("Getting name");
return this._name;
},
set name(value) {
console.log("Setting name");
if (value.length > 0) {
this._name = value;
} else {
throw new Error("Name cannot be empty");
}
}
};
console.log(user.name); // "Getting name", "Bob"
user.name = "Charlie"; // "Setting name"
console.log(user.name); // "Getting name", "Charlie"
// 使用Object.defineProperty定义getter/setter
let product = {
_price: 100,
_quantity: 5
};
Object.defineProperty(product, 'total', {
get() {
return this._price * this._quantity;
}
});
console.log(product.total); // 500
product._quantity = 10;
console.log(product.total); // 10006.4 原型链
javascript
// ES5 构造函数 + 原型
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
let p1 = new Person("Alice");
p1.greet(); // "Hello, I'm Alice"
// 原型链
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// 属性查找顺序:对象自身 → 原型 → 原型的原型 → ... → null
console.log(p1.toString()); // 来自Object.prototype
// 继承
function Student(name, grade) {
Person.call(this, name); // 调用父类构造函数
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.study = function() {
console.log(`${this.name} is studying`);
};
let s1 = new Student("Bob", 12);
s1.greet(); // 继承自Person
s1.study(); // Student自己的方法6.5 Object方法
javascript
// Object.keys/values/entries
let obj = {a: 1, b: 2, c: 3};
console.log(Object.keys(obj)); // ['a', 'b', 'c']
console.log(Object.values(obj)); // [1, 2, 3]
console.log(Object.entries(obj)); // [['a',1], ['b',2], ['c',3]]
// Object.assign(浅拷贝)
let target = {a: 1, b: 2};
let source = {b: 3, c: 4};
let merged = Object.assign({}, target, source);
console.log(merged); // {a: 1, b: 3, c: 4}
// Object.create
let proto = {greet() { return "Hello"; }};
let obj2 = Object.create(proto);
console.log(obj2.greet()); // "Hello"
// Object.freeze(冻结对象)
let frozen = Object.freeze({x: 1, y: 2});
// frozen.x = 10; // 静默失败
// Object.fromEntries
let entries = [['a', 1], ['b', 2], ['c', 3]];
let fromEntries = Object.fromEntries(entries);
console.log(fromEntries); // {a: 1, b: 2, c: 3}6.6 this关键字
javascript
// 全局上下文
console.log(this); // 浏览器中是window
// 函数上下文
function showThis() {
console.log(this);
}
showThis(); // window(非严格模式)或undefined(严格模式)
// 对象方法中的this
let obj3 = {
name: "Alice",
greet() {
console.log(`Hello, ${this.name}`);
},
greetArrow: () => {
console.log(`Hello, ${this.name}`); // this指向外层
}
};
obj3.greet(); // "Hello, Alice"
obj3.greetArrow(); // "Hello, undefined"
// call, apply, bind
function introduce(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
let person = {name: "Bob"};
introduce.call(person, "Hi"); // "Hi, I'm Bob"
introduce.apply(person, ["Hey"]); // "Hey, I'm Bob"
let boundIntroduce = introduce.bind(person);
boundIntroduce("Hello"); // "Hello, I'm Bob"三、实战案例
3.1 对象池模式
javascript
// 使用Object.create实现对象池
const proto = {
init(name) {
this.name = name;
return this;
},
greet() {
return `Hello, ${this.name}`;
}
};
const pool = [];
function getObject(name) {
let obj = pool.pop() || Object.create(proto);
return obj.init(name);
}
function releaseObject(obj) {
obj.name = null;
pool.push(obj);
}
let obj1 = getObject("Alice");
console.log(obj1.greet());
releaseObject(obj1);3.2 深拷贝
javascript
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
const clone = Array.isArray(obj) ? [] : {};
seen.set(obj, clone);
for (const key of Object.keys(obj)) {
clone[key] = deepClone(obj[key], seen);
}
return clone;
}
let original = {a: 1, b: {c: 2}, d: [3, 4]};
let cloned = deepClone(original);
cloned.b.c = 100;
console.log(original.b.c); // 2(不受影响)四、本章小结
第6章深入讲解了JavaScript的对象系统:对象字面量、属性描述符、getter/setter、原型链、this关键字、Object方法。原型链是JavaScript继承的基础,理解它是掌握JavaScript的关键。
五、思考与练习
思考题
原型链的终点是什么?为什么?
this 在箭头函数和普通函数中有什么不同?
Object.freeze和Object.seal有什么区别?
如何实现一个不可扩展的对象?
编程练习
实现一个简单的类系统(不使用class语法)。
编写一个深拷贝函数,处理循环引用。
使用Proxy实现一个响应式数据系统。