JavaScript构造函数与Class终极指南

一、构造函数的本质

1.1 构造函数的定义:普通函数的特殊用法

javascript 复制代码
// 定义一个普通的函数,它可以被当做构造函数使用
function Person(name) {
  // 当通过 new 调用时,this 指向新创建的对象
  // 当直接调用时,this 指向全局对象(浏览器中是 window)
  this.name = name;
  
  // 实例方法:每个实例都会创建一个新的函数
  this.say = function() { 
    console.log('你好!' + this.name); 
  };
}

// 正确用法:通过 new 关键字调用,此时函数充当构造函数
let p1 = new Person('小明'); // 创建 Person 的一个实例

// 错误用法:直接调用构造函数(会导致全局变量污染)
Person('小红'); 
// 此时函数内部的 this 指向 window(浏览器环境)
// 相当于 window.name = '小红',污染了全局作用域
// 在严格模式下('use strict'),直接调用时 this 为 undefined,会报错

1.2 new 运算符的详细执行流程

javascript 复制代码
function Student(name) {
  // new 关键字的第 1-2 步已经完成:创建空对象并绑定 this
  
  // 验证 this 确实是新对象
  console.log(this); // 输出:Student {}(空对象)
  
  // 第 3 步:执行构造函数代码,为新对象添加属性
  this.name = name; // 给新对象添加 name 属性
  
  // 可以在构造函数中验证 this 的指向
  console.log(this instanceof Student); // 输出:true
  
  // 构造函数可以显式返回一个对象
  // 如果返回非对象值,则忽略,仍返回新创建的对象
  // return { custom: '对象' }; // 如果取消注释,将返回这个对象而不是新创建的 Student 实例
}

// new 关键字的完整过程:
// 1. 创建一个新的空对象:const obj = {}
// 2. 将这个新对象的 [[Prototype]](即 __proto__)指向构造函数的 prototype 属性
// 3. 将构造函数的 this 绑定到这个新对象
// 4. 执行构造函数内部的代码(为 this 添加属性)
// 5. 如果构造函数没有显式返回对象,则返回 this(即新创建的对象)
let stu = new Student('李华');
console.log(stu.name); // 输出:'李华'

1.3 构造函数、类和实例的关系

javascript 复制代码
// 构造函数:创建对象的模板/蓝图
function Car(brand, model) {
  // 实例属性:每个实例独有的属性
  this.brand = brand;
  this.model = model;
  
  // 实例方法(不推荐,因为每个实例都会创建新的函数,浪费内存)
  this.displayInfo = function() {
    return `${this.brand} ${this.model}`;
  };
}

// 更好的做法:将方法定义在原型上,所有实例共享同一个函数
Car.prototype.startEngine = function() {
  return `${this.brand} ${this.model} 引擎启动`;
};

// 实例化:使用 new 创建对象的过程
let myCar = new Car('宝马', 'X5');
console.log(myCar.displayInfo()); // 调用实例方法
console.log(myCar.startEngine()); // 调用原型方法

// myCar 是 Car 的一个实例
console.log(myCar instanceof Car);    // true
console.log(myCar instanceof Object); // true,所有对象都是 Object 的实例

1.4 instanceof 运算符的工作原理

javascript 复制代码
function Vehicle(type) {
  this.type = type;
}

function Car(brand) {
  this.brand = brand;
}

// 设置原型链,使 Car 继承 Vehicle
Car.prototype = new Vehicle('汽车');

let bmw = new Car('宝马');

// instanceof 检查原型链
console.log(bmw instanceof Car);      // true:bmw 的原型链上能找到 Car.prototype
console.log(bmw instanceof Vehicle);  // true:bmw 的原型链上能找到 Vehicle.prototype
console.log(bmw instanceof Object);   // true:bmw 的原型链上能找到 Object.prototype

// instanceof 的工作方式:
// 1. 检查对象的 __proto__ 是否等于构造函数的 prototype
// 2. 如果不相等,沿着原型链向上查找(检查 __proto__.__proto__)
// 3. 找到则返回 true,直到原型链尽头(null)则返回 false

// 模拟实现 instanceof
function myInstanceof(obj, constructor) {
  let proto = Object.getPrototypeOf(obj); // 获取 obj 的原型
  
  while (proto !== null) {
    if (proto === constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto); // 继续向上查找
  }
  return false;
}

1.5 关键注意事项与陷阱

javascript 复制代码
// 1. 不要直接调用构造函数
function Animal(name) {
  this.name = name;
}

// 错误:直接调用,this 指向全局对象
Animal('老虎'); // 浏览器中:window.name = '老虎'

// 预防方法:使用 new.target(ES6)或严格模式
function SafeAnimal(name) {
  // 方法1:ES6 的 new.target
  if (!new.target) {
    throw new Error('必须使用 new 关键字调用 SafeAnimal');
  }
  
  // 方法2:严格模式
  // 'use strict'; // 在严格模式下,this 为 undefined,会报错
  
  this.name = name;
}

// 2. 箭头函数不能作为构造函数
const ArrowFunction = () => {
  // 箭头函数没有自己的 this,继承自外层作用域
  this.name = 'test'; // 这里的 this 取决于定义时的上下文
};

try {
  new ArrowFunction(); // TypeError: ArrowFunction is not a constructor
} catch (e) {
  console.log('箭头函数不能作为构造函数:', e.message);
}

// 3. 构造函数命名约定:首字母大写
function User(name) { /* 正确 */ }
function getUsers() { /* 正确,但这不是构造函数 */ }
function user(name) { /* 不推荐,容易混淆 */ }

二、ES5 构造函数详解

2.1 基本语法和原型方法

javascript 复制代码
// 定义构造函数
function Person(name, age) {
  // 实例属性:每个实例都有自己独立的属性值
  this.name = name;
  this.age = age;
  
  // 实例方法(不推荐的方式)
  // 问题:每个实例都会创建新的函数对象,浪费内存
  this.sayHello = function() {
    console.log('你好,我是' + this.name);
  };
  
  // 如果方法中需要使用构造函数内部的变量,可以使用闭包
  let privateCounter = 0; // 私有变量(只能通过闭包访问)
  
  this.getPrivateCount = function() {
    return privateCounter; // 通过闭包访问私有变量
  };
  
  this.incrementPrivateCount = function() {
    privateCounter++;
    return privateCounter;
  };
}

// 推荐:将方法定义在原型上
// 所有实例共享同一个函数,节省内存
Person.prototype.introduce = function() {
  return `我叫${this.name},今年${this.age}岁`;
};

// 还可以在原型上添加更多共享方法
Person.prototype.haveBirthday = function() {
  this.age++; // 修改实例属性
  console.log(`生日快乐!现在${this.name} ${this.age}岁了`);
  return this.age; // 返回新的年龄
};

// 创建实例
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);

// 实例方法对比
console.log(person1.sayHello === person2.sayHello); // false:每个实例有自己的函数
console.log(person1.introduce === person2.introduce); // true:共享原型上的函数

2.2 this 指向的详细分析

javascript 复制代码
// 构造函数中的 this 指向取决于调用方式
function Example(value) {
  console.log('this 指向:', this);
  console.log('this 的值:', value);
  this.value = value;
}

// 情况 1:作为构造函数调用(使用 new)
console.log('=== 情况1:new 调用 ===');
const obj1 = new Example('new调用');
// this 指向新创建的对象:Example {}
// value 被赋值为 'new调用'

// 情况 2:作为普通函数调用
console.log('=== 情况2:直接调用 ===');
Example('直接调用');
// 在非严格模式浏览器中,this 指向 window
// 相当于 window.value = '直接调用'
// 在严格模式或Node.js中,this 为 undefined

// 情况 3:作为对象的方法调用
console.log('=== 情况3:方法调用 ===');
const myObj = {
  name: '我的对象',
  method: Example
};
myObj.method('方法调用');
// this 指向调用者 myObj
// myObj.value = '方法调用'

// 情况 4:使用 call/apply/bind 改变 this
console.log('=== 情况4:call/apply/bind ===');
const customObj = { custom: '自定义对象' };
Example.call(customObj, 'call调用');    // this 指向 customObj
Example.apply(customObj, ['apply调用']); // this 指向 customObj
const boundExample = Example.bind(customObj);
boundExample('bind调用'); // this 指向 customObj

