您现在的位置是:网站首页 > 对象方法重写文章详情
对象方法重写
陈川
【
JavaScript
】
36853人已围观
7356字
对象方法重写的基本概念
对象方法重写是指在子类中重新定义父类已有的方法。当子类实例调用该方法时,执行的是子类中的实现而非父类中的原始实现。这是面向对象编程中多态性的重要体现。
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!
为什么要重写方法
方法重写允许子类根据自身特性修改或扩展父类行为。常见场景包括:
- 实现特定于子类的功能
- 优化父类方法的性能
- 添加额外的处理逻辑
- 改变方法的返回值类型
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!
重写时的注意事项
- 方法签名一致性:重写方法应保持与父类方法相同的参数列表
- 访问权限:不能降低方法的可见性(如父类public方法不能重写为private)
- 返回值类型:在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数组仍为空