前回の演習問題
以下のコードを、enum
ではなくUnionを使うように変更してください。
enum Tiles {
None = 0,
AllSimples = 1,
AllRuns = 1 << 1,
Reach = 1 << 2,
SelfDraw = 1 << 3,
FirstTurnWin = 1 << 4
}
function printTile(tiles: Tiles):string {
let result = ""
if(tiles & Tiles.Reach){
result += "リーチ"
}
if(tiles & Tiles.FirstTurnWin){
result += "一発"
}
if(tiles & Tiles.SelfDraw){
result += "ツモ"
}
if(tiles & Tiles.AllSimples){
result += "断么九"
}
if(tiles & Tiles.AllRuns){
result += "平和"
}
return result
}
let tile = Tiles.Reach | Tiles.SelfDraw
console.log(printTile(tile)) //リーチツモ
解説
これが所謂「ビットフラグ」です。
enum Tiles {
None = 0,
AllSimples = 1,
AllRuns = 1 << 1,
Reach = 1 << 2,
SelfDraw = 1 << 3,
FirstTurnWin = 1 << 4
}
このように論理和演算子を使うことで、複数の条件を格納することができます。
let tile = Tiles.Reach | Tiles.SelfDraw
ただしチェックはあんまり直感的ではありません。
if(tiles & Tiles.FirstTurnWin){
result += "一発"
}
回答例
ビットフラグをUnionで実現しようとすると、配列を使うのが手っ取り早いですね。
type Tile = "AllSimples" | "AllRuns" | "Reach" | "SelfDraw" | "FirstTurnWin"
function printTile(tiles: Tile[]):string {
return tiles.reduce((acc, cur) => {
switch(cur){
case "AllSimples":
return acc += "断么九"
case "AllRuns":
return acc += "平和"
case "Reach":
return acc += "リーチ"
case "SelfDraw":
return "ツモ"
case "FirstTurnWin":
return "一発"
}
}, "")
}
let tiles:Tile[] = ["Reach", "AllRuns"]
console.log(printTile(tiles)) //リーチ平和
※ reduce などの集約関数は別途まとめて会を作ります
関数定義
function greet(name: string): void {
console.log(`hello, ${name}`)
}
いろいろな関数定義
Typescriptでは関数は第一級のオブジェクトであり、数字や文字列などと変わりません。
下記のように変数として定義できます。
// 関数式
let greet2 = function(name: string): void {
console.log(`hello, ${name}`)
}
// アロー関数
let greet3 = (name: string) => {
console.log(`hello, ${name}`)
}
// アロー関数の省略記法
let greet4 = (name: string) => console.log(`hello, ${name}`)
関数式とアロー関数は実装上は同じ、、、ではない。
ただし、Typescriptでクラスベースで使う文にはそこまで大きな影響ないので、そこまで詳しい解説は省略します。
オプションパラメータ、デフォルトパラメータ
- 省略可能なオプションパラメータ、デフォルト値があるデフォルトパラメータ
- 他の言語でもおなじみですよね(オーバーロードで実現する例とかもありますが)
オプションパラメータ
type Language = "jp" | "en"
function helloWorld(name: string, lang?: Language){
if(lang == null){
lang = "jp"
}
switch(lang) {
case "en":
console.log(`Hello, ${name}`)
case "jp":
console.log(`こんにちわ、${name}`)
}
}
helloWorld("世界")
デフォルトパラメータ
type Language = "jp" | "en"
function helloWorld(name: string, lang: Language = "jp"){
switch(lang) {
case "en":
console.log(`Hello, ${name}`)
case "jp":
console.log(`こんにちわ、${name}`)
}
}
helloWorld("世界")
個人的にはデフォルトパラメータをおすすめします(null
を考慮するのは思考コストが高い)
レストパラメータ
- 任意の数のパラメータを配列として受け取る機能。いわゆる可変長引数
- 「レスト」の名前のとおり、最後の引数としてのみ指定可能
function sum(...value: number[]): number {
return value.reduce((acc, val) => {
return acc + val
}, 0)
}
console.log(
sum(1,2,3) //好きな長さのパラメータを渡せる
)
function accumulate(accFunc: (acc: number, cur: number) => number, ...value: number[]): number {
return value.reduce((acc, val) => {
return accFunc(acc, val)
}, 0)
}
console.log(
accumulate((acc, cur) => acc + cur , 1,2,3) //好きな長さのパラメータを渡せる
)
call
, apply
, bind
- すべてJavascriptが持つ機能で、関数の呼び出しを細かく制御したいときに使う
- 呼び出しコンテキスト(
this
)の制御 -
bind
のみ、コンテキストを指定した別関数を返す
- 呼び出しコンテキスト(
const add = (left: number, right: number) => left + right
console.log( add(10, 20) )
console.log( add.apply(null, [10, 20]) )
console.log( add.call(null, 10, 20) )
console.log( add.bind(null, 10, 20)() )
- マニアックなライブラリでも使おうと思わない限り、めったに使わないのでそんなに覚えなくていいです
JS知識マウント取りたい場合に使ってください
関数の this
Javascript / Typescript では関数の呼び出しコンテキストによって this
が異なる
let x = {
a() {
return this
}
}
console.log( x.a() ) //{}
let a = x.a
console.log( a() ) // undefined
この呼出コンテキストを this
を片付けすることにより制御することができる。
let x = {
arg1: 1,
a() {
return this
},
b(this: {arg1: number}) {
return this
}
}
console.log( x.a() ) //{}
let a = x.a
let b = x.b
console.log( a() ) // undefined
b() //The 'this' context of type 'void' is not assignable to method's 'this' of type '{ arg1: number; }'.(2684)
こいつとclassを組み合わせることで非常に面白いことができます。お楽しみに!!
関数の型付け
ここまで暗黙的にやって来ましたが、関数の型付け記法があります。
type funcA = (arg1: number, arg2: string) => void
関数自体をプロパティに持つような定義も可能
type ObjWithFunc = {
funcA: () => void
}
関数の型付け(完全版)
以下の型付けは省略記法になります。
type Log = (message: string, userId: string) => void
以下が正式な記法です。
type Log = {
(message: string, userId: string): void
}
普通は省略記法を使ってください。しかし、正式記法ではないと表現できないこともあります。
それが関数のオーバーロード
関数のオーバーロードとは
- 説明せよ(唐突に演習)
関数のオーバーロード定義
正式な関数の定義記法でしか定義できません。
type Log = {
(message: string): void
(message: string, userId: string): void
(module: number, message: string, userId: string): void
}
これの実装はどうすればいいのでしょうか?
関数のオーバーロード実装
オーバーロードの実装には、すべてのパラメータパターンを受けることができる一つの関数を実装しなかればならない。
let log: Log = (
messageOrModule: string | number,
messageOrUserId?: string,
userId?: string) => {
//TODO
}
ジェネレータとイテレータ
ジェネレータとイテレータは双方Javascript自体の機能なので、そこまで深堀りしません。
ただこういう機能があることを知っておくと良いです。特に漸化式で表すことができるような値との相性は良いです(フィボナッチ数列など)
ジェネレータ
ジェネレータは、値を一つずつ返すオブジェクトのこと。function*
で定義できる。
function* enumulateNumbers() {
let n = 0
while(true){
yield n++
}
}
let numbers = enumulateNumbers()
console.log( numbers.next().value ) //0
console.log( numbers.next().value ) //1
console.log( numbers.next().value ) //2
イテレータ
イテレータは反復可能なオブジェクトのこと。for ... of ...
の構文などで中身を展開できる。
let enumulater = {
*[Symbol.iterator]() {
for (let n = 1; n <= 10; n++) {
yield n
}
}
}
for(let item of enumulater){
console.log(item)
}