2.3 原型链机制的深入理解

javascript 复制代码
// 构造函数
function Animal(type) {
  this.type = type || '未知';
}

// 在原型上添加方法
Animal.prototype.makeSound = function() {
  return '动物发出声音';
};

// 创建实例
const dog = new Animal('犬科');

// 原型链关系验证
console.log('1. dog.__proto__ === Animal.prototype:', 
  dog.__proto__ === Animal.prototype); // true

console.log('2. Animal.prototype.__proto__ === Object.prototype:', 
  Animal.prototype.__proto__ === Object.prototype); // true

console.log('3. Object.prototype.__proto__ === null:', 
  Object.prototype.__proto__ === null); // true

console.log('4. dog 的原型链:', 
  dog.__proto__, // Animal.prototype
  dog.__proto__.__proto__, // Object.prototype
  dog.__proto__.__proto__.__proto__ // null
);

// 属性查找过程
console.log('5. dog.type 查找过程:');
// 1. 在 dog 对象自身查找 type 属性 -> 找到 '犬科'
console.log('  dog.hasOwnProperty("type"):', dog.hasOwnProperty('type')); // true

console.log('6. dog.makeSound 查找过程:');
// 1. 在 dog 对象自身查找 makeSound 属性 -> 未找到
// 2. 沿着原型链查找 dog.__proto__ (Animal.prototype) -> 找到
console.log('  dog.hasOwnProperty("makeSound"):', dog.hasOwnProperty('makeSound')); // false
console.log('  Animal.prototype.hasOwnProperty("makeSound"):', 
  Animal.prototype.hasOwnProperty('makeSound')); // true

console.log('7. dog.toString 查找过程:');
// 1. dog 自身没有 toString
// 2. Animal.prototype 也没有 toString
// 3. Object.prototype 有 toString
console.log('  dog.toString === Object.prototype.toString:', 
  dog.toString === Object.prototype.toString); // true

// instanceof 的工作原理
console.log('8. instanceof 检查:');
console.log('  dog instanceof Animal:', dog instanceof Animal); // true
console.log('  dog instanceof Object:', dog instanceof Object); // true
console.log('  dog instanceof Array:', dog instanceof Array); // false

// constructor 属性
console.log('9. constructor 属性:');
console.log('  dog.constructor === Animal:', dog.constructor === Animal); // true
// 注意:constructor 属性来自原型链,不是实例自身的属性
console.log('  dog.hasOwnProperty("constructor"):', dog.hasOwnProperty('constructor')); // false

三、ES6 Class 详解

3.1 基本类定义:语法糖背后的原理

javascript 复制代码
// ES6 Class 是构造函数的语法糖,更清晰易读
class Person {
  // 构造函数:初始化实例
  // 对应 ES5 的构造函数 function Person(name, age) { ... }
  constructor(name, age) {
    // 实例属性
    this.name = name;
    this.age = age;
    
    // 可以在构造函数中定义实例方法(但不推荐,与 ES5 构造函数相同的问题)
    this.instanceMethod = function() {
      return '这是实例方法';
    };
  }
  
  // 实例方法:自动添加到 Person.prototype 上
  // 对应 ES5 的 Person.prototype.sayHello = function() { ... }
  sayHello() {
    return `你好,我是${this.name}`;
  }
  
  // Getter:当做属性访问时自动调用
  // 例如:person.info 会调用这个方法
  get info() {
    return `${this.name} - ${this.age}岁`;
  }
  
  // Setter:当给属性赋值时自动调用
  // 例如:person.info = '王五,28' 会调用这个方法
  set info(value) {
    const [name, age] = value.split(',');
    this.name = name.trim();
    this.age = parseInt(age);
  }
  
  // 静态方法:属于类本身,实例不能调用
  // 对应 ES5 的 Person.createDefault = function() { ... }
  static createDefault() {
    // 静态方法中的 this 指向类本身(Person),不是实例
    return new Person('匿名', 0);
  }
  
  // 静态属性(ES2022+):属于类本身的属性
  // 对应 ES5 的 Person.species = '人类'
  static species = '人类';
  
  // 私有字段(ES2022+):以 # 开头,只能在类内部访问
  #secret = '这是我的秘密';
  
  // 访问私有字段的方法
  getSecret() {
    return this.#secret;
  }
  
  // 修改私有字段的方法
  setSecret(newSecret) {
    this.#secret = newSecret;
  }
}

// 使用示例
const person = new Person('张三', 25);

console.log('实例方法调用:', person.sayHello()); // 你好,我是张三
console.log('Getter 访问:', person.info); // 张三 - 25岁

// 使用 Setter
person.info = '李四,30';
console.log('Setter 修改后:', person.name, person.age); // 李四 30

// 静态方法调用
const defaultPerson = Person.createDefault();
console.log('静态方法创建:', defaultPerson.name); // 匿名

// 静态属性访问
console.log('静态属性:', Person.species); // 人类

// 私有字段访问
console.log('私有字段:', person.getSecret()); // 这是我的秘密
person.setSecret('新秘密');
console.log('修改后的私有字段:', person.getSecret()); // 新秘密

// 无法直接访问私有字段
// console.log(person.#secret); // 语法错误:私有字段只能在类内部访问

// 验证 Class 本质是函数
console.log('typeof Person:', typeof Person); // function
console.log('Person.prototype:', Person.prototype); // 包含 sayHello 等方法
console.log('person instanceof Person:', person instanceof Person); // true

3.2 类继承:extends 和 super

javascript 复制代码
// 父类(基类)
class Animal {
  constructor(name, type = '动物') {
    // 父类的实例属性
    this.name = name;
    this.type = type;
    this.energy = 100;
  }
  
  // 父类的方法
  speak() {
    return `${this.name} 发出声音`;
  }
  
  eat(food) {
    this.energy += 10;
    return `${this.name} 吃了 ${food},能量增加到 ${this.energy}`;
  }
  
  // 静态方法也可以被继承
  static isAnimal(obj) {
    return obj instanceof Animal;
  }
  
  // 静态属性
  static kingdom = '动物界';
}

// 子类(派生类)
class Dog extends Animal {
  // 子类的构造函数
  constructor(name, breed) {
    // 必须首先调用 super(),相当于 Animal.call(this, name)
    // super() 会:
    // 1. 调用父类的构造函数
    // 2. 将父类的实例属性和方法绑定到 this(子类的实例)
    super(name, '犬科');
    
    // 子类特有的属性
    this.breed = breed;
    this.tricks = [];
  }
  
  // 重写(覆盖)父类方法
  speak() {
    // 可以调用父类的方法
    const parentSound = super.speak();
    return `${parentSound},具体是汪汪叫!`;
  }
  
  // 新增子类特有的方法
  learnTrick(trick) {
    this.tricks.push(trick);
    return `${this.name} 学会了 ${trick}`;
  }
  
  performTricks() {
    if (this.tricks.length === 0) {
      return `${this.name} 还不会任何技巧`;
    }
    return `${this.name} 表演技巧: ${this.tricks.join(', ')}`;
  }
  
  // 静态方法继承
  // 子类可以有自己的静态方法,也可以覆盖父类的静态方法
  static createPuppy(name, breed) {
    const puppy = new Dog(name, breed);
    puppy.age = '幼犬';
    return puppy;
  }
}

// 使用继承
const myDog = new Dog('Buddy', '金毛寻回犬');

console.log('继承的属性:', myDog.name, myDog.type, myDog.breed); // Buddy 犬科 金毛寻回犬
console.log('重写的方法:', myDog.speak()); // Buddy 发出声音,具体是汪汪叫!
console.log('父类方法:', myDog.eat('狗粮')); // Buddy 吃了 狗粮,能量增加到 110
console.log('子类方法:', myDog.learnTrick('握手')); // Buddy 学会了 握手
console.log('子类方法:', myDog.performTricks()); // Buddy 表演技巧: 握手

// 静态方法继承
console.log('静态方法:', Dog.isAnimal(myDog)); // true(从 Animal 继承)
console.log('静态属性:', Dog.kingdom); // 动物界(从 Animal 继承)

