Accessors (getter/setter)
Basic Concepts of Accessors
Accessors in TypeScript allow control over access to object members through the get
and set
keywords. This approach provides finer-grained property access control, making it safer and more flexible than directly exposing fields. Accessors are essentially special methods but syntactically resemble regular properties.
class Person {
private _age: number;
get age(): number {
return this._age;
}
set age(value: number) {
if (value < 0) {
throw new Error("Age cannot be negative");
}
this._age = value;
}
}
const person = new Person();
person.age = 25; // Calls set age(25)
console.log(person.age); // Calls get age()
Differences Between Accessors and Regular Properties
Although accessors are used like properties, they differ fundamentally from directly defined properties:
- Computed Properties: Getters can return computed values.
- Validation Logic: Setters can include validation logic.
- Access Control: Only providing a getter can create read-only properties.
- Lazy Loading: Getters can implement lazy initialization.
class Circle {
private _radius: number;
private _area: number | null = null;
constructor(radius: number) {
this._radius = radius;
}
get radius(): number {
return this._radius;
}
set radius(value: number) {
if (value <= 0) {
throw new Error("Radius must be positive");
}
this._radius = value;
this._area = null; // Clear cache
}
get area(): number {
if (this._area === null) {
this._area = Math.PI * this._radius ** 2;
}
return this._area;
}
}
Advanced Usage of Accessors
Read-Only Properties
By defining only a getter without a setter, you can create read-only properties:
class Configuration {
private readonly _apiUrl: string;
constructor(apiUrl: string) {
this._apiUrl = apiUrl;
}
get apiUrl(): string {
return this._apiUrl;
}
}
const config = new Configuration("https://api.example.com");
// config.apiUrl = "new-url"; // Error: Cannot assign to read-only property
Accessor Inheritance
Accessors can be inherited and overridden like regular methods:
class Animal {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
class Dog extends Animal {
get name(): string {
return super.name + " (Dog)";
}
}
const dog = new Dog("旺财");
console.log(dog.name); // Output: 旺财 (Dog)
Accessors and Interfaces
Interfaces can define contracts for accessors:
interface IUser {
username: string;
get fullName(): string;
set fullName(value: string);
}
class User implements IUser {
private _fullName: string = "";
username: string;
constructor(username: string) {
this.username = username;
}
get fullName(): string {
return this._fullName;
}
set fullName(value: string) {
if (value.length < 3) {
throw new Error("Full name must be at least 3 characters");
}
this._fullName = value;
}
}
Accessors in Frameworks
Many frontend frameworks use accessors to implement reactive data binding:
class Observable<T> {
private _value: T;
private _subscribers: Array<(value: T) => void> = [];
constructor(initialValue: T) {
this._value = initialValue;
}
get value(): T {
return this._value;
}
set value(newValue: T) {
if (this._value !== newValue) {
this._value = newValue;
this._subscribers.forEach(callback => callback(newValue));
}
}
subscribe(callback: (value: T) => void): void {
this._subscribers.push(callback);
}
}
const temperature = new Observable<number>(20);
temperature.subscribe(value => {
console.log(`Temperature changed to: ${value}°C`);
});
temperature.value = 25; // Triggers subscription callback
Performance Considerations for Accessors
While accessors offer many conveniences, performance considerations are important:
- Avoid Complex Computations: Avoid time-consuming operations in getters.
- Cache Results: For values with high computation costs, consider caching.
- Reduce Trigger Frequency: Avoid frequent side effects in setters.
class ExpensiveComputation {
private _input: number = 0;
private _cachedResult: number | null = null;
get input(): number {
return this._input;
}
set input(value: number) {
if (this._input !== value) {
this._input = value;
this._cachedResult = null; // Clear cache when input changes
}
}
get result(): number {
if (this._cachedResult === null) {
console.log("Performing complex computation...");
// Simulate time-consuming computation
this._cachedResult = this._input * this._input;
}
return this._cachedResult;
}
}
Combining Accessors with Decorators
TypeScript decorators can be combined with accessors to achieve more powerful functionality:
function logAccess(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGet = descriptor.get;
const originalSet = descriptor.set;
if (originalGet) {
descriptor.get = function() {
console.log(`Getting ${propertyKey}`);
return originalGet.apply(this);
};
}
if (originalSet) {
descriptor.set = function(value: any) {
console.log(`Setting ${propertyKey} to ${value}`);
originalSet.call(this, value);
};
}
}
class Product {
private _price: number = 0;
@logAccess
get price(): number {
return this._price;
}
@logAccess
set price(value: number) {
this._price = value;
}
}
const product = new Product();
product.price = 100; // Console output: Setting price to 100
console.log(product.price); // Console output: Getting price
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn