您现在的位置是:网站首页 > 对象方法重写文章详情

对象方法重写

对象方法重写的基本概念

对象方法重写是指在子类中重新定义父类已有的方法。当子类实例调用该方法时,执行的是子类中的实现而非父类中的原始实现。这是面向对象编程中多态性的重要体现。

class Animal {
  makeSound() {
    console.log('Some generic animal sound');
  }
}

class Dog extends Animal {
  makeSound() {
    console.log('Bark! Bark!');
  }
}

const myDog = new Dog();
myDog.makeSound(); // 输出: Bark! Bark!

为什么要重写方法

方法重写允许子类根据自身特性修改或扩展父类行为。常见场景包括:

  1. 实现特定于子类的功能
  2. 优化父类方法的性能
  3. 添加额外的处理逻辑
  4. 改变方法的返回值类型
class Shape {
  area() {
    return 0;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

const myCircle = new Circle(5);
console.log(myCircle.area()); // 输出: 78.53981633974483

方法重写与super关键字

在重写方法时,可以使用super关键字调用父类的原始实现,通常用于扩展而非完全替换父类行为。

class Vehicle {
  startEngine() {
    console.log('Engine started');
  }
}

class Car extends Vehicle {
  startEngine() {
    super.startEngine();
    console.log('Checking fuel level...');
    console.log('All systems go!');
  }
}

const myCar = new Car();
myCar.startEngine();
// 输出:
// Engine started
// Checking fuel level...
// All systems go!

重写时的注意事项

  1. 方法签名一致性:重写方法应保持与父类方法相同的参数列表
  2. 访问权限:不能降低方法的可见性(如父类public方法不能重写为private)
  3. 返回值类型:在TypeScript等强类型语言中,返回值类型应兼容
class Parent {
  greet(name: string): string {
    return `Hello, ${name}`;
  }
}

class Child extends Parent {
  // 正确 - 参数和返回类型匹配
  greet(name: string): string {
    return `Hi there, ${name.toUpperCase()}!`;
  }
  
  // 错误示例 - 参数类型不匹配
  // greet(name: number): string {
  //   return `Hi ${name}`;
  // }
}

JavaScript特有的重写特性

由于JavaScript的原型继承机制,方法重写有一些独特表现:

function Parent() {}
Parent.prototype.sayHello = function() {
  console.log("Hello from Parent");
};

function Child() {}
Child.prototype = Object.create(Parent.prototype);

// 方法重写
Child.prototype.sayHello = function() {
  console.log("Hello from Child");
};

const child = new Child();
child.sayHello(); // 输出: Hello from Child

重写与属性遮蔽的区别

方法重写不同于属性遮蔽,后者发生在实例属性与原型链同名属性之间:

class MyClass {
  value = 'prototype value';
}

const instance = new MyClass();
instance.value = 'instance value'; // 属性遮蔽

console.log(instance.value); // 输出: 'instance value'
delete instance.value;
console.log(instance.value); // 输出: 'prototype value'

静态方法的重写

静态方法也可以被重写,但调用方式是通过类而非实例:

class Database {
  static connect() {
    console.log('Connecting to generic database');
  }
}

class MySQL extends Database {
  static connect() {
    console.log('Connecting to MySQL database');
  }
}

MySQL.connect(); // 输出: Connecting to MySQL database

重写内置对象方法

JavaScript允许重写内置对象原型上的方法,但通常不推荐这种做法:

// 不推荐的做法 - 修改原生原型
Array.prototype.push = function() {
  console.log('Push operation intercepted');
  return 0;
};

const arr = [1, 2, 3];
arr.push(4); // 输出: Push operation intercepted
console.log(arr); // 输出: [1, 2, 3]

使用Symbol防止方法被意外重写

ES6引入的Symbol可以创建唯一属性键,避免方法被意外重写:

const uniqueKey = Symbol('uniqueMethod');

class SecureClass {
  [uniqueKey]() {
    console.log('This method cannot be accidentally overridden');
  }
}

class InsecureClass {
  regularMethod() {
    console.log('This can be overridden');
  }
}

const instance = new SecureClass();
instance[uniqueKey](); // 正常调用

方法重写与性能考量

频繁的方法重写可能影响性能,特别是在热代码路径中:

class Optimized {
  process(data) {
    // 基础处理逻辑
    return data.filter(x => x > 0);
  }
}

class Specialized extends Optimized {
  process(data) {
    // 先执行父类处理
    const filtered = super.process(data);
    // 添加额外处理
    return filtered.map(x => x * 2);
  }
}

// 性能更好的替代方案
class OptimizedAlternative extends Optimized {
  process(data) {
    // 合并过滤和映射操作
    const result = [];
    for (const item of data) {
      if (item > 0) {
        result.push(item * 2);
      }
    }
    return result;
  }
}

重写与混入模式

混入(Mixins)可以实现类似方法重写的效果,但不使用类继承:

const canSwim = {
  swim() {
    console.log('Swimming in water');
  }
};

const canFly = {
  fly() {
    console.log('Flying in air');
  }
};

class Duck {
  constructor() {
    Object.assign(this, canSwim, canFly);
  }
  
  // 重写混入方法
  fly() {
    console.log('Duck flying with wings');
  }
}

const donald = new Duck();
donald.swim(); // 输出: Swimming in water
donald.fly();  // 输出: Duck flying with wings

重写与私有方法

ES2022引入的私有类字段和方法会影响重写策略:

class Base {
  #privateMethod() {
    console.log('Base private method');
  }
  
  publicMethod() {
    this.#privateMethod();
  }
}

class Derived extends Base {
  // 无法重写私有方法
  // #privateMethod() {
  //   console.log('Derived private method');
  // }
  