// 多层继承
class Puppy extends Dog {
  constructor(name, breed, ageInMonths) {
    super(name, breed);
    this.ageInMonths = ageInMonths;
  }
  
  // 可以重写祖父类的方法
  speak() {
    return `${this.name} 小声叫:呜~呜~`;
  }
}

const puppy = new Puppy('小不点', '拉布拉多', 3);
console.log('多层继承:', puppy.speak()); // 小不点 小声叫:呜~呜~
console.log('instanceof 检查:');
console.log('  puppy instanceof Puppy:', puppy instanceof Puppy); // true
console.log('  puppy instanceof Dog:', puppy instanceof Dog); // true
console.log('  puppy instanceof Animal:', puppy instanceof Animal); // true
console.log('  puppy instanceof Object:', puppy instanceof Object); // true

3.3 私有属性和方法:真正的封装

javascript 复制代码
// ES2022 引入了真正的私有字段和方法
class BankAccount {
  // 私有属性:以 # 开头,只能在类内部访问
  #balance = 0;
  #transactionHistory = [];
  #accountNumber;
  
  // 公共属性
  owner;
  accountType;
  
  constructor(owner, initialBalance = 0, accountType = '储蓄账户') {
    this.owner = owner;
    this.accountType = accountType;
    
    // 调用私有方法生成账号
    this.#accountNumber = this.#generateAccountNumber();
    
    // 调用私有方法验证并设置初始余额
    if (initialBalance > 0) {
      this.#deposit(initialBalance, '初始存款');
    }
  }
  
  // 私有方法:只能内部调用
  #generateAccountNumber() {
    // 生成随机的账号
    const randomPart = Math.random().toString(36).substring(2, 10).toUpperCase();
    const timestamp = Date.now().toString().substring(8);
    return `ACC${timestamp}${randomPart}`;
  }
  
  #validateAmount(amount) {
    if (typeof amount !== 'number' || isNaN(amount)) {
      throw new Error('金额必须是数字');
    }
    if (amount <= 0) {
      throw new Error('金额必须大于0');
    }
    return true;
  }
  
  #addTransaction(type, amount, description) {
    const transaction = {
      type,
      amount,
      description,
      date: new Date().toISOString(),
      balance: this.#balance
    };
    this.#transactionHistory.push(transaction);
  }
  
  // 内部存款方法
  #deposit(amount, description = '存款') {
    if (this.#validateAmount(amount)) {
      this.#balance += amount;
      this.#addTransaction('存款', amount, description);
      return true;
    }
    return false;
  }
  
  // 公共存款方法
  deposit(amount, description = '存款') {
    const success = this.#deposit(amount, description);
    if (success) {
      console.log(`${description}成功,金额: ${amount},当前余额: ${this.#balance}`);
    }
    return success;
  }
  
  // 取款
  withdraw(amount, description = '取款') {
    if (!this.#validateAmount(amount)) {
      return false;
    }
    
    if (amount > this.#balance) {
      console.error('余额不足');
      return false;
    }
    
    this.#balance -= amount;
    this.#addTransaction('取款', amount, description);
    console.log(`${description}成功,金额: ${amount},当前余额: ${this.#balance}`);
    return true;
  }
  
  // Getter:提供对私有属性的受控访问
  get balance() {
    return this.#balance;
  }
  
  get accountInfo() {
    // 返回账户信息(不包含私有数据)
    return {
      owner: this.owner,
      accountType: this.accountType,
      accountNumber: this.#accountNumber, // 可以公开账号
      balance: this.#balance,
      transactionCount: this.#transactionHistory.length
    };
  }
  
  // 提供交易历史查询(可以控制返回的数据)
  getRecentTransactions(count = 5) {
    return this.#transactionHistory
      .slice(-count) // 取最近的 count 条记录
      .map(t => ({
        type: t.type,
        amount: t.amount,
        description: t.description,
        date: t.date
        // 不返回余额信息
      }));
  }
  
  // 静态方法:工具函数
  static validateAccountNumber(number) {
    return /^ACC\d+[A-Z0-9]+$/.test(number);
  }
}

// 使用示例
const account = new BankAccount('张三', 1000);

console.log('账户信息:', account.accountInfo);

account.deposit(500, '工资');
account.withdraw(200, '购物');

console.log('当前余额:', account.balance); // 1300
console.log('最近交易:', account.getRecentTransactions(2));

// 无法直接访问私有属性
// console.log(account.#balance); // 语法错误
// console.log(account.#transactionHistory); // 语法错误

// 私有字段是真正私有的
console.log('私有字段检测:');
console.log('  #balance in account:', '#balance' in account); // false
console.log('  无法通过常规方式访问私有字段');

// 静态方法使用
console.log('验证账号:', BankAccount.validateAccountNumber('ACC123456ABC'));

四、Class vs 构造函数详细对比

4.1 语法对比

javascript 复制代码
// ==================== ES5 构造函数 ====================
function ES5Person(name, age) {
  // 实例属性
  this.name = name;
  this.age = age;
  
  // 实例方法(不推荐,每个实例都有独立的函数)
  this.sayHello = function() {
    console.log('ES5: 你好,' + this.name);
  };
}

// 原型方法(推荐)
ES5Person.prototype.introduce = function() {
  return `ES5: 我是${this.name},${this.age}岁`;
};

// 静态方法
ES5Person.createDefault = function() {
  return new ES5Person('默认用户', 0);
};

// 继承实现
function ES5Employee(name, age, position) {
  // 调用父类构造函数
  ES5Person.call(this, name, age);
  this.position = position;
}

// 设置原型链实现继承
ES5Employee.prototype = Object.create(ES5Person.prototype);
ES5Employee.prototype.constructor = ES5Employee;

// 子类方法
ES5Employee.prototype.work = function() {
  return `ES5: ${this.name} 正在工作,职位是 ${this.position}`;
};

// ==================== ES6 Class ====================
class ES6Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 实例方法(自动添加到原型)
  sayHello() {
    console.log('ES6: 你好,' + this.name);
  }
  
  introduce() {
    return `ES6: 我是${this.name},${this.age}岁`;
  }
  
  // 静态方法
  static createDefault() {
    return new ES6Person('默认用户', 0);
  }
}

// 继承(语法更简洁)
class ES6Employee extends ES6Person {
  constructor(name, age, position) {
    super(name, age); // 调用父类构造函数
    this.position = position;
  }
  
  work() {
    return `ES6: ${this.name} 正在工作,职位是 ${this.position}`;
  }
}

// ==================== 对比测试 ====================
const es5Instance = new ES5Person('张三', 25);
const es6Instance = new ES6Person('李四', 30);

const es5Employee = new ES5Employee('王五', 28, '工程师');
const es6Employee = new ES6Employee('赵六', 32, '经理');

console.log('ES5 Person:', es5Instance.introduce());
console.log('ES6 Person:', es6Instance.introduce());

console.log('ES5 Employee:', es5Employee.introduce(), es5Employee.work());
console.log('ES6 Employee:', es6Employee.introduce(), es6Employee.work());

console.log('静态方法调用:');
console.log('ES5:', ES5Person.createDefault().name);
console.log('ES6:', ES6Person.createDefault().name);

// Class 本质验证
console.log('Class 本质:', typeof ES6Person); // function
console.log('Class 构造函数:', ES6Person === ES6Person.prototype.constructor); // true

4.2 特性对比表

javascript 复制代码
console.log('==================== ES5构造函数 vs ES6 Class 对比 ====================');

