I. Class Members
Class definitions in TypeScript are consistent with [ES6 Class specification](/articles/class-es6 笔记 10/), supporting static properties, instance properties, accessors, etc.:
class Grid {
static origin = {x: 0, y: 0};
}
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
But need to note 2 points:
-
ES3 doesn't support getter/setter, therefore requires compilation configuration to ES5+
-
Properties with only getter without setter will be automatically inferred as
readonly(one of member modifiers)
II. Member Modifiers
Access Control Modifiers
Supports 3 access control modifiers:
-
public: Class member properties/methods are
publicby default, no access restrictions -
private: Cannot access its members outside the class declaration (e.g., cannot access private members via
this.xxx) -
protected: Similar to
private, but protected members can also be accessed in derived classes
For example:
class Animal {
// Private member property
private name: string;
// Protected member property
protected ancestor: string;
// If not written, defaults to public
constructor(theName: string) { this.name = theName; }
// Public member method
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
Note, these access controls are only compile-time restrictions, no strong checks at runtime. Consistent with TypeScript's design principle:
Don't add runtime overhead to compiled output
Additionally, class member accessibility is also part of type checking, private/protected modifiers will break duck typing, for example:
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
}
class Person {
name: string;
constructor(theName: string) { this.name = theName; }
}
// Correct: duck typing (if it looks like it, it is)
let person: Animal = new Person('Sam');
class Plant {
// Private member name
private name: string;
constructor(theName: string) { this.name = theName; }
}
// Error: name property accessibility doesn't match (even if looks like, accessibility is different)
// Property 'name' is private in type 'Plant' but not in type 'Person'.
let invalidPlant: Plant = new Person('Stone');
P.S. Specially, protected constructor indicates the class doesn't allow direct instantiation, but supports inheritance
readonly Modifier
Can declare properties as read-only via readonly modifier, can only assign at declaration or in constructor, for example:
class Octopus {
readonly name: string;
public readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.
P.S. Of course, readonly doesn't conflict with access control modifiers, can act on same property
Parameter Properties
For properties initialized in constructor:
class Octopus {
readonly name: string;
constructor (theName: string) {
this.name = theName;
}
}
There's a shorthand way:
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
Where name is called parameter property, declared by adding private/protected/public/readonly modifier before constructor parameter name
III. Inheritance
class A extends B {
//...
}
Similar to Babel transformation (for clarity, only excerpt key parts):
function _inherits(subClass, superClass) {
// Inherit parent class prototype properties
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
// Inherit parent class static properties
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
// Inherit parent class instance properties in subclass constructor
function A() {
// Add parent class instance properties to subclass instance this via parent class constructor
return A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments)
}
Class inheritance in TypeScript will also be compiled and replaced with prototype-based inheritance, as follows:
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
// Inherit parent class static properties
extendStatics(d, b);
function __() { this.constructor = d; }
// Inherit parent class prototype properties, and instance properties
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// __extends(A, B);
Both are similar, from implementation perspective, TypeScript compiled output is more robust, because its goal is:
Run in any host environment supporting ES3+
P.S. Interesting is static property inheritance, see [I. How to Inherit Static Properties?](/articles/class 继承-es6 笔记 12/#articleHeader2) for details
IV. Abstract Classes
TypeScript also has abstract class concept:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
Abstract classes can have concrete methods with implementation (like move), can also have abstract methods with only declaration without implementation (like makeSound), but requires subclasses must implement these methods:
class Cat extends Animal {
makeSound() {
console.log('meow meow meow');
}
}
Another similar concept is interface, difference between the two is interfaces can only define "abstract methods" (no abstract keyword, more precisely method signatures), for example:
interface Animal {
// Compare abstract makeSound(): void;
makeSound(): void;
}
// Compare class Cat extends Animal
class Cat implements Animal {
makeSound() {
console.log('meow meow meow');
}
}
V. Classes and Types
Declaring a class also declares the type of instances of that class, for example:
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
Where instance greeter is of type Greeter, that is, Class declarations have type meaning:
-
Type of class instances:
Greeter -
Type of class itself:
typeof Greeter
Actually, type of class itself constrains static properties, instance properties, constructor, prototype methods and other characteristics, for example:
class GreeterDuck {
// Class itself type constraints
static standardGreeting = 'Hi';
greeting: string;
constructor(message: string) { }
greet() { return 'there'; }
// Allows more not less (duck typing)
sayHello() { /*...*/ }
}
let greeterType: typeof Greeter = GreeterDuck;
Further:
// Extend Interface from Class type
interface HelloGreeter extends Greeter {
sayHello(): void;
}
let hGreeter: HelloGreeter = new GreeterDuck('world');
Yes, because classes have type meaning, so interfaces can extend from classes
No comments yet. Be the first to share your thoughts.