Polymorphism and the this type
Polymorphism and the this
Type
Polymorphism in TypeScript allows objects of different types to be operated on through a unified interface, while the this
type provides a dynamic reference to the type of the current instance. Combining these two enables more flexible code structures, especially in inheritance and method chaining scenarios.
Basic Concepts of Polymorphism
Polymorphism in TypeScript is primarily achieved through interfaces and class inheritance. When different classes implement the same interface or inherit from the same base class, they can be handled uniformly:
interface Animal {
makeSound(): void;
}
class Dog implements Animal {
makeSound() {
console.log('Woof!');
}
}
class Cat implements Animal {
makeSound() {
console.log('Meow!');
}
}
function animalSound(animal: Animal) {
animal.makeSound(); // Polymorphic call
}
const dog = new Dog();
const cat = new Cat();
animalSound(dog); // Output: Woof!
animalSound(cat); // Output: Meow!
Dynamic Nature of the this
Type
The this
type represents "the type of the current class instance" and is dynamically determined at runtime. This allows methods to return the current instance, facilitating method chaining:
class Calculator {
value: number;
constructor(value = 0) {
this.value = value;
}
add(n: number): this {
this.value += n;
return this;
}
multiply(n: number): this {
this.value *= n;
return this;
}
}
const calc = new Calculator();
calc.add(5).multiply(2); // Method chaining
console.log(calc.value); // Output: 10
Polymorphic this
and Inheritance
When a class is inherited, the this
type automatically adjusts to the subtype, which is particularly useful for building extensible class hierarchies:
class BaseClass {
clone(): this {
// Return a copy of the current instance
return Object.create(this);
}
}
class DerivedClass extends BaseClass {
additionalMethod() {
console.log('Additional method');
}
}
const derived = new DerivedClass();
const cloned = derived.clone(); // Type is DerivedClass, not BaseClass
cloned.additionalMethod(); // Can call subclass methods
The this
Type in Interfaces
Interfaces can also use the this
type to define more flexible contracts:
interface Builder<T> {
setOption<K extends keyof T>(key: K, value: T[K]): this;
build(): T;
}
class PersonBuilder implements Builder<Person> {
private options: Partial<Person> = {};
setOption<K extends keyof Person>(key: K, value: Person[K]): this {
this.options[key] = value;
return this;
}
build(): Person {
return new Person(this.options);
}
}
const person = new PersonBuilder()
.setOption('name', 'Alice')
.setOption('age', 30)
.build();
Practical Applications of Polymorphic this
In real-world development, polymorphic this
is often used to build fluent APIs and extend class functionality:
class QueryBuilder {
where(condition: string): this {
// Add where condition
return this;
}
limit(count: number): this {
// Set limit
return this;
}
}
class MySQLQueryBuilder extends QueryBuilder {
useIndex(index: string): this {
// MySQL-specific method
return this;
}
}
const query = new MySQLQueryBuilder()
.where('age > 18')
.useIndex('age_index') // Subclass method
.limit(10); // Parent class method
Type Guards and this
Combining type guards enables more precise type checking:
class FileSystemObject {
isFile(): this is File {
return this instanceof File;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
}
class File extends FileSystemObject {
read(): string {
return 'file content';
}
}
class Directory extends FileSystemObject {
list(): string[] {
return ['file1', 'file2'];
}
}
function process(obj: FileSystemObject) {
if (obj.isFile()) {
obj.read(); // Here, obj is inferred as File
} else if (obj.isDirectory()) {
obj.list(); // Here, obj is inferred as Directory
}
}
Special Usage of this
Parameters
The this
parameter in method definitions can constrain the calling context:
function clickHandler(this: HTMLButtonElement) {
this.disabled = true;
}
const btn = document.querySelector('button');
btn?.addEventListener('click', clickHandler); // Correct
// clickHandler(); // Error: this context does not match
Advanced Patterns with Polymorphic this
In complex type systems, the this
type can be combined with other advanced type features:
class Observable<T> {
private subscribers: ((value: T) => void)[] = [];
subscribe(callback: (value: T) => void): this {
this.subscribers.push(callback);
return this;
}
next(value: T): this {
this.subscribers.forEach(cb => cb(value));
return this;
}
}
class NumberObservable extends Observable<number> {
map(transform: (n: number) => number): this {
// Implement map operation
return this;
}
}
const obs = new NumberObservable()
.map(n => n * 2)
.subscribe(console.log)
.next(10); // Output: 20
The this
Type and Mixin Patterns
The this
type is particularly useful in mixin patterns to maintain correct type inference:
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false;
activate(): this {
this.isActive = true;
return this;
}
};
}
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const TimestampedActivatableUser = Activatable(Timestamped(User));
const user = new TimestampedActivatableUser('Alice');
user.activate();
console.log(user.timestamp, user.isActive);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:混入(Mixins)模式实现
下一篇:类与接口的关系