// 创建对比函数
function compareFeatures() {
  const features = [
    {
      feature: '定义方式',
      es5: 'function Person() {}',
      es6: 'class Person {}',
      note: 'Class 更接近传统面向对象语法'
    },
    {
      feature: '方法定义',
      es5: 'Person.prototype.method = function() {}',
      es6: '直接在类中定义 method() {}',
      note: 'Class 方法自动添加到原型,语法更简洁'
    },
    {
      feature: '构造函数',
      es5: 'function Person() { /* 构造函数体 */ }',
      es6: 'constructor() { /* 构造函数体 */ }',
      note: 'Class 使用明确的 constructor 方法'
    },
    {
      feature: '继承实现',
      es5: 'Child.prototype = Object.create(Parent.prototype)',
      es6: 'class Child extends Parent {}',
      note: 'Class 使用 extends 关键字,更直观'
    },
    {
      feature: 'super调用',
      es5: 'Parent.call(this, args)',
      es6: 'super(args)',
      note: 'Class 使用 super() 调用父类构造函数'
    },
    {
      feature: '静态方法',
      es5: 'Person.staticMethod = function() {}',
      es6: 'static staticMethod() {}',
      note: 'Class 使用 static 关键字定义静态方法'
    },
    {
      feature: '静态属性',
      es5: 'Person.staticProp = value',
      es6: 'static staticProp = value',
      note: 'ES6+ 支持类内部的静态属性定义'
    },
    {
      feature: '私有成员',
      es5: '约定使用 _private (非真正私有)',
      es6: '#private (ES2022+,真正私有)',
      note: 'Class 支持真正的私有字段和方法'
    },
    {
      feature: '提升(Hoisting)',
      es5: '函数提升,可在定义前调用',
      es6: '不提升,有暂时性死区(TDZ)',
      note: 'Class 必须先定义后使用'
    },
    {
      feature: '原型访问',
      es5: '可直接修改 Person.prototype',
      es6: '不能直接修改,语法更严格',
      note: 'Class 提供了更严格的封装'
    }
  ];
  
  console.table(features);
}

// 调用对比函数
compareFeatures();

4.3 兼容性和转换

javascript 复制代码
// Babel 如何将 ES6 Class 转换为 ES5 代码
// 假设有 ES6 Class:
/*
class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    return `Hello, ${this.name}`;
  }
  
  static create() {
    return new Person('Default');
  }
}
*/

// Babel 转换后的 ES5 代码大致如下:
function Person(name) {
  // 类检查(确保通过 new 调用)
  if (!(this instanceof Person)) {
    throw new TypeError("Cannot call a class as a function");
  }
  
  this.name = name;
}

// 实例方法
Object.defineProperty(Person.prototype, "sayHello", {
  value: function sayHello() {
    return "Hello, " + this.name;
  },
  enumerable: false, // 不可枚举
  writable: true,
  configurable: true
});

// 静态方法
Object.defineProperty(Person, "create", {
  value: function create() {
    return new Person('Default');
  },
  enumerable: false, // 不可枚举
  writable: true,
  configurable: true
});

// 验证转换结果
console.log('Babel 转换验证:');
const person = new Person('Test');
console.log('实例方法:', person.sayHello()); // Hello, Test
console.log('静态方法:', Person.create().name); // Default

// 重要区别:Class 方法默认不可枚举
const es5Func = function() {};
es5Func.prototype.method = function() {};
console.log('ES5 方法可枚举:', Object.keys(es5Func.prototype)); // ['method']

class ES6Class {
  method() {}
}
console.log('ES6 Class 方法不可枚举:', Object.keys(ES6Class.prototype)); // []

五、最佳实践总结

5.1 何时使用 Class vs 构造函数

javascript 复制代码
// 场景1:现代项目 -> 使用 ES6+ Class
class ModernUser {
  #id; // 私有字段
  
  constructor(name, email) {
    this.name = name;
    this.email = email;
    this.#id = this.#generateId();
  }
  
  #generateId() {
    return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
  
  sendEmail(subject, content) {
    // 发送邮件逻辑
    console.log(`发送邮件到 ${this.email}: ${subject}`);
  }
  
  // Getter/Setter 提供更好的封装
  get id() {
    return this.#id;
  }
  
  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

// 场景2:需要兼容旧浏览器 -> 使用 ES5 构造函数 + polyfill
function LegacyUser(name, email) {
  // 兼容性检查
  if (!(this instanceof LegacyUser)) {
    console.warn('LegacyUser 应该使用 new 关键字调用');
    return new LegacyUser(name, email);
  }
  
  // 公共属性
  this.name = name;
  this.email = email;
  
  // 私有模式(使用闭包)
  var id = 'user_' + Date.now();
  
  // 公共方法访问私有数据
  this.getId = function() {
    return id;
  };
}

// 原型方法
LegacyUser.prototype.sendEmail = function(subject, content) {
  console.log(`发送邮件到 ${this.email}: ${subject}`);
};

// 静态方法
LegacyUser.validateEmail = function(email) {
  var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};

// 场景3:小型工具函数 -> 使用工厂函数
function createUser(name, email) {
  // 返回普通对象,不使用 this 或 new
  const user = {
    name,
    email,
    sendEmail(subject, content) {
      console.log(`发送邮件到 ${this.email}: ${subject}`);
    }
  };
  
  return user;
}

// 使用建议
console.log('使用建议:');
console.log('1. 现代项目(ES6+): 优先使用 Class');
console.log('2. 需要兼容性: 使用构造函数 + polyfill');
console.log('3. 简单对象: 使用工厂函数或对象字面量');
console.log('4. 库/框架开发: 根据目标环境选择');

5.2 继承的最佳实践

javascript 复制代码
// 原则1:优先使用组合 over 继承
console.log('=== 组合 vs 继承 ===');

// 不推荐:过度使用继承
class Animal {
  eat() { return 'eating'; }
}

class Dog extends Animal {
  bark() { return 'barking'; }
}

class RobotDog extends Dog {
  // 问题:机器人狗需要吃东西吗?
  charge() { return 'charging'; }
}

// 推荐:使用组合
class BetterDog {
  constructor() {
    this.animalBehaviors = new AnimalBehaviors();
    this.soundBehaviors = new SoundBehaviors();
  }
  
  eat() { return this.animalBehaviors.eat(); }
  bark() { return this.soundBehaviors.bark(); }
}

class BetterRobotDog {
  constructor() {
    this.movementBehaviors = new MovementBehaviors();
    this.soundBehaviors = new SoundBehaviors();
  }
  
  move() { return this.movementBehaviors.move(); }
  beep() { return this.soundBehaviors.beep(); }
}

// 原则2:使用抽象基类
class Shape {
  constructor(name) {
    if (new.target === Shape) {
      throw new Error('Shape 是抽象类,不能直接实例化');
    }
    this.name = name;
  }
  
  // 抽象方法(子类必须实现)
  area() {
    throw new Error('子类必须实现 area 方法');
  }
  
  // 具体方法
  display() {
    return `${this.name} 的面积是 ${this.area()}`;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super('圆形');
    this.radius = radius;
  }
  
  area() {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super('矩形');
    this.width = width;
    this.height = height;
  }
  
  area() {
    return this.width * this.height;
  }
}

// 使用
const circle = new Circle(5);
console.log(circle.display()); // 圆形 的面积是 78.53981633974483

// 不能直接实例化抽象类
try {
  new Shape('抽象形状');
} catch (e) {
  console.log('抽象类保护:', e.message);
}

5.3 封装和数据保护

javascript 复制代码
// 多层封装策略
class SecureSystem {
  // 公有属性
  publicInfo = '公开信息';
  
  // 保护属性(约定,非强制)
  _protectedInfo = '保护信息(约定)';
  
  // 私有属性(真正私有)
  #privateInfo = '私有信息(真正私有)';
  
  // 私有字段的访问器
  getPrivateInfo() {
    // 可以添加访问控制逻辑
    return this.#privateInfo;
  }
  
  setPrivateInfo(value) {
    // 可以添加验证逻辑
    if (typeof value !== 'string') {
      throw new Error('必须是字符串');
    }
    this.#privateInfo = value;
  }
  
  // 保护属性的访问器
  getProtectedInfo() {
    // 保护属性通常用于子类访问
    return this._protectedInfo;
  }
  
  // 静态私有字段
  static #secretKey = '静态私有密钥';
  
