LoginSignup
0
0

More than 3 years have passed since last update.

TypeScriptの関数について TypeScript & Angular 勉強会 #3

Last updated at Posted at 2020-08-09
1 / 14

前回の演習問題

以下のコードを、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)
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0