6JavaScript高级程序设计(第4版)

对象

JavaScript的核心

对象字面量属性方法原型

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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")); // true

6.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);  // 1000

6.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实现一个响应式数据系统。


6/14对象
0%