  static getSecretKey() {
    // 静态私有字段的访问
    return SecureSystem.#secretKey;
  }
}

// 使用示例
const system = new SecureSystem();

console.log('公有属性:', system.publicInfo); // 可以直接访问
console.log('保护属性(约定):', system._protectedInfo); // 可以访问,但不建议
console.log('保护属性(通过方法):', system.getProtectedInfo()); // 建议方式
console.log('私有属性:', system.getPrivateInfo()); // 必须通过方法访问

// 静态私有字段访问
console.log('静态私有字段:', SecureSystem.getSecretKey());

// 封装验证
console.log('封装检查:');
console.log('  直接访问私有字段?', '#privateInfo' in system); // false
console.log('  私有字段在原型上?', Object.getPrototypeOf(system).hasOwnProperty('#privateInfo')); // false

5.4 错误处理和验证

javascript 复制代码
// 健壮的类设计
class ValidatedUser {
  constructor(name, email, age) {
    // 参数验证
    if (!name || typeof name !== 'string') {
      throw new TypeError('name 必须是非空字符串');
    }
    
    if (!email || !this.constructor.validateEmail(email)) {
      throw new TypeError('email 格式不正确');
    }
    
    if (age !== undefined && (typeof age !== 'number' || age < 0)) {
      throw new TypeError('age 必须是正数或 undefined');
    }
    
    // 初始化属性
    this.name = name;
    this.email = email;
    this.age = age;
    
    // 设置创建时间
    this.createdAt = new Date();
    
    // 生成唯一ID
    this.id = this.constructor.generateId();
  }
  
  // Getter 和 Setter 用于验证
  set name(value) {
    if (!value || typeof value !== 'string') {
      throw new TypeError('name 必须是非空字符串');
    }
    this._name = value.trim();
  }
  
  get name() {
    return this._name;
  }
  
  set email(value) {
    if (!value || !this.constructor.validateEmail(value)) {
      throw new TypeError('email 格式不正确');
    }
    this._email = value.toLowerCase();
  }
  
  get email() {
    return this._email;
  }
  
  // 静态验证方法
  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
  
  // 静态生成方法
  static generateId() {
    return `user_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
  }
  
  // 实例方法
  updateProfile(updates) {
    // 验证更新数据
    const validUpdates = {};
    
    if (updates.name !== undefined) {
      validUpdates.name = updates.name;
    }
    
    if (updates.email !== undefined) {
      validUpdates.email = updates.email;
    }
    
    if (updates.age !== undefined) {
      validUpdates.age = updates.age;
    }
    
    // 应用更新
    Object.assign(this, validUpdates);
    
    // 记录更新时间
    this.updatedAt = new Date();
    
    return this;
  }
  
  // 序列化方法
  toJSON() {
    return {
      id: this.id,
      name: this.name,
      email: this.email,
      age: this.age,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt
    };
  }
}

// 使用示例
try {
  const user = new ValidatedUser(' 张三 ', 'zhangsan@example.com', 25);
  console.log('用户创建成功:', user.toJSON());
  
  // 更新测试
  user.updateProfile({ name: '张三丰', age: 26 });
  console.log('更新后的用户:', user.toJSON());
  
  // 错误测试
  const badUser = new ValidatedUser('', 'invalid-email', -1);
} catch (error) {
  console.log('创建用户失败:', error.message);
}

// 验证 Getter/Setter
const testUser = new ValidatedUser('李四', 'lisi@example.com');
testUser.name = ' 李四修改 ';
console.log('Getter/Setter 测试:', testUser.name); // '李四修改'

六、核心概念梳理

6.1 原型链的完整图示

javascript 复制代码
// 构建完整的原型链示例
function GrandParent() {
  this.grandParentProp = '祖父属性';
}

GrandParent.prototype.grandParentMethod = function() {
  return '祖父方法';
};

function Parent() {
  GrandParent.call(this); // 继承实例属性
  this.parentProp = '父属性';
}

// 继承原型方法
Parent.prototype = Object.create(GrandParent.prototype);
Parent.prototype.constructor = Parent;

Parent.prototype.parentMethod = function() {
  return '父方法';
};

function Child() {
  Parent.call(this); // 继承实例属性
  this.childProp = '子属性';
}

// 继承原型方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.childMethod = function() {
  return '子方法';
};

// 创建实例
const instance = new Child();

console.log('=== 原型链完整图示 ===');
console.log('1. 实例自身的属性:');
console.log('   instance.childProp:', instance.childProp);
console.log('   instance.parentProp:', instance.parentProp);
console.log('   instance.grandParentProp:', instance.grandParentProp);

console.log('\n2. 原型链查找:');
console.log('   instance.__proto__ === Child.prototype:', 
  instance.__proto__ === Child.prototype);
console.log('   instance.__proto__.__proto__ === Parent.prototype:', 
  instance.__proto__.__proto__ === Parent.prototype);
console.log('   instance.__proto__.__proto__.__proto__ === GrandParent.prototype:', 
  instance.__proto__.__proto__.__proto__ === GrandParent.prototype);
console.log('   instance.__proto__.__proto__.__proto__.__proto__ === Object.prototype:', 
  instance.__proto__.__proto__.__proto__.__proto__ === Object.prototype);
console.log('   instance.__proto__.__proto__.__proto__.__proto__.__proto__ === null:', 
  instance.__proto__.__proto__.__proto__.__proto__.__proto__ === null);

console.log('\n3. 方法调用(原型链查找):');
console.log('   instance.childMethod():', instance.childMethod());
console.log('   instance.parentMethod():', instance.parentMethod());
console.log('   instance.grandParentMethod():', instance.grandParentMethod());
console.log('   instance.toString():', instance.toString()); // 来自 Object.prototype

console.log('\n4. instanceof 检查:');
console.log('   instance instanceof Child:', instance instanceof Child);
console.log('   instance instanceof Parent:', instance instanceof Parent);
console.log('   instance instanceof GrandParent:', instance instanceof GrandParent);
console.log('   instance instanceof Object:', instance instanceof Object);

// 可视化原型链
console.log('\n5. 原型链可视化:');
let current = instance;
let level = 0;
while (current) {
  console.log(`   ${'  '.repeat(level)}Level ${level}:`, 
    current.constructor ? current.constructor.name : 'null');
  current = Object.getPrototypeOf(current);
  level++;
}

6.2 new 关键字的模拟实现(详细版)

javascript 复制代码
// 详细模拟 new 关键字的工作机制
function myNew(constructor, ...args) {
  console.log('=== myNew 模拟开始 ===');
  
  // 1. 创建新对象
  console.log('步骤1: 创建新的空对象');
  const obj = {};
  
  // 2. 设置原型链(指向构造函数的 prototype)
  console.log('步骤2: 设置原型链 (obj.__proto__ = constructor.prototype)');
  Object.setPrototypeOf(obj, constructor.prototype);
  
  // 3. 绑定 this 并执行构造函数
  console.log('步骤3: 执行构造函数,绑定 this');
  console.log('  构造函数参数:', args);
  const result = constructor.apply(obj, args);
  
  // 4. 处理返回值
  console.log('步骤4: 处理返回值');
  if (result && (typeof result === 'object' || typeof result === 'function')) {
    console.log('  构造函数返回对象/函数,使用返回值:', result);
    return result;
  } else {
    console.log('  构造函数返回非对象或未返回,使用新创建的对象:', obj);
    return obj;
  }
}

// 测试构造函数
function TestConstructor(name, value) {
  this.name = name;
  this.value = value;
  console.log('  构造函数内部 this:', this);
  
  // 测试返回值
  // return { custom: '自定义对象' }; // 如果取消注释,将返回这个对象
}

// 添加原型方法
TestConstructor.prototype.greet = function() {
  return `Hello from ${this.name}`;
};

// 使用自定义 new
console.log('\n=== 测试 myNew ===');
const instance1 = myNew(TestConstructor, '实例1', 100);
console.log('创建的结果:', instance1);
console.log('原型方法:', instance1.greet());
console.log('instanceof 检查:', instance1 instanceof TestConstructor);

// 与原生 new 对比
console.log('\n=== 原生 new 对比 ===');
const instance2 = new TestConstructor('实例2', 200);
console.log('创建的结果:', instance2);
console.log('原型方法:', instance2.greet());
console.log('instanceof 检查:', instance2 instanceof TestConstructor);

6.3 完整的面向对象系统示例

javascript 复制代码
// 完整的电子商务系统示例
console.log('=== 完整的电子商务系统 ===');

// 1. 产品类
class Product {
  #id;
  #stock;
  
  constructor(name, price, description = '', initialStock = 0) {
    // 参数验证
    if (!name || typeof name !== 'string') {
      throw new Error('产品名称必须是非空字符串');
    }
    
    if (typeof price !== 'number' || price < 0) {
      throw new Error('价格必须是正数');
    }
    
    // 初始化属性
    this.name = name;
    this.price = price;
    this.description = description;
    this.#stock = Math.max(0, initialStock);
    this.#id = `prod_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    // 元数据
    this.createdAt = new Date();
    this.updatedAt = new Date();
  }
  