  // 但可以重写公共方法
  publicMethod() {
    console.log('Derived public method');
    super.publicMethod();
  }
}

const d = new Derived();
d.publicMethod();
// 输出:
// Derived public method
// Base private method

方法重写的替代方案

在某些情况下,组合优于继承,可以考虑以下替代模式:

// 使用策略模式替代继承
class Processor {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  process(data) {
    return this.strategy(data);
  }
}

const doubleStrategy = data => data.map(x => x * 2);
const filterStrategy = data => data.filter(x => x > 0);

const doubleProcessor = new Processor(doubleStrategy);
console.log(doubleProcessor.process([1, 2, 3])); // [2, 4, 6]

const filterProcessor = new Processor(filterStrategy);
console.log(filterProcessor.process([-1, 0, 1])); // [1]

浏览器环境中的重写实践

在浏览器环境中,重写常用于扩展DOM元素行为:

// 扩展原生DOM方法示例
const originalAddEventListener = EventTarget.prototype.addEventListener;

EventTarget.prototype.addEventListener = function(type, listener, options) {
  console.log(`Adding event listener for ${type}`);
  // 调用原始实现
  originalAddEventListener.call(this, type, listener, options);
};

document.addEventListener('click', () => {
  console.log('Document clicked');
});
// 控制台会先输出: Adding event listener for click
// 然后输出: Document clicked

重写与测试模拟

方法重写在单元测试中常用于创建模拟对象:

class Database {
  save(data) {
    // 实际数据库操作
  }
}

// 测试专用子类
class MockDatabase extends Database {
  savedItems = [];
  
  save(data) {
    this.savedItems.push(data);
    return Promise.resolve();
  }
}

// 在测试中使用
describe('UserService', () => {
  it('should save user data', async () => {
    const mockDb = new MockDatabase();
    const service = new UserService(mockDb);
    
    await service.createUser({name: 'Test'});
    expect(mockDb.savedItems.length).toBe(1);
  });
});

重写与性能优化技巧

在某些性能敏感场景,可以结合方法重写进行优化:

class Particle {
  update() {
    // 通用更新逻辑
  }
}

class FastParticle extends Particle {
  constructor() {
    super();
    // 在构造函数中"编译"优化版本
    this.update = this.optimizedUpdate;
  }
  
  optimizedUpdate() {
    // 高度优化的专用逻辑
  }
}

// 创建大量实例时,优化版本性能更好
const particles = Array(10000).fill().map(() => new FastParticle());

重写与内存管理

不当的方法重写可能导致内存泄漏:

class EventEmitter {
  constructor() {
    this.listeners = [];
  }
  
  addListener(fn) {
    this.listeners.push(fn);
  }
}

// 有问题的重写
class LeakyEmitter extends EventEmitter {
  addListener(fn) {
    // 忘记调用super导致父类监听器数组未更新
    console.log('Listener added', fn);
    // 应该加上: super.addListener(fn);
  }
}

const emitter = new LeakyEmitter();
emitter.addListener(() => {});
// 父类的listeners数组仍为空

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步