TypeScript の基礎知識
●基本的なデータ型
- boolean : 真偽値 (true, false)
- number : 数 (整数, 浮動小数点数)
- [ES2020] 多倍長整数 (bigint)
- 整数値の末尾に n を付ける
- string : 文字列
- " または ' で囲む
- ` で囲む ([ES2015] テンプレート文字列)
- any : すべてのデータ型
- void : Void型 (関数が値を返さないときに指定する)
- null : Null型
- undefined : Undefined型
●変数
- 変数は var, let, const で宣言する ([ES2015] let, const)
- 型注釈 (type annotation) は変数名の後ろに : データ型 を付ける
let num: number; // 数
let str: string; // 文字列
let done: boolean; // 真偽値
変数の型注釈を省略すると any 型になる (コンパイラオプション --noImplicitAny を指定するとエラー)
初期値を指定する場合、型注釈を省略すると TypeScript が型推論によりデータ型を決定する
let num = 1234; // 数
let str = "hello, world"; // 文字列
let done = true; // 真偽値
変数にデータを代入するとき、データ型が異なるとコンパイルエラーになる
null と undefined はどのデータ型の変数にも代入できる
ただし、オプション --strictNullChecks を指定するとコンパイルエラーになる
null や undefind も代入したい場合は「Union Types (共用型)」を使う (あとで説明する)
リスト : データ型のチェック (test.ts)
let num: number;
num = "hello, world";
console.log(num);
$ tsc test.ts
test.ts:2:1 - error TS2322: Type 'string' is not assignable to type 'number'.
2 num = "hello, world";
~~~
Found 1 error in test.ts:2
●基本的な演算子
- 算術演算子 (+, -, *, /, %)
- 比較演算子 (==, !=, ===, !==, <, >, <=, >=)
- 論理演算子 (!, &&, ||)
- ビット演算子 (~, &, |, ^, <<, >>, >>>)
- 代入演算子 (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>=)
- インクリメント (++)
- デクリメント (--)
- 三項演算子 (test ? true節 : else節)
●基本的な制御構造
- if (test1) { then節; ... } else if (test2) { then節2; ... } else { else節; ... }
- switch(変数) { case 値1: 処理1; ...; break; ... default: 処理; ... }
- while (test) { 処理; ... }
- do { 処理; ... } while (test);
- for (初期化式; 条件式; 更新式) { 処理; ... }
- for (変数 in object) { 処理; ... }
- for (変数 of iterable) { 処理; ... } ([ES2015] イテレータ, ジェネレータ)
- 繰り返しの制御に break と continue が使える
●配列
- 配列のデータ型は 要素の型[] か Array<要素の型>
- Array<T> はジェネリック型の配列で、型パラメータ T に要素のデータ型を指定する
- 初期値を [値1, 値2, ...] で指定すると、データ型を省略することができる (型推論)
- new Array<要素の型>(size) で要素数が size の配列を生成することができる
- JavaScript と同様に TypeScript の配列は可変長
- 要素のアクセスも JavaScript と同じく角カッコ [] を使い、添字は 0 から始まる
- 二次元配列は 要素の型[][] または Array<Array<要素の型>> で宣言する
- 要素のアクセスは角カッコ [][] で行う
リスト : 配列の使用例 (sample01.ts)
let ary = [1, 2, 3, 4, 5, 6];
let ary1: number[];
let ary2: string[];
ary1 = ary;
// ary2 = ary; コンパイルエラー
console.log(ary[0]);
console.log(ary[5]);
let ary3 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(ary3[0][0]);
console.log(ary3[2][2]);
$ tsc sample01.ts
$ node sample01.js
1
6
1
9
●タプル
- TypeScript のタプル (tuple) は個々の要素のデータ型を指定した配列である
- データ型の指定は [型1, 型2, ...] とする
- 要素のアクセスは配列と同じ
- 書き換えも可能 (mutable)
- もちろん「分割代入」もできる ([ES2015] 分割代入)
リスト : タプルの使用例 (sample02.ts)
let a: [number, string, boolean]; // タプル
let b = [10, "bar", true]; // 配列 (要素の型が number | string | boolean)
a = [1, "foo", false];
// b = a; コンパイルエラー (b はタプルではなく配列)
console.log(a[0]);
console.log(a[1]);
console.log(a[2]);
// 代入するときデータ型が異なるとコンパイルエラー
a[0] = 0;
a[1] = "bar";
a[2] = true;
console.log(a);
let [x, y, z] = a; // 分割代入も可能
console.log(x);
console.log(y);
console.log(z);
// b はタプルではないので、異なるデータ型を代入できる
b[0] = "oops";
b[1] = false;
b[2] = 123;
console.log(b);
$ tsc sample02.ts
$ node sample02.js
1
foo
false
[ 0, 'bar', true ]
0
bar
true
[ 'oops', false, 123 ]
●列挙型
- TypeScrit の「列挙型 (enumerated type)」はC言語のそれとよく似ている
- 列挙型は enum で宣言する
enum 名前 {name0, name1, name2, name3, name4, name5, ... }
{ ... } の中の要素を「列挙定数」とか「列挙子」と呼ぶ
列挙子には整数値が順番に割り当てられる
デフォルトでは先頭の name0 に 0 が割り当てられ、name1 に 1 が、name2 に 2 が割り当てられる
列挙子の値を指定することもできる
たとえば、name3 = n とすると、name3 の値は n になり、name4 には n + 1, name5 には n + 2 が割り当てられる
列挙子のアクセスは 名前.列挙子 で行う
名前[添字] で列挙子の名前を文字列で得ることができる
リスト : 列挙型の簡単な使用例 (sample03.ts)
enum Fruit {Apple, Orange, Grape}
console.log(Fruit.Apple);
console.log(Fruit.Orange);
console.log(Fruit.Grape);
console.log(Fruit[0]);
console.log(Fruit[1]);
console.log(Fruit[2]);
// 値段表 (連想リスト)
const priceData: [Fruit, number][] = [[Fruit.Apple, 100], [Fruit.Orange, 150],
[Fruit.Grape, 200]];
// 値を求める
function getPrice(fruit: Fruit): number {
for (let [x, p] of priceData) {
if (x == fruit) return p;
}
return 0;
}
console.log(getPrice(Fruit.Apple));
console.log(getPrice(Fruit.Orange));
console.log(getPrice(Fruit.Grape));
$ tsc sample03.ts
$ node sample03.js
0
1
2
Apple
Orange
Grape
100
150
200
●関数
- TypeScript の関数定義は JavaScript のそれに型注釈を付けたもの
function 関数名(仮引数名: データ型, ...): 返り値の型 { 処理; ...; return 返り値; }
返り値の型は型推論できるのであれば省略可
返り値が無い場合はデータ型を void にする
関数呼び出しは 関数名(実引数, ...)
可変長引数の定義は function 関数名(..., ...args: データ型[]) { ... }
デフォルト引数は 仮引数: データ型 = 値 で定義する ([ES2015] 可変長引数, デフォルト引数)
仮引数?: データ型 のように、仮引数の後ろに ? を付けるとオプション引数になる
オプション引数は実引数を与えなくてもよい。そのときの値は undefined になる
アロー関数の定義は (仮引数:データ型, ...): 返り値の型 => { 処理; ...; return 返り値; } ([ES2015] アロー関数)
高階関数にアロー関数を渡す場合、型推論できるときは型注釈を省略できる
関数のデータ型は (仮引数: データ型, ...) => 返り値のデータ型 で表す
TypeScript は関数の多重定義 (オーバーロード) が可能
その定義方法は C++ や Java などとは違って単純ではない (あとで説明する)
リスト : 関数の簡単な使用例 (sample04.ts)
// 階乗 (再帰)
function fact(n: number): bigint {
if (n == 0) return 1n;
return BigInt(n) * fact(n - 1);
}
for (let i = 0; i <= 15; i++) console.log(fact(i));
// フィボナッチ数 (末尾再帰)
function fibo(n: number, a = 0, b = 1): number {
if (n == 0) return a;
return fibo(n - 1, b, a + b);
}
for (let i = 0; i < 15; i++) console.log(fibo(i));
// 高階関数
const a = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(a.map(x => x * x));
console.log(a.filter(x => x % 2 == 0));
console.log(a.reduce((a, x) => a + x));
- bigint を使っているのでコンパイルオプション --target es2020 が必要
$ tsc --target es2020 sample04.tsc
$ node sample04.js
1n
1n
2n
6n
24n
120n
720n
5040n
40320n
362880n
3628800n
39916800n
479001600n
6227020800n
87178291200n
1307674368000n
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
[
1, 4, 9, 16,
25, 36, 49, 64
]
[ 2, 4, 6, 8 ]
36
●クラス
- TypeScript のクラスは ES2015 のスーパーセット ([ES2015] クラス)
リスト : クラスの簡単な使用例 (sample05.ts)
class Foo {
// フィールド
private bar: number;
private baz: string;
// コンストラクタ
constructor(s: string, x: number) {
this.bar = x;
this.baz = s;
}
// メソッド
getBar(): number { return this.bar; }
setBar(x: number): void { this.bar = x; }
getBaz(): string { return this.baz; }
setBaz(s: string): void { this.baz = s; }
}
// インスタンスの生成
const foo = new Foo("abc", 123);
// メソッドでフィールドにアクセスする
console.log(foo.getBar());
console.log(foo.getBaz());
console.log(foo);
foo.setBar(456);
foo.setBaz("def");
console.log(foo);
$ tsc sample05.ts
$ node sample05.js
123
abc
Foo { bar: 123, baz: 'abc' }
Foo { bar: 456, baz: 'def' }
クラス定義は class クラス名 { ... }
クラス内で宣言された変数の呼び方はプログラミング言語によって異なる
Java や C# では「フィールド」、C++ では「メンバ変数」、ほかにも「インスタンス変数」や「スロット」などがある
JavaScript では「プロパティ」だが、本稿では「フィールド」と記述することにする
フィールドは var, let, const を付けないで 名前: データ型 で宣言する
クラス内で定義された関数を「メソッド (method)」という (C++ は「メンバ関数」という)
メソッドを定義するとき function は付けない ([ES2015] メソッド定義)
フィールドやメソッドにはアクセス修飾子をつけることができる
- public, protected, private
- 省略した場合は public
インスタンスの生成は new クラス名() で行う
このときクラスに定義されているコンストラクタが呼び出される
- constructor(仮引数: データ型, ...) { ... }
- コンストラクタは値を返さない (void はつけない)
- コンストラクタの引数にアクセス修飾子を付けると、仮引数名と同じフィールドを宣言できる
- これを引数プロパティ宣言 (parameter property declaration) という
リスト : 引数プロパティ宣言の使用例
class Foo {
// コンストラクタ
// フィールドにコンストラクタの実引数がセットされる
constructor(private baz: string, private bar: number) { }
// メソッド
getBar(): number { return this.bar; }
setBar(x: number): void { this.bar = x; }
getBaz(): string { return this.baz; }
setBaz(s: string): void { this.baz = s; }
}
インスタンスを obj とすると、フィールドのアクセスは obj.field名, メソッドの呼び出しは obj.メソッド名(実引数, ...)
メソッドやコンストラクタの中で変数 this は自分自身 (インスタンス) を表す
メソッドやコンストラクタでフィールドにアクセスするとき、this を省略することはできない
メソッドに static を付けると「クラスメソッド」になる
クラスメソッドは クラス名.メソッド名(...) で呼び出す
フィールドに static を付けると「クラス変数」になる
クラス変数は クラス名.フィールド名 でアクセスする
リスト : 簡単な例題 (point.ts)
class Point {
// コンストラクタ
constructor(private x: number, private y: number) { }
// メソッド
distance(p: Point): number {
const dx = this.x - p.x;
const dy = this.y - p.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
class Point3D {
// コンストラクタ
constructor(private x: number, private y: number, private z: number) { }
// メソッド
distance(p: Point3D): number {
const dx = this.x - p.x;
const dy = this.y - p.y;
const dz = this.z - p.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
}
const p0 = new Point(0, 0);
const p1 = new Point(10, 10);
const p2 = new Point3D(0, 0, 0);
const p3 = new Point3D(10, 10, 10);
console.log(p0.distance(p1));
console.log(p2.distance(p3));
$ tsc point.ts
$ node point.js
14.142135623730951
17.320508075688775
●get/set アクセサ
- get/set アクセサ (getter と setter) は、クラス外部から見るとフィールドと同じように振る舞うメソッドのこと ([ES2015] メソッド定義)
アクセス修飾子 get 名前(): データ型 { return this.フィールド名; }
アクセス修飾子 set 名前(仮引数: データ型) { this.フィールド名 = 仮引数; }
set の返り値には型注釈を指定しないこと
インスタンス.名前 でフィールドの値を取得する
インスタンス.名前 = 値 でフィールドの値を更新する
リスト : get/set アクセサの使用例 (sample06.ts)
class Foo {
// コンストラクタ
constructor(private _baz: string, private _bar: number) { }
// メソッド
get bar(): number { return this._bar; }
set bar(x: number) { this._bar = x; }
get baz(): string { return this._baz; }
set baz(s: string) { this._baz = s; }
}
const foo = new Foo("abc", 123);
console.log(foo.bar);
console.log(foo.baz);
console.log(foo);
foo.bar = 456;
foo.baz = "def";
console.log(foo);
$ tsc sample06.ts
$ node sample06.js
123
abc
Foo { _baz: 'abc', _bar: 123 }
Foo { _baz: 'def', _bar: 456 }
●継承
- TypeScript のクラスは他のクラスのフィールドやメソッドを「継承」することができる
class className extends superClassName { ... }
元になるクラスを「スーパークラス」、継承したクラスを「サブクラス」とか「派生クラス」という
TypeScript は単一継承なのでスーパークラスは一つだけ指定できる
スーパークラスのコンストラクタを呼び出すときは super(...) を使う
サブクラスのコンストラクタで this を使用する場合、その前に super() でスーパークラスのコンストラクタを呼び出す必要がある
サブクラスでスーパークラスのメソッドと同名のメソッドを定義することができる
これを「オーバーライド (over ride)」という
オーバーライドしたメソッドからスーパークラスのメソッドを呼び出すときは super を使う
- super.methodName(...); // スーパークラスの methodName を呼び出す
リスト : 継承の簡単なサンプル (sample07.ts)
class Bar {
constructor(private _a: number, private _b: number) { }
get a(): number { return this._a; }
get b(): number { return this._b; }
// 合計値を求める
sum(): number { return this.a + this.b; }
}
class Baz extends Bar {
constructor(a: number, b: number, private _c: number) {
super(a, b);
}
get c(): number { return this._c; }
// 合計値を求める
sum(): number {
return super.sum() + this.c;
}
}
const bar = new Bar(1, 2);
const baz = new Baz(10, 20, 30);
console.log(bar.a);
console.log(bar.b);
console.log(baz.a);
console.log(baz.b);
console.log(baz.c);
console.log(bar.sum());
console.log(baz.sum());
$ tsc sample07.ts
$ node sample07.js
1
2
10
20
30
3
60
●抽象クラス
- 修飾子 abstract を付けたクラスを「抽象クラス (abstract class)」という
- 抽象クラスはインスタンスを生成することができない
- 抽象クラスの中では、メソッドの仕様だけを宣言することができる
- これを「抽象メソッド (abstract method)」といい、修飾子 abstract を付ける
abstract class クラス名 {
...
abstract メソッド名(仮引数: データ型, ...): データ型;
...
}
抽象クラスは継承されることを前提としたクラス
抽象メソッドはサブクラスにおいて具体的に定義される
リスト : 抽象クラスの簡単な例題 (sample08.ts)
// 図形クラス
abstract class Figure {
abstract kindOf(): string;
abstract area(): number;
print(): void {
console.log(`${this.kindOf()}: area = ${this.area()}`)
}
}
// 三角形
class Triangle extends Figure {
constructor(private altitude: number, private baseLine: number) {
super();
}
kindOf(): string { return "Triangle"; }
area(): number { return this.altitude * this.baseLine / 2; }
}
// 四角形
class Rectangle extends Figure {
constructor(private width: number, private height: number) {
super();
}
kindOf(): string { return "Rectangle"; }
area(): number { return this.width * this.height; }
}
// 円
class Circle extends Figure {
constructor(private radius: number) {
super();
}
kindOf(): string { return "Circle"; }
area(): number { return Math.PI * this.radius * this.radius; }
}
const a = new Triangle(2, 2);
const b = new Rectangle(2, 2);
const c = new Circle(2);
a.print();
b.print();
c.print();
const table = [new Triangle(3, 3), new Rectangle(3, 3), new Circle(3)];
table.forEach(x => x.print());
$ tsc sampl08.ts
$ node sample08.js
Triangle: area = 2
Rectangle: area = 4
Circle: area = 12.566370614359172
Triangle: area = 4.5
Rectangle: area = 9
Circle: area = 28.274333882308138
●インターフェース
- インターフェース (interface) はフィールドやメソッドの仕様 (宣言) だけを記述した抽象クラス
interface インターフェース名 extends インターフェース1, ... { フィールドの宣言; ...; メソッドの宣言; ... }
フィールドの宣言は フィールド名: データ型;
メソッドの宣言は メソッド名(仮引数: データ型, ...): 返り値のデータ型
インターフェースは extends で複数のインターフェースを継承できる
フィールドやメソッドはインターフェースを継承したサブクラスで実装する
class クラス名 extends スーパークラス implements インターフェース1, ... { ... }
クラスは implements で複数のインターフェースを継承できる
フィールド名やメソッド名の後ろに ? を付けると、サブクラスでの実装を省略できる
インターフェースもデータ型として使用できる
リスト : インターフェースの簡単な使用例 (sample09.ts)
interface FooI {
bar: number;
baz(): string;
oops?: string;
}
class FooC implements FooI {
constructor(public bar: number) { }
baz(): string { return `bar = ${this.bar}`}
}
const d = new FooC(123);
console.log(d.baz());
console.log(d);
let e: FooI;
e = d; // 代入できる (いわゆるアップキャスト)
console.log(d);
$ tsc sample09.ts
$ node sample09.js
bar = 123
FooC { bar: 123 }
FooC { bar: 123 }
リスト : 図形クラスを interface で書き直す (sample10.ts)
// インターフェースの定義
interface Figure {
kindOf(): string;
area(): number;
print(): void;
}
// 三角形
class Triangle implements Figure {
constructor(private altitude: number, private baseLine: number) { }
kindOf(): string { return "Triangle"; }
area(): number { return this.altitude * this.baseLine / 2; }
print(): void {
console.log(`${this.kindOf()}: area = ${this.area()}`);
}
}
// 四角形
class Rectangle implements Figure {
constructor(private width: number, private height: number) { }
kindOf(): string { return "Rectangle"; }
area(): number { return this.width * this.height; }
print(): void {
console.log(`${this.kindOf()}: area = ${this.area()}`);
}
}
// 円
class Circle implements Figure {
constructor(private radius: number) { }
kindOf(): string { return "Circle"; }
area(): number { return Math.PI * this.radius * this.radius; }
print(): void {
console.log(`${this.kindOf()}: area = ${this.area()}`);
}
}
const a = new Triangle(2, 2);
const b = new Rectangle(2, 2);
const c = new Circle(2);
a.print();
b.print();
c.print();
const table = [new Triangle(3, 3), new Rectangle(3, 3), new Circle(3)];
table.forEach(x => x.print());
$ tsc sample10.ts
$ node sample10.js
Triangle: area = 2
Rectangle: area = 4
Circle: area = 12.566370614359172
Triangle: area = 4.5
Rectangle: area = 9
Circle: area = 28.274333882308138
●ジェネリック
- ジェネリック (generics) はデータ型をパラメータ化する機能のこと
- 型パラメータ (型引数) は <T, U, V, ...> のように < > の中で指定する
- ジェネリックを使って関数 (メソッド) とクラスを定義できる
関数名<T, ...>(仮引数: データ型, ...): データ型 { ... }
<T, ...>(仮引数: データ型, ...): データ型 => { ... } // アロー関数
class クラス名<T, ...> extends ... implements ... { ... }
データ型は クラス名<データ型, ...> になる
インスタンスの生成は new クラス名<データ型, ...>(実引数, ...)
メソッドの呼び出しは メソッド名<データ型, ...>(実引数, ...)
実引数から型変数のデータ型が推論できる場合、型変数の指定は省略できる
TypeScript (Version 2.3.2) の場合、static なフィールドやメソッドはクラスの型パラメータを参照できない
extends を使って型パラメータに制約を設定することができる
<T extends U> とすると、T は U のサブクラスに限定される
実際は、継承関係がなくても U のフィールドやメソッドが T にあればよい (構造的部分型)
リスト : ジェネリックの簡単な使用例 (sample11.ts)
class FooG<T> {
constructor(private _x: T) {}
get x(): T { return this._x;}
}
// 組
class Pair <T, U> {
constructor(private _fst: T, private _snd: U) { }
get fst(): T { return this._fst; }
get snd(): U { return this._snd; }
}
const foo1 = new FooG(123),
foo2 = new FooG("hello, world");
console.log(foo1.x);
console.log(foo2.x);
const pair1 = new Pair(1, "foo");
console.log(pair1.fst);
console.log(pair1.snd);
const pair2 = new Pair(foo1, foo2);
// const pair2 = new Pair<FooG<number>, FooG<string>>(foo1, foo2); と同じ
console.log(pair2.fst);
console.log(pair2.snd);
// 連想リストの探索
function assoc<T, U>(key: T, table: Pair<T, U>[]): U {
for (let p of table) {
if (p.fst == key) return p.snd;
}
return null;
}
const table = [
new Pair("foo", 123), new Pair("bar", 456), new Pair("baz", 789)
]
console.log(assoc("foo", table));
console.log(assoc("bar", table));
console.log(assoc("baz", table));
console.log(assoc("oops", table));
$ tsc sample11.ts
$ node sample11.js
123
hello, world
1
foo
FooG { _x: 123 }
FooG { _x: 'hello, world' }
123
456
789
null
●オブジェクト型リテラル
- TypeScript は JavaScript のオブジェクトリテラルとよく似た形式で無名の型を定義できる
- これを「オブジェクト型リテラル」という
- プロパティシグネチャ { 名前: データ型, ... }
- メソッドシグネチャ { 名前(仮引数: データ型, ...): データ型, ... }
- コールシグネチャ { (仮引数: データ型, ...): データ型 } (関数呼び出し可能なオブジェクト型)
- コンストラクタシグネチャ { new (仮引数: データ型, ...): データ型 } (new できるオブジェクト型)
- インデックスシグネチャ { [index: データ型]: データ型 } (添字のデータ型を指定)
- 名前を付ける場合はインターフェースを使う
- インターフェースは、プロパティシグネチャやメソッドシグネチャ以外のシグネチャも使用できる
リスト : オブジェクト型リテラルの使用例 (sample12.ts)
let obj1: {
foo: number; // プロパティシグニチャ
bar(): string; // メソッドシグニチャ
};
const obj2 = { foo: 123, bar() { return "hello, world"; }};
// obj1 と obj2 は同じデータ型
obj1 = obj2;
console.log(obj1.foo);
console.log(obj1.bar());
let obj3: {
(x: number, y: number): number; // コールシグニチャ
};
// let obj3: (x: number, y: number) => number; と同じ
obj3 = (x, y) => x + y;
console.log(obj3(1, 2));
class FooObj {
constructor(private _x: number) { }
get x(): number { return this._x; }
}
let obj4: {
new(x: number): FooObj; // コンストラクタシグネチャ
};
obj4 = FooObj;
const obj5 = new obj4(123);
console.log(obj5);
// インデックスシグネチャ
let obj6: {
[index: number]: number; // [ ] の中の添字は数値だけ
};
obj6 = {
1: 123,
2: 456,
// foo: 789 コンパイルエラー
};
console.log(obj6);
// 角カッコでの代入はエラーにならない
obj6['foo'] = '100';
console.log(obj6['foo']);
console.log(obj6);
let obj7: {
[index: string]: number; // [ ] の中の添字は文字列と数値だけ
};
obj7 = {
foo: 100,
bar: 200,
123: 400 // 数値も OK
}
console.log(obj7);
$ tsc sample12.ts
$ node sample12.js
123
hello, world
3
FooObj { _x: 123 }
{ '1': 123, '2': 456 }
100
{ '1': 123, '2': 456, foo: '100' }
{ '123': 400, foo: 100, bar: 200 }
●構造的部分型
- 一般的なオブジェクト指向言語の場合、サブクラスはスーパークラスの部分集合 (部分型) になる
- 継承によって部分型を生成する方法を「名前的部分型 (nomincal subtyping)」という
- 継承によらないで、データ型の構造によって部分型を判定する方法もある
- これを「構造的部分型 (structural subtyping)」という
- たとえば、メソッドの引数の型が { x(): number } の場合、メソッド x() を持つオブジェクトであれば、何でも受け付ける
- 逆に、メソッド x() を持たないオブジェクトを渡すとコンパイルエラーになる
- 動的型付け言語では、同じインターフェースが備わっているオブジェクトは同じデータ型とみなす、という考え方がある
- これを「ダック・タイピング (duck typing)」という
- 静的型付け言語では、構造的部分型によりダック・タイピングのようなプログラミングスタイルが可能になる
リスト : 構造的部分型 (sample13.ts)
const obj10 = { x(): number { return 123; } };
const obj11 = {
x(): number { return this._x; },
_x: 456
};
const obj12 = {
x(): number { return this._x;},
_x: 789,
y(): string { return "hello, world"; }
};
function getX(obj: { x(): number }) {
return obj.x();
}
// どのオブジェクトもメソッド x() を持っているので
// getX() に渡すことができる
console.log(getX(obj10));
console.log(getX(obj11));
console.log(getX(obj12));
$ tsc sample13.ts
$ node sample13.js
123
456
789
●イテレータとジェネレータ
- イテレータとジェネレータは JavaScript の仕様 ES2015 で追加された機能である
- TypeScript の場合、イテレータのデータ型は次のように定義されている
リスト : イテレータのデータ型
interface IteratorResult<T> {
done: boolean;
value?: T;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
TypeScript (JavaScript) では、Iterator を実装したオブジェクトをイテレータと呼ぶ
Iterable を実装したオブジェクトを iterable オブジェクトという
iterable オブジェクトは for ... of 構文で要素を順番に取り出すことができる
TypeScript の場合、ジェネレータのデータ型は次のように定義されている
リスト : ジェネレータのデータ型
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
function* で定義するジェネレータの返り値型は IterableIterator<T> になる
リスト : イテレータとジェネレータ (sample14.ts)
// 引数を順番に取り出す
function makeIter<T>(...args: T[]): Iterator<T> {
let idx = 0;
return {
next(): IteratorResult<T> {
if (idx == args.length) {
return { done: true, value: undefined };
} else {
return { done: false, value: args[idx++] };
}
}
};
}
const iter = makeIter(1,2,3,4);
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
// for ... of を使いたい場合
function makeIterable<T>(...args: T[]): IterableIterator<T> {
let idx = 0;
return {
next(): IteratorResult<T> {
if (idx == args.length) {
return { done: true, value: undefined };
} else {
return { done: false, value: args[idx++] };
}
},
[Symbol.iterator](): IterableIterator<T> { return this; }
};
}
for (let x of makeIterable(1,2,3,4)) {
console.log(x);
}
// function* を使うともっと簡単
function* makeGen<T>(...args: T[]): IterableIterator<T> {
yield* args;
}
for (let x of makeGen(5,6,7,8)) {
console.log(x);
}
- イテレータを使用しているので、コンパイラオプション --target es2015 が必要
$ tsc --target es2015 sample14.ts
$ node sample14.js
{ done: false, value: 1 }
{ done: false, value: 2 }
{ done: false, value: 3 }
{ done: false, value: 4 }
{ done: true, value: undefined }
1
2
3
4
5
6
7
8
●セットとマップ
- セット (Set) とマップ (Map) は JavaScript の仕様 ES2015 で追加されたコレクション
- TypeScript では、セットとマップをジェネリックで実装している
- Set<データ型>, Map<キーの型, 値の型>
リスト : セットとマップの簡単な使用例 (sample15.ts)
const s1 = new Set<number>();
[1,3,5,7,9].forEach(x => s1.add(x));
console.log(s1);
console.log(s1.has(5));
console.log(s1.has(6));
const m1 = new Map<string, number>();
m1.set("foo", 123);
m1.set("bar", 456);
m1.set("baz", 789);
console.log(m1);
console.log(m1.get("bar"));
console.log(m1.get("oops"));
- Map, Set を使用しているので、コンパイラオプション --target es2015 が必要
$ tsc --target es2015 sample15.ts
$ node sample15.js
Set { 1, 3, 5, 7, 9 }
true
false
Map { 'foo' => 123, 'bar' => 456, 'baz' => 789 }
456
undefined