  // Getter 和 Setter
  get id() { return this.#id; }
  get stock() { return this.#stock; }
  
  // 库存管理
  addStock(quantity) {
    if (quantity <= 0) {
      throw new Error('添加的库存数量必须大于0');
    }
    this.#stock += quantity;
    this.updatedAt = new Date();
    return this.#stock;
  }
  
  removeStock(quantity) {
    if (quantity <= 0) {
      throw new Error('移除的库存数量必须大于0');
    }
    
    if (quantity > this.#stock) {
      throw new Error('库存不足');
    }
    
    this.#stock -= quantity;
    this.updatedAt = new Date();
    return this.#stock;
  }
  
  // 产品信息
  getInfo() {
    return {
      id: this.#id,
      name: this.name,
      price: this.price,
      description: this.description,
      stock: this.#stock,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt
    };
  }
  
  // 格式化显示
  display() {
    return `${this.name} - ¥${this.price.toFixed(2)} (库存: ${this.#stock})`;
  }
}

// 2. 购物车类
class ShoppingCart {
  #items = new Map(); // 使用 Map 存储商品和数量
  #discountCode = null;
  #taxRate = 0.1; // 税率 10%
  
  constructor(cartId) {
    this.cartId = cartId || `cart_${Date.now()}`;
    this.createdAt = new Date();
  }
  
  // 添加商品
  addItem(product, quantity = 1) {
    if (!(product instanceof Product)) {
      throw new Error('只能添加 Product 实例');
    }
    
    if (quantity <= 0) {
      throw new Error('数量必须大于0');
    }
    
    if (quantity > product.stock) {
      throw new Error('商品库存不足');
    }
    
    const currentQuantity = this.#items.get(product) || 0;
    this.#items.set(product, currentQuantity + quantity);
    
    // 从库存中移除
    product.removeStock(quantity);
    
    return this;
  }
  
  // 移除商品
  removeItem(product, quantity = 1) {
    if (!this.#items.has(product)) {
      throw new Error('购物车中不存在该商品');
    }
    
    const currentQuantity = this.#items.get(product);
    
    if (quantity >= currentQuantity) {
      // 完全移除
      this.#items.delete(product);
      // 退回库存
      product.addStock(currentQuantity);
    } else {
      // 部分移除
      this.#items.set(product, currentQuantity - quantity);
      // 退回库存
      product.addStock(quantity);
    }
    
    return this;
  }
  
  // 计算小计
  getSubtotal() {
    let subtotal = 0;
    
    for (const [product, quantity] of this.#items) {
      subtotal += product.price * quantity;
    }
    
    return subtotal;
  }
  
  // 计算折扣
  getDiscount() {
    if (!this.#discountCode) {
      return 0;
    }
    
    // 这里可以实现复杂的折扣逻辑
    const discountRules = {
      'SAVE10': 0.1, // 10% 折扣
      'SAVE20': 0.2, // 20% 折扣
      'FIXED50': 50  // 固定减 50
    };
    
    const rule = discountRules[this.#discountCode];
    if (!rule) {
      return 0;
    }
    
    const subtotal = this.getSubtotal();
    
    if (rule <= 1) {
      // 百分比折扣
      return subtotal * rule;
    } else {
      // 固定金额折扣
      return Math.min(rule, subtotal);
    }
  }
  
  // 计算税
  getTax() {
    const subtotal = this.getSubtotal();
    const discount = this.getDiscount();
    return (subtotal - discount) * this.#taxRate;
  }
  
  // 计算总计
  getTotal() {
    const subtotal = this.getSubtotal();
    const discount = this.getDiscount();
    const tax = this.getTax();
    
    return subtotal - discount + tax;
  }
  
  // 应用折扣码
  applyDiscount(code) {
    // 验证折扣码逻辑
    const validCodes = ['SAVE10', 'SAVE20', 'FIXED50'];
    
    if (validCodes.includes(code)) {
      this.#discountCode = code;
      return true;
    }
    
    return false;
  }
  
  // 清空购物车
  clear() {
    // 退回所有商品库存
    for (const [product, quantity] of this.#items) {
      product.addStock(quantity);
    }
    
    this.#items.clear();
    this.#discountCode = null;
    
    return this;
  }
  
  // 获取购物车详情
  getDetails() {
    const items = [];
    
    for (const [product, quantity] of this.#items) {
      items.push({
        product: product.getInfo(),
        quantity,
        total: product.price * quantity
      });
    }
    
    const subtotal = this.getSubtotal();
    const discount = this.getDiscount();
    const tax = this.getTax();
    const total = this.getTotal();
    
    return {
      cartId: this.cartId,
      items,
      summary: {
        subtotal,
        discount,
        tax,
        total
      },
      discountCode: this.#discountCode,
      createdAt: this.createdAt,
      itemCount: items.length
    };
  }
  
  // 结账
  checkout() {
    if (this.#items.size === 0) {
      throw new Error('购物车为空,无法结账');
    }
    
    const orderDetails = this.getDetails();
    const orderId = `order_${Date.now()}`;
    
    // 在实际应用中,这里会调用支付接口
    console.log(`=== 订单 ${orderId} 结账 ===`);
    console.log('订单详情:', orderDetails);
    
    // 清空购物车(但保留记录)
    const checkoutTime = new Date();
    
    return {
      orderId,
      ...orderDetails,
      checkoutTime,
      paymentStatus: 'pending' // 实际中会有支付状态
    };
  }
}

// 3. 用户类
class User {
  #password;
  #cart;
  
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.#password = this.#hashPassword(password);
    this.#cart = new ShoppingCart();
    this.orders = [];
    this.createdAt = new Date();
  }
  
  // 私有方法:密码哈希
  #hashPassword(password) {
    // 实际应用中应该使用 bcrypt 等安全哈希
    return `hashed_${password}_${Date.now()}`;
  }
  
  // 验证密码
  verifyPassword(password) {
    const hashed = this.#hashPassword(password);
    return this.#password === hashed;
  }
  
  // 获取购物车
  getCart() {
    return this.#cart;
  }
  
  // 添加订单
  addOrder(order) {
    this.orders.push({
      ...order,
      orderTime: new Date()
    });
  }
  
  // 获取用户信息
  getProfile() {
    return {
      username: this.username,
      email: this.email,
      orderCount: this.orders.length,
      totalSpent: this.orders.reduce((sum, order) => sum + order.summary.total, 0),
      createdAt: this.createdAt
    };
  }
}

// 4. 使用示例
console.log('\n=== 系统使用示例 ===');

// 创建商品
try {
  const laptop = new Product('笔记本电脑', 5999.99, '高性能游戏本', 10);
  const mouse = new Product('游戏鼠标', 299.99, '电竞游戏鼠标', 50);
  const keyboard = new Product('机械键盘', 499.99, 'RGB背光键盘', 30);
  
  console.log('创建的商品:');
  console.log('1.', laptop.display());
  console.log('2.', mouse.display());
  console.log('3.', keyboard.display());
  
  // 创建用户
  const user = new User('张三', 'zhangsan@example.com', 'password123');
  console.log('\n创建的用户:', user.getProfile());
  
  // 用户购物
  const cart = user.getCart();
  
  cart.addItem(laptop, 1);
  cart.addItem(mouse, 2);
  cart.addItem(keyboard, 1);
  
  console.log('\n购物车初始状态:');
  console.log('小计:', cart.getSubtotal().toFixed(2));
  console.log('商品数量:', cart.getDetails().itemCount);
  
  // 应用折扣
  cart.applyDiscount('SAVE10');
  console.log('\n应用 10% 折扣后:');
  console.log('折扣:', cart.getDiscount().toFixed(2));
  console.log('税:', cart.getTax().toFixed(2));
  console.log('总计:', cart.getTotal().toFixed(2));
  
  // 查看购物车详情
  console.log('\n购物车详情:', cart.getDetails());
  
  // 移除商品
  cart.removeItem(mouse, 1);
  console.log('\n移除1个鼠标后:');
  console.log('商品数量:', cart.getDetails().itemCount);
  console.log('总计:', cart.getTotal().toFixed(2));
  
  // 结账
  console.log('\n=== 结账流程 ===');
  const order = cart.checkout();
  user.addOrder(order);
  
  console.log('订单完成:', order.orderId);
  console.log('用户订单历史:', user.orders.length, '个订单');
  
  // 验证库存更新
  console.log('\n=== 库存验证 ===');
  console.log('笔记本电脑库存:', laptop.stock);
  console.log('鼠标库存:', mouse.stock);
  console.log('键盘库存:', keyboard.stock);
  
  // 清空购物车
  cart.clear();
  console.log('\n清空购物车后商品数量:', cart.getDetails().itemCount);
  
} catch (error) {
  console.error('系统错误:', error.message);
}

七、常见面试问题

7.1 构造函数和普通函数的区别?

javascript 复制代码
console.log('=== 面试问题1:构造函数和普通函数的区别 ===\n');

function demoFunction(value) {
  this.value = value;
  
  // 普通方法
  this.showValue = function() {
    return this.value;
  };
}

// 区别1:调用方式
console.log('区别1:调用方式不同');
console.log('普通函数调用:', demoFunction('直接调用')); // undefined(没有返回值)
console.log('在浏览器中,直接调用会使 this.value 成为全局变量');

// 区别2:this 指向
console.log('\n区别2:this 指向不同');
console.log('普通函数调用时,this 指向:');
console.log('  - 非严格模式:全局对象(window/global)');
console.log('  - 严格模式:undefined');

const obj = { method: demoFunction };
obj.method('方法调用');
console.log('  方法调用时,this 指向调用者:', obj.value); // '方法调用'

// 区别3:返回值
console.log('\n区别3:返回值不同');
const instance = new demoFunction('构造函数调用');
console.log('构造函数默认返回新对象:', instance);
console.log('构造函数可以显式返回对象,否则返回 this');

// 区别4:原型链
console.log('\n区别4:原型链设置');
console.log('构造函数创建的实例有原型链:', instance instanceof demoFunction);
console.log('普通函数调用没有创建原型链');

// 区别5:命名约定
console.log('\n区别5:命名约定');
console.log('构造函数通常首字母大写: Person, Car, Animal');
console.log('普通函数通常小写或驼峰: getData, calculateTotal');

7.2 Class 是真正的类吗?

javascript 复制代码
console.log('\n=== 面试问题2:Class 是真正的类吗? ===\n');

// 演示 Class 的本质
class ES6Class {
  constructor(value) {
    this.value = value;
  }
  
