Capítulo 12 de 36
TypeScript fully supports ES2015 class syntax and layers type annotations, visibility modifiers (public/protected/private), and compile-time-only contracts (implements) on top — enforcing that a derived class always stays a structural subtype of its base.
x: number declares a public writable property (implicitly any if untyped). An initializer (x = 0) both sets a runtime default and lets TypeScript infer the field's type.strictPropertyInitialization: requires every field to be assigned inside the constructor body itself — TS does not trace calls out to other methods invoked from the constructor (a subclass could override those and skip initialization). Use the definite-assignment assertion (name!: string) when something outside the constructor is responsible for initializing a field (e.g. a framework/DI container).readonly fields: block reassignment outside the constructor — same compile-time-only guarantee as readonly on object type properties.super() before touching this in a subclass is a compile error, not just a runtime footgun.this. — an unqualified name inside a method body resolves to the enclosing scope, not implicitly to a class member of the same name.get x()/set x(v) behave like accessors. A getter with no matching setter makes the property automatically readonly. An unannotated setter parameter infers its type from the getter's return type. Since TS 4.3, getter and setter can have genuinely different types (e.g. a setter accepting string | number | boolean that normalizes into a getter that only ever returns number).implements only checks compatibility — it never changes the class's inferred types. A method's parameter types are still inferred purely from the class body; implementing an interface with an optional property does not create that property on the class. This is a common source of surprise: implements is a validation, not a code-generation mechanism.extends / overriding: a derived class must remain a subtype of its base — you can widen a parameter to optional (greet(name?: string)) but not narrow it to required, because any code holding a Base-typed reference to a Derived instance must still be able to call the base signature safely.this.someField sees the base class's own initializer value, not a subclass's override, because the subclass's field initializers haven't run yet.declare on a field: when useDefineForClassFields/target >= ES2022 makes subclass field initializers overwrite inherited values, declare resident: Dog re-narrows an inherited field's type with zero runtime emit — pure type-only re-declaration.public (default, accessible anywhere), protected (subclasses only — and not accessible across sibling subclasses of the same base, even though that might seem intuitively safe), private (not even visible to subclasses). All are compile-time only — plain JS property lookup (obj["secretKey"], or a .js consumer) can still read a private TS field; that's "soft private." Genuine "hard private" (unreadable even from JS, no bracket-notation escape hatch) requires JS's own #privateField syntax, which TS compiles to closures/WeakMaps on older targets.private access is allowed — two instances of the same class can read each other's private members (TypeScript follows Java/C#/C++/Swift/PHP convention here, not Ruby's stricter model).class Base {
protected getName() { return "hi"; }
}
class Special extends Base {
greet() { return "Howdy, " + this.getName(); } // OK: protected visible in subclass
}
protected members are usable from subclass method bodies but not from outside the hierarchy.interface Checkable { check(name: string): boolean; }
class NameChecker implements Checkable {
check(s) { return s.toLowerCase() === "ok"; } // s is inferred `any`, NOT `string`!
}
implements is a compatibility check only — it does not backfill parameter types from the interface into the class body.| Modifier | Visible from | Compile-time or runtime? |
|---|---|---|
public (default) | anywhere | n/a |
protected | class + subclasses only (not sibling subclasses) | compile-time only |
private | declaring class only (readable cross-instance of same class) | compile-time only ("soft private") |
#field (JS private) | declaring class only | runtime-enforced ("hard private") |
implements never changes a class's inferred types — always annotate method parameters explicitly even when an interface already constrains them.private/protected are erased at compile time and don't stop plain JS access — reach for JS's #field syntax (or closures/WeakMaps) when you actually need runtime-enforced privacy.Base-typed reference to a Derived instance stays safe.this typing, parameter properties, and abstract classes.readonly, index signatures, and structural typing concepts reused directly on classes.this is Type guards (advanced chapter) extend the type-predicate mechanism to class methods.