「静的型付けが出来るJavaScriptの上位言語」を触ってみよう。パート1 - TypeScript #1
最近JavaScriptを書くことが多い
JavaScriptは書けば書くほどモヤモヤする(原因のほとんどは自身の力不足だが...)
今後どんどん触れる機会を増すJavaScript達をもっとスッキリ書けないものかと、
考えた結果、やはり型の表現が難しいことが理由のひとつかと思ったりした。
そこで!!!
「静的型付けが出来るJavaScriptの上位言語」を触ってみよう。
今日はその第一弾
なにやんの??
探すといろいろある、
どれがいいとかはわからないので、とりあえずちょろりと書いて感覚を味わってみようと思う。
(このシリーズはあくまで導入部分のみを記載、詳細な部分にはふれません。あくまで自身の感覚的なお話)
対象
「静的型付けが出来るJavaScriptの上位言語」で自身が興味のある以下3つを選択
Dartが一番興味があるのだが、Dartの良し悪しを測る為にも他の言語を触れておく Dartについては後ほど
環境
- OS: Max OS X(10.7.5)
- Editor: Sublime Text 2
- おきになのでww
- 本来IDEを使う方がサクサクなんだろうけど、一旦素のEditorで書いてみる
じゃまずはTypeScriptから
TypeScript
http://www.typescriptlang.org/
ざっくりと
- Microsoft製
- IDEはVisualStudioがいい感じ??
- MS系言語のクライアント実装とかに使うと捗りそう
- 新手
- 2012〜
- ソースファイル(.ts)をコンパイルしてJavaScriptを出力
- コンパイルする時に型を判定
- 同じアプローチの言語としてCoffeeScriptとよく比較されてる
- 既存コードとの親和性が高い
- 使用されるデータ型JavaScriptと同じなので既存コードと親和性が高い
- JQueryやBackbone.jsなどの既出ライブラリも合わせ使用可能
かなり詳しく載っているので詳細はこちらで
TypeScriptはこの動画を元に試してみる
本日の見出し
- コンパイラインストール
- コンパイルしてみる
- 引数型
- 関数の引数に型を定義をできる
- 型定義されたプロパティを持つオブジェクトを引数型とすることもできる
- 引数のオプション化
- 引数オブジェクトをインターフェースとして抽出
- クラス定義
- 継承
コンパイラインストール
まずはコンパイラがないと何も出来ないのでインストール
$ npm install -g typescript
これだけ。簡単ですねww
- npm(node)のインストールはmacなら
brew install node
とか
$ tsc -v Version 0.8.3.0
コンパイルしてみる
まずはsample.ts
をつくって、普通にJavaScriptで関数を書いてみる
function proccess(x) { x.name = 'foo'; var v = x + y; console.log(v); }
このままコンパイルしてみる
$ tsc sample.ts .../sample.ts(3,16): The name 'y' does not exist in the current scope
yがないっておこられちゃいましたね。
動画のコードにだまされたww
ここでわかるのは素のJavaScriptコードもコンパイルできると言うこと
JavaScriptのバリデータとしても使えるし、JavaScriptコードも混ぜて実装したり、部分的にTypeScriptに移行なんかもできそう
コンパイルが通ると同じディレクトリにsample.js
ができる
- まだ普通のJavaScriptコードしか書いてないので
sample.ts
とsample.js
は全く同じ内容
引数型
そろそろ本題
関数の引数に型を定義をできる
引数xをstring
型で定義
function proccess(x: string) { x.name = 'foo'; ...
こういうコードをコンパイルすると
$ tsc sample.ts .../sample.ts(3,6): The property 'name' does not exist on value of type 'string'
ですね。x.name = 'foo';
が怒られますね。xはstringとして定義したため、stringにnameプロパティがないと
しっかりプロパティ判定が行われてます
動画で紹介されていたxを
number
や、bool
で定義した場合はコンパイル通っちゃったので割愛(IDEなら警告がでる??)
動画でさらっと出て来ましたが、戻り値型を持つ関数を引数として受け取る引数は以下のような定義のよう
function proccess(x: () => string) { x().charAt; }
- 関数を受け取り実行
- その関数が戻り値型
string
で定義されている為、charAt
が使用出来る
型定義されたプロパティを持つオブジェクトを引数型とすることもできる
function proccess(x: {a: number; b: string;}) { x.b.charAt; }
- プロパティはそれぞれセミコロンが必要(無いとコンパイルエラー、忘れそう...カンマ書きそう...)
- もちろん
charAt
の対象オブジェクトがxでもx.aでもコンパイルエラー
引数のオプション化
↑のコードを実行する場合
proccess({a: 1, b: 'sample'});
となる
でもaは必要ないので省略したい
proccess({b: 'sample'});
これはコンパイルエラー
そこで
function proccess(x: {a?: number; b: string;}) { x.b.charAt; }
引数名に?をつけるとオプション化することができる
引数オブジェクトをインターフェースとして抽出
{a?: number; b: string;}
この部分をインターフェースとして抽出し、再利用できる
interface Thing { a?: number; b: string; } function proccess(x: Thing) { return x.b.charAt; }
こんな感じ、かなりスッキリですね
ここまでコードをコンパイルしたJavaScriptコードは全て引数型定義がなくなったもの
もちろんですがコンパイル後のJavaScriptでは型は無効となる
コンパイル後のJavaScript
function proccess(x) { return x.b.charAt; }
クラス定義
さてさて、じゃクラスを書いてみる
クラスの定義はこんな感じ
class Point { x: number; // ... 1. プロパティ y: number; constructor(x: number, y: number) { // ... 2. コンストラクタ this.x = x; this.y = y; } static origin = new Point(0, 0); // ... 3. スタティックメンバー func1() { return this.x; } // ... 4. メソッド private func2() { return this.y; } // ... 5. アクセス制御 get func3() { return 'sample'; } // ... 6. アクセッサ }
ざっくりと
- プロパティ
- 型が指定できる
- コンストラクタ
- 「initialize」は使えないみたい
- スタティックメンバー
- 上記であれば
Point.origin
のように使用
- 上記であれば
- メソッド
new Point().func1()
- アクセス制御
new Point().func2()
はコンパイルエラー
- アクセッサ
new Point().func3
のように使う- アクセッサを定義する場合はコンパイル時に
--target ES5
が必要tsc --target ES5 sample.ts
- デフォルトはES3でコンパイルされる
ゆるくなく、ガチガチでもなく程よい
全くもってしっくりくる!!!
コンパイルすると
var Point = (function () { function Point() { } Point.prototype.initialize = function (x, y) { this.x = x; this.y = y; }; Point.origin = new Point(0, 0); Point.prototype.func1 = function () { }; Point.prototype.func2 = function () { }; Object.defineProperty(Point.prototype, "func3", { get: function () { return 'sample'; }, enumerable: true, configurable: true }); return Point; })();
アクセッサの部分以外想像通り
アクセス制御はこれまたtsコードの中だけで有効(コンパイルすると消える)
このコードをJavaScriptで書くストレスを考えると、これだけでTypeScriptを使う価値がある
継承
class Point3D extends Point { constructor(x: number, y: number, z: number) { super(x, y); } func1() { return this.y } }
自然だ。
コンパイルすると
var __extends = this.__extends || function (d, b) { function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); }; var Point = (function () { ... })(); var Point3D = (function (_super) { __extends(Point3D, _super); function Point3D(x, y, z) { _super.call(this, x, y); } Point3D.prototype.func1 = function () { return this.y; }; return Point3D; })(Point);
当たり前ですが、ちゃんと継承が実装されてる
継承すると__extends
メソッドが自動生成される
- 継承を全く使ってなければ
__extends
は生成されない - Backbone.jsなんかはUnderscoer.jsの
_.extend
を使うけど、この場合はextends
は使えない??(これは後半で記載します)
んんん、なるほど、もはやこれだけでも素晴らしと思ってしまった。
こっからが面白いんですが、情報量が多くなってしまったので今日はここまで。
次回「TypeScript後半戦」として以下のようなことを試してみます。
- Any
- thisの解決
- module(namespace)
- export
- import
- ファイルインポート
- *.d.ts
- jsファイルからのコンパイル
- IDE
本日参考にさせていただいたサイト
ありがとうございました。