  method() {
    return this.value;
  }
  
  static staticMethod() {
    return '静态方法';
  }
}

console.log('1. Class 的本质是函数:');
console.log('   typeof ES6Class:', typeof ES6Class); // function
console.log('   ES6Class.prototype.constructor === ES6Class:', 
  ES6Class.prototype.constructor === ES6Class); // true

console.log('\n2. Class 是语法糖:');
console.log('   ES6 Class 编译成 ES5:');
console.log('   class Person {} -> function Person() {}');
console.log('   method() {} -> Person.prototype.method = function() {}');
console.log('   extends -> 原型链继承');

console.log('\n3. 与传统面向对象语言的对比:');
console.log('   JavaScript (基于原型):');
console.log('     - 使用原型链实现继承');
console.log('     - 没有真正的类(ES6 Class 是语法糖)');
console.log('     - 动态性:运行时可以修改原型');
console.log('   Java/C++ (基于类):');
console.log('     - 有真正的类概念');
console.log('     - 编译时确定类型');
console.log('     - 静态继承');

console.log('\n4. 验证 Class 的工作方式:');
const instance = new ES6Class('test');
console.log('   实例的原型:', Object.getPrototypeOf(instance) === ES6Class.prototype);
console.log('   方法在原型上:', ES6Class.prototype.hasOwnProperty('method'));

console.log('\n5. 结论:');
console.log('   JavaScript 的 Class 不是真正的类,而是基于原型的语法糖。');
console.log('   理解这一点对于掌握 JavaScript 面向对象编程至关重要。');

7.3 什么时候用继承?什么时候用组合?

javascript 复制代码
console.log('\n=== 面试问题3:继承 vs 组合 ===\n');

console.log('继承(is-a 关系):');
console.log('  当一个对象是另一个对象的特殊类型时使用');
console.log('  示例:Dog is an Animal');

// 继承示例
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  eat() {
    return `${this.name} 在吃东西`;
  }
}

class Dog extends Animal {
  bark() {
    return `${this.name} 汪汪叫`;
  }
}

console.log('  优点:代码复用,层次清晰');
console.log('  缺点:紧密耦合,可能过度设计');

console.log('\n组合(has-a 关系):');
console.log('  当一个对象包含另一个对象时使用');
console.log('  示例:Car has an Engine');

// 组合示例
class Engine {
  start() {
    return '引擎启动';
  }
}

class Car {
  constructor() {
    this.engine = new Engine();
  }
  
  start() {
    return `汽车:${this.engine.start()}`;
  }
}

console.log('  优点:灵活,松耦合,易于测试');
console.log('  缺点:需要更多代码,关系不直观');

console.log('\n继承的问题:');
console.log('  1. 脆弱基类问题:父类修改可能影响所有子类');
console.log('  2. 多重继承问题:JavaScript 不支持(但可以通过混入模拟)');
console.log('  3. 过度继承:可能导致复杂的继承链');

console.log('\n组合的优点:');
console.log('  1. 更灵活:可以在运行时改变行为');
console.log('  2. 更安全:修改组件不会影响其他部分');
console.log('  3. 更易测试:可以单独测试每个组件');

console.log('\n现代最佳实践:');
console.log('  - 优先使用组合 over 继承');
console.log('  - 继承只用于真正的 is-a 关系');
console.log('  - 使用接口/抽象类定义契约');

// 实际示例
console.log('\n实际示例:');

// 不好的继承设计
class Bird {
  fly() {
    return '飞';
  }
}

class Penguin extends Bird {
  // 企鹅不会飞,但继承了 fly 方法
  fly() {
    throw new Error('企鹅不会飞!');
  }
}

// 好的组合设计
class FlyingBehavior {
  fly() {
    return '飞';
  }
}

class SwimmingBehavior {
  swim() {
    return '游泳';
  }
}

class BetterBird {
  constructor() {
    this.flyingBehavior = new FlyingBehavior();
    this.swimmingBehavior = new SwimmingBehavior();
  }
  
  move() {
    return this.flyingBehavior.fly();
  }
}

class BetterPenguin {
  constructor() {
    this.swimmingBehavior = new SwimmingBehavior();
  }
  
  move() {
    return this.swimmingBehavior.swim();
  }
}

7.4 如何实现真正的私有属性?

javascript 复制代码
console.log('\n=== 面试问题4:实现私有属性 ===\n');

console.log('方法1:闭包(ES5 及之前)');
function ClosureExample() {
  // 私有变量
  let privateVar = '私有数据';
  let privateCounter = 0;
  
  // 公共方法访问私有数据
  this.getPrivateVar = function() {
    return privateVar;
  };
  
  this.incrementCounter = function() {
    return ++privateCounter;
  };
  
  // 公共属性
  this.publicVar = '公共数据';
}

const closureInstance = new ClosureExample();
console.log('  闭包私有数据:', closureInstance.getPrivateVar());
console.log('  无法直接访问:', closureInstance.privateVar); // undefined
console.log('  缺点:每个实例都有独立的方法,内存效率低');

console.log('\n方法2:Symbol(ES6)');
const _privateVar = Symbol('privateVar');
const _privateMethod = Symbol('privateMethod');

class SymbolExample {
  constructor() {
    this[_privateVar] = 'Symbol 私有数据';
    this.publicVar = '公共数据';
  }
  
