- はじめに
- おことわり
- JavaScriptの仕様
- 変数
- オブジェクト
- 関数
- クラス(別途記載)
- モジュール(import、export、require)
はじめに
この記事は、AWS CDKを覚えるにあたり、その前にAWS CDKを使ったIaCで最も使われるプログラミング言語であるTypeScriptの基礎を押さえないと行けないなと思い、触った時のメモである。
この記事では、TypeScriptとその前提となるJavaScritpの言語としてのメモを記載する。
TypeScriptを始めるまでの初期セットアップについては以下の記事にまとめている。
nopipi.hatenablog.com
おことわり
結局以下のページの解説を見ながらペコペコ叩いただけなので、このブログを見るよりこちらのページを見た方が有益かと思う。最初は分量が少ない「とほほ」から読んで、なんとなくわかったところから公式のドキュメントなどを読んだ。
- JavaScript:
- TypeScript
あと以下のOREILLYの本も役に立った。
www.oreilly.co.jp
JavaScriptの仕様
JavaScriptの基本仕様
- 大文字と小文字を区別する
- 文はセミコロンで区切られる
- 冒頭に
"use strict";を入れると厳格な実行モードで安全でない仕様が無効化されるstrict modeで実行される - 実行ファイルの冒頭から実行される(
main(){}のように必ずこの関数から実行するという仕様はない)- 一番最初に呼び出すファイル(エントリーポイント)は、
index.jsにすることが多い
- 一番最初に呼び出すファイル(エントリーポイント)は、
JavaScriptサンプル: index.js
"use strict"; // `name`という名前の変数を宣言 const name = "azu"; // `NAME`という名前の変数を宣言 const NAME = "azu"; console.log("name=",name) console.log("NAME=",NAME)
式と演算子
以下を参照
developer.mozilla.org
変数
(JavaScript仕様)変数・定数宣言
- 変数の宣言
let: ES2015(ES6) から使える、varの後継。範囲が局所化できネストされたブロックで同一変数名で宣言しても上書きされない。var: 以前からある変数宣言方式。ネストされたブロック内で同一変数名で宣言されると上書きされる。
var a = 3; let b = 3; { var a = 5; let b = 5; } console.log("a = "+a); // 5 (ブロック内で上書きされてしまう) console.log("b = "+b); // 3 (ブロック内で上書きされない)
- 定数の宣言
const: 定数を宣言する
const a = 1; a = 2; <<= 定数には代入できないのでエラーになる(tscコンパイル時に"error TS2588: Cannot assign to 'aaa' because it is a constant."となる) console.log("a = "+a);
(TypeScript固有)変数の型指定
JavaScriptは動的型付け言語であることが特徴であるが、大規模開発を行う際は「動的型付け」であることが意図しない挙動に繋がりバグが見つけられにくいというデメリットがある。TypeScriptでは明示的に型宣言を行う(ゆるい)静的型付け宣言であることが特徴である。これにより潜在的なバグの存在を減らすことができる。
型の明示方法(型アノテーション)
- 単一の型を指定する方法
let a: number = 1; let b: string = "hello";
- 複数の型を指定する方法 :
|で複数の方を指定する
function add(a: number | undefined, b: number | undefined): number { if (a === undefined || b === undefined) { throw new Error("引数が不正です"); } return a + b; } let bb: undefined; console.log(add(1, 2)); console.log(add(1, bb));
型の種類
TypeScriptのデータ型は、結局のところJavaScriptで扱うデータ型に準ずる。
JavaScriptで扱うデータ型は大きく、プリミティブ型とオブジェクト型の2つに分類できる。
プリミティブ型には8種類のタイプがある。
プリミティブ型: 数値や文字列など単一のデータを格納するデータ型- よく使うデータ型
string: 文字列("Hello, world")number: 数値(43)boolean: 真偽(true, false)
- 任意の型
any: どのような型の入力も許容する型(JavaScriptの型と一緒)
- nullと未定義
null: ヌル(null pointer)undefined: 初期化をしておらず未定義
- あまり使わない型
bigint: ES2020で追加。 number型で扱えない非常に大きな整数に使用する型symbol: よくわからない
- よく使うデータ型
let a: number = 1; let b: string = "hello"; let c: boolean = true; let d: any = 1; let e: null = null; let f: undefined = undefined; // let g: bigint = 10n; let h: symbol = Symbol("key"); console.log("type a:", typeof a); console.log("type b:", typeof b); console.log("type c:", typeof c); console.log("type d:", typeof d); console.log("type e:", typeof e); console.log("type f:", typeof f); // console.log("type g:", typeof g); console.log("type h:", typeof h);
オブジェクト型: プリミティブ型以外のデータ型- Array型
- Dictonary型
- オブジェクト
- などなど
let array: number[] = [1, 2, 3]; let dictionary: { [key: string]: string } = { "name": "Taro Yamada", "age": "20", "address": "Tokyo" }; console.log("type array:", typeof array); console.log("type dictionary:", typeof dictionary);
リテラル型(特定の文字列や数値などのデータのみ許可する型)
限られた文字や数値などのデータのみデータの投入を許可する特殊な型。
例えば、EC2インスタンスタイプの数値など、特定の文字列の入力のみ許可する変数を定義&宣言するのに利用する。
let StrInstanceSize: "m5.large" | "m5.xlarge" | "m5.2xlarge"; StrInstanceSize = "m5.large"; console.log(StrInstanceSize); StrInstanceSize = "t2.xlarge"; // t2.xlargeは定義されてないためErrorになる console.log(StrInstanceSize);
オブジェクト
(JavaScript&TypeScript)配列(Array)
Arrayの詳細な説明は以下を参照
基本的なArrayの操作
//配列の宣言 let year: number[] = [2019, 2020, 2021, 2022, 2023]; let word: string[] = ['hello', 'world']; let arr: (number | string)[] = [1, 'a']; // 配列の参照 console.log(year); //全ての要素を表示 console.log(year[0]); //1番目の要素を表示 console.log(year[1]); //2番目の要素を表示 // 配列の操作 word.push('!'); //末尾に追加 word.unshift('Hi'); //先頭に追加 console.log(word.pop()); //末尾を削除 console.log(word.shift()); //先頭を削除
高度なArrayの操作
filter: 条件に合致する要素を一つ以上抽出し新しいArrayを作成する
const words: string[] = ["spray", "elite", "exuberant", "destruction", "present"]; const result: string[] = words.filter((word) => word.length > 6); console.log(result); // Expected output: Array ["exuberant", "destruction", "present"]
find: 先頭から検索し条件に合致する要素を一つ抽出する
const array1: number[] = [5, 12, 8, 130, 44]; const found: number | undefined = array1.find((element) => element > 10); console.log(found); // Expected output: 12
map: 全ての要素に対して定義したメソッドの処理を行い新しい配列を生成する
const array1: number[] = [1, 4, 9, 16]; // Pass a function to map const map1: number[] = array1.map((x) => x * 2); console.log(map1); // Expected output: Array [2, 8, 18, 32]
(JavaScript&TypeScript)連想配列(=>はJavaScriptにない。オブジェクトで実現)
JavaScriptには連想配列の実装はない。
Objectを利用して、連想配列のような利用を実現する。
ということで、詳しくは次の「オブジェクト」を参照。
(JavaScript&TypeScript)オブジェクト
オブジェクトの基本的(定義&宣言/参照/更新)
// オブジェクトの定義と宣言 let poetletter: { [key: string]: string } = { name: "Taro Yamada", age: "20", address: "Tokyo" }; // オブジェクトの参照 console.log(poetletter); console.log(poetletter.name); // Taro Yamada console.log(poetletter.age); // 20 console.log(poetletter["address"]); // <==こういう書き方もできる // オブジェクトの操作 poetletter.name = "Hanako Yamada"; console.log(poetletter.name); // Hanako Yamada
- 宣言時に複数の型タイプを指定することもできる
// オブジェクトの型ていぎ let poetletter2: { [key: string]: string | number } = { name: "Taro Yamada", age: 20, address: "Tokyo" }; // <==ageの型がnumberになっている
(TypeScript)オブジェクトの定義(type, interface)
type(型エイリアス)を使った定義
// typeを使って型を事前に定義する type StrPoetLetter = { name: string; age: number; address: string; }; // オブジェクトの宣言 let poetletter: StrPoetLetter = { name: "Taro Yamada", age: 20, address: "Tokyo" }; console.log(poetletter);
interfaceを使った定義
// interfaceを使って型を事前に定義する interface StrPoetLetter { name: string; age: number; address: string; } // オブジェクトの宣言 let poetletter: StrPoetLetter = { name: "Taro Yamada", age: 20, address: "Tokyo" }; console.log(poetletter);
(TypeScript)ネストする複雑なオブジェクトの定義
interfaceを使った定義
// 配列にするオブジェクトの型を宣言 interface StrInternal { fuga: string; nuga: string | undefined; } // オブジェクトの型を宣言 interface StrPoetLetter { name: string; age: number; address: string; hoge: StrInternal[]; } // オブジェクトの宣言 let poetletter: StrPoetLetter = { name: "Taro Yamada", age: 20, address: "Tokyo", hoge: [ { fuga: "fugafuga", nuga: undefined }, { fuga: "fugafuga2", nuga: "nuganuga2" } ] }; // オブジェクトの構成要素を表示 console.dir(poetletter);
※typeの場合は、interfaceの部分を以下のように=を入れればOK
interface StrInternal = {
(TypeScript)型エイリアス(type)とinterfaceの違い
- 継承や同名の宣言時にマージされるなど、拡張性が高いのは
interface - 拡張性は低いがシンプルなのが型エイリアス(
type)
この辺の記事が参考になる。
(Javascript)オブジェクト構造の表示方法
console.dir(オブジェクト);
継承
割愛
(JavaScript)JSON処理
JSONの処理にはJSONオブジェクトを利用する。
オブジェクトの説明は以下を参照。
JSON to Object
const json = '{"result":true, "count":42, "data":[{"foo": "hoge1", "bar": "hage1},{"foo": "hoge2","bar": "hage2"}]}'; const obj = JSON.parse(json); console.dir(obj);
Object to JSON
const obj = { result: true, count: 42, data: [ { foo: "hoge1", bar: "hage1" }, { foo: "hoge2", bar: "hage2" } ] }; console.log(JSON.stringify(obj, null, 4));
関数
JavaScriptの関数仕様
JavaScriptの関数の基本
JavaScriptの関数は、
function XXX(){ }の形式- 関数呼び出し時に引数を省略した場合は、
undefinedになる
function add(a, b) { console.log("type of a: "+typeof(a)+" a="+a) console.log("type of b: "+typeof(b)+" b="+b) return a + b; } // aとb両方指定した場合 console.log(add(1, 2)); // aだけ指定した場合 ->bはundefined,リターンはNaN(Not-A-Number) console.log(add(1)); // bだけを明示的に指定した場合 ->aはundefined,リターンはNaN(Not-A-Number) console.log(add(b=2));
JavaScriptの関数の引数の渡し方(参照渡しと値渡し)
javascriptには、参照渡しと値渡しの使い分けはない
- プリミティブ型 : 値渡し(call by value)
- オブジェクト型:参照渡し(call by reference)
(TypeScript)関数のパラメータ
関数パラメータの基本的な考え方
TypeScriptでは、
- 関数を呼び出す時に、関数で定義した全てのパラメータを指定するのが基本
- なので関数呼び出し時に、TypeScriptではJavaScriptでは許容される
add(a=1, b=2)という書き方は許容されない
- なので関数呼び出し時に、TypeScriptではJavaScriptでは許容される
- パラメータは、型アノテーションを行うのが基本(型アノテーションを省略した場合は
anyになる) - 関数の戻り値の型アノテーションは、
function xxx(): 型アノテーション {}と書く。- 関数の戻り値の型アノテーションは省略可能
function add(a: number, b: number): number { console.log("type of a: " + typeof (a)) console.log("type of b: " + typeof (b)) return a + b; } console.log(add(1, 2)); console.log(add(1)); // エラーになる console.log(add(a=1, b=2)); //エラーになる
オプションパラメーター
引数の :型アノテーションの前に?をつけると呼び出し時に引数を省略できるオプションパラメーターとなる
ただし以下の注意点がある。
- 必須パラメーターの後ろにオプションパラメーターを指定する必要がある。
- 必須パラメータの前にオプションパラメーターは指定できない
- 指定を省略した場合、オプションパラメーターには
undefinedが設定される。- undefinedが設定されるのはjavascriptの仕様によるもの
- オプションパラメーターを2つ以上定義することも可能であるが、基本はオプションパラメーターは1つにするのが望ましい
- 呼び出し時に省略できるのは最後のパラメーターのみのため
注意点があるので、
次に説明するデフォルトパラメーターを基本的に利用する方が良いと思う。
function add(a: number, b?: number, c?: number): number { console.log("type of a: " + typeof (a)) console.log("type of b: " + typeof (b)) console.log("type of c: " + typeof (c)) if (b && c) { return a + b + c; } if (b) { return a + b; } if (c) { return a + c; } else { return a; } } console.log(add(1, 2, 3)); console.log(add(1, 2)); // 3番目の引数cは省略可能(その場合、c=undefinedになる console.log(add(1, undefined, 3)); // 2番目の引数を省略する場合はundefinedを渡す必要がある console.log(add(1));
- エラーになるパターン(必須パラメータの前にオプションパラメーターを指定)
function add2(a?: number, b: number): number { //エラーが発生する: error TS1016: A required parameter cannot follow an optional parameter. return a + b; }
デフォルトパラメーター
パラメータ記載時にb = 9とイコールでデフォルト値を指定することで、パラメータ省略時のデフォルト値が自動的に入る。
デフォルトパラメータを利用した場合、デフォルト値で定義した型から類推しパラメーターの型になり、この関数ではその型固定となる。
そのため、パラメーター省略時を考慮した型チェック(undefinedかどうかの確認)を行う必要がないのがメリットとなる。
function add(a: number, b: number = 9): number { return a + b; } console.log(add(1, 2)); // 3 console.log(add(1)); // 10 console.log(add(1, undefined)); // 10
レストパラメーター
割愛
(TypeScript)関数の戻り値
関数戻り時の暗示的/明示的な型アノテーション
関数戻り時の型指定のポイント
- TypeScriptは、関数内の
returnから関数戻り値の型を類推する。 - そのため、通常は明示的な型アノテーションはしなくても良い(O.REILLYの本によると)
- ただ再起呼出や大規模開発の場合は、明示的に指定するのが良い、らしい
所感として、安全のためには明示的に関数の戻り値を指定した方が良いような気がする。
- 暗示的な戻り時の型アノテーション
function add(a: number, b: number) { console.log("type of a: " + typeof (a)) console.log("type of b: " + typeof (b)) return a + b; }
- 明示的な戻り時の型アノテーション
function add(a: number, b: number): number { console.log("type of a: " + typeof (a)) console.log("type of b: " + typeof (b)) return a + b; }
(JavaScript)関数宣言と関数式とアロー関数式
最初にJavaScriptの関数の仕様を整理する。
- 関数宣言 :
function 関数名{...}で定義する一般的な関数の宣言方法 - 関数式: 一回しか利用しないような関数をより簡易的に書く方法
- アロー関数式: 関数名を付けない無名関数を、より簡略化して書く方法。ES2015(ES6)でJavaScriptの仕様として採用されたもの。
関数宣言を簡略化してよりコードをシンプルにするために、関数式やアロー関数式があると理解している。
アロー関数は、Arrayの節でwords.filter((word) => word.length > 6);でちらっと書いているが、ここではアロー関数を使うことで何が便利になるかをarray.filter()を例として説明する。
関数宣言の利用例
文字列が6文字より大きかどうかを判定する関数checkLengthを作り、その関数をarray.filter()に渡している。
words.filter(checkLength)という書き方が一見不自然に見えるが、こちらのwords.filterの仕様を見ると、以下の通りである。
words.filter(checkLength)の構文仕様は以下の通りfilter(callbackFn, thisArg)callbackFn: 真偽を返す関数を指定する。filter関数は、指定した関数に対して以下の引数を渡して実行する- (a)element:処理中の要素のデータ
- (b)index:処理中要素のarrayの中のindex情報
- (c)array: filter() が呼び出された配列の情報
つまり、真偽を判断する関数の関数ポインタ的なものをfilter関数に渡して、filter関数はその関数ポインタを利用し関数に所定の引数を渡して実行ということになる。
この書き方だと、(1)関数を外出しに書く必要があり冗長、(2)書き方が関数ポインタのような使い方と直感的に分かりずらい、というデメリットがある。(個人的な所感)
function checkLength(word) { return word.length > 6; } const words = ["spray", "elite", "exuberant", "destruction", "present"]; const result = words.filter(checkLength); console.log(result);
関数式の利用例
先ほどの関数宣言での書き方を関数式で書き直した例。
コードの量自体は変わらないが、関数を変数的に扱うような書き方になったので、関数ポインタ的な利用が直感的にわかりやすい書き方に感じる。
可読性向上の目的が大きい書き方かと思う。
const checkLength = function(word) { return word.length > 6; } const words = ["spray", "elite", "exuberant", "destruction", "present"]; const result = words.filter(checkLength); console.log(result);
アロー関数式の利用例
要は、上記のcheckLength関数をより簡略な書き方にして、filter()の中に押し込んだ書き方。
const words = ["spray", "elite", "exuberant", "destruction", "present"]; const result = words.filter(word => word.length > 6); console.log(result);
アロー関数は正式には以下の書き方をする
(引数1, 引数N) => { 文 }
以下のような簡略方式も可能である。
() => 式 引数 => 式 (引数) => 式 (引数1, 引数N) => 式
クラス(別途記載)
別途記載します。
モジュール(import、export、require)
JavaScriptのスコープ
JavaScriptで定義した変数・関数などは、そのままの状態では該当のJavaScriptファイル(.js)の中だけのスコープにとどまる。
別の.jsファイルからそれらを参照したい場合は、参照先のjsファイルの中で該当の変数なり関数をexportしたものを、利用したいjsファイルでrequireまたはimportで取り込む必要がある。
エラーになる例
util.js
function add(a, b) { return a + b; }
index.js
console.log(add(3)); // add関数が見つからずエラーとなる
CommonJS形式でのexport, import
util.js
exports.add = add; function add(a, b) { return a + b; }
index.js
const lib_1 = require("./lib"); console.log((0, lib_1.add)(1, 2)); // 3
JavaScriptモジュールの記載方式
モジュール間のexport, importは、JavaScriptベースで、(a)JavaScriptの古い仕様、(b)node.jsの仕様(CommonJS)、(c) ES2015(ES6) の仕様、が混在していて正直よくわからない。
具体的内容をきちんと理解したい人は以下のリンク先を参照。
(JavaScript)CommonJSの書き方
util.js
exports.increment = (i) => i + 1;
index.js
const { increment } = require("./util"); console.log(util.increment(3));
(JavaScript)ES6の書き方
util.js
export const increment = (i) => i + 1;
index.js
import { increment } from "./util"; console.log(increment(3));
または以下の書き方
import * as util from "./util"; console.log(util.increment(3));
TypeScriptでの書き方
よくわからない&悩ましいですが、
私が使おうとしているのはAWS CDKでのTypeScript利用で、AWS CDKのサンプルではES6モードで書いているようなので、ES6で書こうと思う。
(TypeScriptからJavaScriptにコンパイルする時に、tscコマンドがいい感じにcommonJSに書き直しているようにも見える)