  // 公共方法
  getPrivateData() {
    return this[_privateVar];
  }
  
  // "私有"方法
  [_privateMethod]() {
    return 'Symbol 私有方法';
  }
  
  callPrivateMethod() {
    return this[_privateMethod]();
  }
}

const symbolInstance = new SymbolExample();
console.log('  Symbol 私有数据:', symbolInstance.getPrivateData());
console.log('  Symbol 私有方法:', symbolInstance.callPrivateMethod());
console.log('  Symbol 缺点:可以通过 Object.getOwnPropertySymbols() 访问');

console.log('\n方法3:WeakMap(ES6)');
const privateData = new WeakMap();

class WeakMapExample {
  constructor() {
    // 存储私有数据
    privateData.set(this, {
      privateVar: 'WeakMap 私有数据',
      privateCounter: 0
    });
    
    this.publicVar = '公共数据';
  }
  
  // 公共方法访问私有数据
  getPrivateVar() {
    return privateData.get(this).privateVar;
  }
  
  incrementCounter() {
    const data = privateData.get(this);
    return ++data.privateCounter;
  }
}

const weakMapInstance = new WeakMapExample();
console.log('  WeakMap 私有数据:', weakMapInstance.getPrivateVar());
console.log('  WeakMap 优点:真正的私有,外部无法访问');
console.log('  WeakMap 缺点:语法复杂,调试困难');

console.log('\n方法4:# 语法(ES2022+,真正的私有字段)');
class HashExample {
  #privateVar = '真正的私有数据';
  #privateCounter = 0;
  
  constructor() {
    this.publicVar = '公共数据';
  }
  
  // 公共方法访问私有字段
  getPrivateVar() {
    return this.#privateVar;
  }
  
  incrementCounter() {
    return ++this.#privateCounter;
  }
  
  // 私有方法
  #privateMethod() {
    return '真正的私有方法';
  }
  
  callPrivateMethod() {
    return this.#privateMethod();
  }
}

const hashInstance = new HashExample();
console.log('  # 语法私有数据:', hashInstance.getPrivateVar());
console.log('  # 语法私有方法:', hashInstance.callPrivateMethod());
try {
  console.log('  直接访问:', hashInstance.#privateVar);
} catch (e) {
  console.log('  直接访问错误:', e.message);
}
console.log('  # 语法优点:语言原生支持,真正的私有');
console.log('  # 语法缺点:需要现代环境支持');

console.log('\n方法5:命名约定(最简单,但不安全)');
class ConventionExample {
  constructor() {
    this._privateVar = '约定私有数据'; // 下划线约定
    this.publicVar = '公共数据';
  }
  
  getPrivateVar() {
    return this._privateVar;
  }
}

const conventionInstance = new ConventionExample();
console.log('  约定私有数据:', conventionInstance.getPrivateVar());
console.log('  可以直接访问:', conventionInstance._privateVar);
console.log('  优点:简单直观');
console.log('  缺点:只是约定,不提供真正的保护');

console.log('\n总结:');
console.log('  1. 现代项目:使用 # 语法(ES2022+)');
console.log('  2. 需要兼容性:使用 WeakMap 或闭包');
console.log('  3. 简单场景:使用命名约定');
console.log('  4. 库开发:根据目标环境选择合适方法');

7.5 其他常见面试问题

javascript 复制代码
console.log('\n=== 其他常见面试问题 ===\n');

// 问题1:Object.create() 和 new 的区别
console.log('问题1:Object.create() 和 new 的区别');
function Parent(name) {
  this.name = name;
}

// 使用 new
const child1 = new Parent('new创建');
console.log('  new Parent():', child1.name, child1 instanceof Parent);

// 使用 Object.create
const child2 = Object.create(Parent.prototype);
Parent.call(child2, 'Object.create创建');
console.log('  Object.create():', child2.name, child2 instanceof Parent);

console.log('  区别:');
console.log('    - new: 创建新对象 + 调用构造函数');
console.log('    - Object.create: 只创建对象,不调用构造函数');

// 问题2:prototype 和 __proto__ 的区别
console.log('\n问题2:prototype 和 __proto__ 的区别');
function Example() {}
const obj = new Example();

console.log('  Example.prototype:', typeof Example.prototype); // object
console.log('  obj.__proto__:', typeof obj.__proto__); // object
console.log('  Example.prototype === obj.__proto__:', 
  Example.prototype === obj.__proto__); // true

console.log('  区别:');
console.log('    - prototype: 函数属性,用于构造函数');
console.log('    - __proto__: 对象属性,指向对象的原型');

// 问题3:如何实现多重继承
console.log('\n问题3:如何实现多重继承');
// 使用混入(Mixin)模式
const CanEat = {
  eat() {
    return `${this.name} 在吃东西`;
  }
};

const CanSleep = {
  sleep() {
    return `${this.name} 在睡觉`;
  }
};

class AnimalBase {
  constructor(name) {
    this.name = name;
  }
}

// 使用 Object.assign 实现混入
class Dog extends AnimalBase {
  constructor(name) {
    super(name);
  }
}

// 混入方法
Object.assign(Dog.prototype, CanEat, CanSleep);

const dog = new Dog('Buddy');
console.log('  混入继承:', dog.eat(), dog.sleep());

// 问题4:如何判断属性是自身的还是继承的
console.log('\n问题4:判断自身属性 vs 继承属性');
function Test() {
  this.ownProp = '自身属性';
}
Test.prototype.inheritedProp = '继承属性';

const testObj = new Test();

console.log('  hasOwnProperty("ownProp"):', testObj.hasOwnProperty('ownProp')); // true
console.log('  hasOwnProperty("inheritedProp"):', testObj.hasOwnProperty('inheritedProp')); // false
console.log('  "ownProp" in testObj:', 'ownProp' in testObj); // true
console.log('  "inheritedProp" in testObj:', 'inheritedProp' in testObj); // true

// 问题5:如何防止对象被修改
console.log('\n问题5:防止对象被修改');
const obj1 = { name: '原始对象' };

// 1. Object.preventExtensions:不能添加新属性
Object.preventExtensions(obj1);
obj1.age = 25; // 静默失败(严格模式下报错)
console.log('  preventExtensions 后添加属性:', obj1.age); // undefined

// 2. Object.seal:不能添加/删除属性
const obj2 = { name: '密封对象' };
Object.seal(obj2);
delete obj2.name; // 静默失败(严格模式下报错)
obj2.name = '修改名称'; // 可以修改
console.log('  seal 后删除属性:', delete obj2.name); // false
console.log('  seal 后修改属性:', obj2.name); // '修改名称'

// 3. Object.freeze:完全冻结
const obj3 = { name: '冻结对象' };
Object.freeze(obj3);
obj3.name = '尝试修改'; // 静默失败(严格模式下报错)
console.log('  freeze 后修改属性:', obj3.name); // '冻结对象'
相关推荐
foundbug99915 小时前
基于MATLAB的TDMP-LDPC译码器模型构建、仿真验证及定点实现
开发语言·matlab
X***078815 小时前
从语言演进到工程实践全面解析C++在现代软件开发中的设计思想性能优势与长期生命力
java·开发语言
毕设源码-邱学长15 小时前
【开题答辩全过程】以 基于VUE的打车系统的设计与实现为例,包含答辩的问题和答案
前端·javascript·vue.js
用户390513321928815 小时前
JS判断空值只知道“||”?不如来试试这个操作符
前端·javascript
毕设源码-钟学长15 小时前
【开题答辩全过程】以 基于Python的车辆管理系统为例,包含答辩的问题和答案
开发语言·python
CCPC不拿奖不改名16 小时前
数据处理与分析:数据可视化的面试习题
开发语言·python·信息可视化·面试·职场和发展
液态不合群16 小时前
线程池和高并发
开发语言·python
小镇学者16 小时前
【c++】C++字符串删除末尾字符的三种实现方法
java·开发语言·c++
SmartRadio16 小时前
在CH585M代码中如何精细化配置PMU(电源管理单元)和RAM保留
linux·c语言·开发语言·人工智能·单片机·嵌入式硬件·lora