初めての方は、 公式ドキュメント に軽く目を通し、
JSX Sandbox を使って、実際にコードを動かしつつ学習する事をおすすめします。
JavaScript の型と JSX の型の対応表です。
JSX のビルトイン型は、null, boolean, int, number, string, variant, Array, Map です。
これらのうち、プリミティブ型( boolean, int, number, string )は値として null を持つことが許されません。
この表にない JavaScript が元々持っている型( Data, RegExp, Error )も JSX で利用可能です。
| JavaScript Types | JSX Types | 備考 |
|---|---|---|
| Undefined | なし | |
| Null | null | |
| Boolean | boolean | true or false |
| Number | number | 数値型 (IEEE 754) |
| Number | int | 整数値型 (32bit) |
| String | string | |
| Any | variant | 汎用型 |
| Array | Array.<T> | var ary = []:Array.<variant>; |
| Object | Map.<T> | var obj = {}:Map.<int>; |
| JSON Object | Map.<variant> | JSON.stringify({ a: 1 }:Map.<variant>); |
| Function | function(引数名:型, …):戻り値の型 | function add(a:int, b:int):int { return a + b; } |
Array.<T> や Map.<T> のTの部分には型名が入ります。
T に指定できるのは
- string, number などのビルトイン型
- JavaScript が元々持っている型( Date, RegExp, Error 等)
- ユーザが定義した関数名やクラス名
などです。
これ以外にも、Nullable.<T> などがありますが、一度も使ったことがないので、
それらは知らないままでも特に困らないのでは? と思います。
- undefined は存在しません
undefinedキーワードが現れるとコンパイルエラーになりますvoid 0で undefined を生成できません。コンパイルエラーになります
- variant は何でも入る汎用型です
- 実際の中身は、string でも number でも null でも構いません
- 何でも入ってしまうので、コンパイラが静的に型をチェックしきれず、 思わぬ所で実行時エラーが発生してしまうかもしれません
- Array や Map には、一種類の型しか入りません
Array.<int>は int型の要素のみを持つ専用の配列になりますArray.<variant>は汎用型の要素のみを持つ専用の配列になります- 型を意識せず色々な物を入れたい場合は
Array.<variant>を使います
typeof演算子は variant型の変数にしか適用できません。他の型に適用するとコンパイルエラーになります&&や||演算子 は、自動的に boolean 型に変換して評価しません- JavaScript の
&&や||は、自動的に boolean 値に変換して評価してくれますが、JSX でそれを期待したコードを書くとコンパイルエラーにならず、実行時にハマります &&や||を使わず if文で書くと、JavaScript と同様に意図した通りに動作しますので、挙動が怪しい場合は if 文を使います
- JavaScript の
a = b || c;というJavaScriptの式は、JSX ではa = b ?: c;と記述します- この場合 b が falsy ならば
a = c;が、 b が truthy ならa = b;と評価されます
- この場合 b が falsy ならば
- JavaScript の型厳密比較演算子(
===と!==) は JSX にはありません。コンパイルエラーになります
Array や Map に variant 型を指定すると、なんでも入れられるようになりますが、
取り出す際は as 演算子によるキャスト構文で型を変換(指定)する必要があります。
// JSX
var array = []:Array.<variant>; // なんでも入る配列
array.push(123, "abc");
log array[0] as number; // -> 123
////////// number 型にキャスト
var object = {}:Map.<variant>; // なんでも入るObject
object["0"] = 123;
object["1"] = "abc";
log object["1"] as string; // -> "abc"
////////// string 型にキャスト// JSX
var obj = {
key: "value",
array: [1, "z"]:Array.<variant>
}:Map.<variant>;
log obj["key"] as string; // -> value
////////// string にキャスト
log obj["array"][0] as number; // -> 1
////////// number にキャストArray.<int> に対し、異なる型(ここではstring)をpushしようとすると型が異なるためコンパイルエラーになります。
// JSX
var array = [1,2,3]:Array.<int>;
array.push("str");
^ ERROR型推論が機能する場合は、型の指定を省略する事ができます。
現状の JSX は型推論があまり得意ではありません。
// JSX
// boolean (Primitive Type)
var changed:boolean = false;
var changed_ = false;
// number (Primitive Type)
var range:number = 0.0;
var range_ = 0.0;
// int (Primitive Type)
var count:int = 123;
var count_ = 123;
// string (Primitive Type)
var message:string = "hello";
var message_ = "hello";
// Nullable.<boolean> (Nullable Primitive Type)
var array:Array.<int>;
var array_ = new Array.<int>(3);
var stringHash:Map.<string> = {};
var stringHash_ = {}:Map.<string>;
var variantHash:Map.<variant> = null;
var variantHash_ = {}:Map.<variant>;
var now:Date = null;
var now_ = new Date();
var rex:RegExp = /hello/;
var rex_ = /hello/;// JSX -> js
var changed;
var changed_;
var range;
var range_;
var count;
var count_;
var message;
var message_;
var array;
var array_;
var stringHash;
var stringHash_;
var variantHash;
var variantHash_;
var now;
var now_;
var rex;
var rex_;
changed = false;
changed_ = false;
range = 0.0;
range_ = 0.0;
count = 123;
count_ = 123;
message = "hello";
message_ = "hello";
array_ = new Array(3);
stringHash = ({ });
stringHash_ = ({ });
variantHash = null;
variantHash_ = ({ });
now = null;
now_ = new Date();
rex = /hello/;
rex_ = /hello/;number型専用の配列を作るには、Array:<number> を使い、
汎用型専用の配列を作るには、Array:<variant> を使います。
// JSX
var numberArray1 = [1, 2]; // 数値のみの配列なら型推論が機能するため型の指定を省略できます
var numberArray2 = [1, 2]:Array.<number>; // このように型を明示してもよいです
var mixedArray = [1, "z"]:Array.<variant>; // 型推論が機能しないため Array.<variant> を指定しないとコンパイルエラーになります
log numberArray2[0];
log mixedArray[0];
log mixedArray[1];number型専用の Map を作る場合は、Map.<number> を使い、
汎用型専用の配列を作るには、Map.<variant> を使います。
各要素へのアクセスは、ドットシンタックス( object.key )ではなく、ブラケットシンタックス( object["key"] )を使います。
JSX にはドットシンタックスによるシンタックスシュガーはありません。
// JSX
var numberObject1 = { a: 1, b: 2 }; // 数値のみのMapなら型推論が機能するため型の指定を省略できます
var numberObject2 = { a: 1, b: 2 }:Map.<number>; // このように型を明示してもよいです
var mixedObject = { a: 1, b: "z" }:Map.<variant>; // 型推論が機能しないため Map.<variant> を指定しないとコンパイルエラーになります
log numberObject1["a"];
log mixedObject["a"];
log mixedObject["b"];
// ドットシンタックスでアクセスするとコンパイルエラーになります
// log mixedObject.b; // ERROR
// ブラケットシンタックスでアクセスします
log mixedObject["b"]; // OKコールバック関数を指定する場合は、コールバック関数の引数と引数の型、戻り値の型も全て明記する必要があります。
JSX初心者がまず躓くのが、このコールバック関数の型指定です。
戻り値を持たない(undefined)な関数の戻り値型には void を指定します。
// js
function hoge( callback ) { // @arg Function: callback(err:Error, result:String):void
callback(null, "ok");
callback(new Error("!!"), "ok");
}// JSX
function hoge( callback: function( err: Error, result: string ): void ): void {
callback(null, "ok");
callback(new Error("!!"), "ok");
}上記のコードをブレイクダウンし、コメントを追加したのが以下のコードになります。
// JSX
function hoge(
callback: function(
err: Error,
result: string
): void
): void {
////////// callback の第一引数(err)は Error 型
////////////// callback の第二引数(result)は string 型
/////// 関数 callback の戻り値は void
/////// 関数 hoge の戻り値は void
callback(null, "ok");
callback(new Error("!!"), "ok");
}JSX の基本単位は class です。
JSX の class システムの特徴は、
- コンストラクタを定義可能
- スタティック変数とスタティック関数が使える
- 全て public になる
interface クラス名 { ... }でインターフェースを定義可能class クラス名 extends 親クラス名 { ... }で単一継承が可能class クラス名 implements インターフェース名 { ... }でインターフェースを実装可能abstract class クラス名 { ... }で、直接 new できない抽象クラスを定義可能- mixin が使えるらしい
class _Main { ... }のようにクラス名の先頭が_で始まるクラスはエクスポートされない- メソッドのオーバーロードが可能
override function メソッド名(引数:型...):戻り値の型 { ... }でメソッドのオーバーライドが可能- 暗黙の this は省略不能
function メソッド名(arg:boolean = false):void { ... }のようにデフォルト引数を指定可能
です。
new クラス名(引数…) で呼び出す初期化メソッド(コンストラクタ)を
function constructor(引数:型) { ... } で定義できます(省略可能です)。
constructor は戻り値の型を指定できません。うっかり指定するとコンパイルエラーになります。
// js
// コンストラクタ
function God(message) { // @arg String:
this._message = message; // インスタンス変数
}
// スタティック関数
God.isReady = isReady; // God.isReady():Boolean
// メソッド
God.prototype.say = say; // God#say():String
function isReady() { // @ret Boolean: God が活動可能なら true を返す
return true;
}
function say() { // @ret String:
// @desc: コンストラクタで指定された message の値を返す
return this._message;
}
// ----------------------------
if ( God.isReady() ) {
var god = new God("よーし、7日間で世界作っちゃうぞー");
console.log( god.say() );
}// JSX
class God {
var _message = ""; // インスタンス変数
function constructor(message:string) { // コンストラクタ
this._message = message;
}
static function isReady():boolean { // スタティック関数
return true;
}
function say():string { // メソッド
return this._message;
}
}
class _Main {
static function main(args :string[]) : void {
if ( God.isReady() ) {
var god = new God("よーし、7日間で世界作っちゃうぞー");
log god.say();
}
}
}JSX はスタティック変数とスタティック関数が使えます。
インスタンス化せずに
クラス名.関数名(引数)で利用できる関数をスタティック関数と呼びます。
同様に、インスタンス化せずにクラス名.変数名で利用できる変数をスタティック変数と呼びます。
スタティック関数の定義は static function 関数名(引数:型…):戻り値の型 { ... } とします。
スタティック変数の定義は static var 変数名 = 初期値:型 とします。
// JSX
class God {
var _message = ""; // インスタンス変数
static var location = "earth"; // スタティック変数
}JSX の言語仕様に、public, protected, private の区別はありません。全て public になります。
JSX の利用者は、protected, private なメンバの先頭に _ を付与し、使う側が意識することでガードします。
// JSX
class God {
function _progress() { // メソッド名の先頭に `_` が付いているのは private なのです。
// 勝手に呼んだら神罰下すよ?
}
}クラス名の先頭が _ で始まるクラスは、エクスポートされません。class _Main などがそうです。
エクスポートされないクラスは import で読み込む事ができません。
_ で始まるクラスは、ファイル内でのみ有効なクラスの定義や、ユニットテスト用のクラスを定義する際に利用できます。
メソッドのオーバーロードが可能です。引数の型が異なる同じ名前のメソッドを複数定義できます。
名前が同じで、戻り値が違うだけのメソッドは作成できません。
これらのメソッドは クラス名.prototype.メソッド名$引数の型... という名前で書きだされます。
// JSX
class God {
// オーバーロード
function overload():void { }
function overload(arg:int):void { }
}override キーワードで、派生先のクラスでメソッドのオーバーライドが可能です。
// JSX
TODO: example code自分のクラスのインスタンス変数や関数を呼び出す際の、暗黙の this を省略できません。
// JSX
class God {
var _message = "";
function say():string {
//return _message; // コンパイルエラーになります
return this._message;
}
}最新のJSXでは、デフォルト引数の指定が可能になりました。
引数を省略可能にすると、内部的に引数省略版と引数指定版の2つのメソッドが生成されます。
// JSX
function piyo(key:string = ""):void {
}// JSX -> js
function Hoge() {
};
Hoge.prototype.piyo$S = function (key) { // 引数を指定した場合に呼び出されるメソッド
};
Hoge.prototype.piyo$ = function () { // 引数を省略した場合に呼び出されるメソッド
this.piyo$S("");
};new Array.<T>(length) を使います。
配列の生成もJSXのハマりポイントです。
// js
var array = new Array(3);// JSX
var array = new Array.<number>(3);console.log は、JSX では log キーワードになります。
log キーワードは release ビルド時で削除されます。(by @shibukawa san)
// js
console.log(new Date() + ""); // "Thu Jun 27 2013 13:42:46 GMT+0900 (JST)"// JSX
log new Date().toString(); // "Thu Jun 27 2013 13:44:44 GMT+0900 (JST)"また、import "console.jsx"; すると、console.log や console.warn が利用可能になります。(by @shibukawa san)
// JSX
import "console.jsx";
console.log("hoge");JSX では、+ や - 演算子による暗黙の型変換ができずコンパイルエラーになります。
toString() や valueOf() を明示的に呼び出す必要があります。
// js
console.log(new Date() + ""); // "Thu Jun 27 2013 13:42:46 GMT+0900 (JST)"
console.log(+new Date()); // 1372308767932
new TypeError(xhr.status);// JSX
log new Date().toString(); // "Thu Jun 27 2013 13:44:44 GMT+0900 (JST)"
log new Date().valueOf(); // 1372308767932
new TypeError(xhr.status.toString());import "js/web.jsx" とすることで、window や DOM を扱う事が可能になります。
また、js.global[...] で windowオブジェクトにアクセスすることができます。
// js
window.addEventListener("load", function(event) {
}, false);// JSX
import "js.jsx";
import "js/web.jsx";
class _Main {
static function main(args:string[]):void {
dom.window.addEventListener("load", function(event:Event) {
}, false);
}
}コンパイルエラーや実行時にエラーが出る場合は、以下のコードを試してみてください。
// JSX
import "js.jsx";
import "js/web.jsx";
var window = js.global["window"] as __noconvert__ Window;
var document = js.global["document"] as __noconvert__ HTMLDocument;// js
function load(fn) { // fn(err:Error, result:String)
fn(new TypeError("wow"), "404");
}JSXでは、Error から派生した TypeError を fn(err:Error) で受け取ることができず、コンパイルエラーになります。
// js
function load( fn(err:Error, result:string):void ):void {
/////
fn(new TypeError("wow"), "404");
}
no function with matching arguments
fn(new TypeError("wow"), "404");
^^^variant にするか、Error に統一します。
// JSX
function load( fn(err:variant, result:string):void ):void {
///////
fn(new TypeError("wow"), "404");
/////////
}
load( function(err:variant, result:string):void {
///////
});// JSX
function load( fn(err:Error, result:string):void ):void {
/////
fn(new Error("wow"), "404");
/////
}
load( function(err:Error, result:string):void {
/////
});以下のコードは動きません。第2引数はfunctionなので、nullを与える必要があります。
// JSX
log JSON.stringify(json, "", 2);
no function with matching arguments
log JSON.stringify(json, "", 2);
^log JSON.stringify(json, null, 2);以下は、localStorage に "key" が存在するかどうかを確認するコードです。
// js
if ("key" in window.localStorage) { }このコードは、JSX ではコンパイルできません。
db as variant に変形することでコンパイルは可能ですが、
それではコードの意味が変化し、localStorage["key"] === "" の時も true と評価されてしまいます。
// JSX
var db = js.global["localStorage"] as Storage;
if ((db as variant)["key"]) { }このような、JSX にとってイレギュラーなコードをコンパイルするには、 __noconvert__ キーワードを使います。
// JSX
var db = js.global["localStorage"] as Storage;
if ("key" in db as __noconvert__ Map.<variant>) { }上記のコードは、このようにコンパイルされます。
if ("key" in (function ($v) {
if (! ($v == null || typeof $v === "object")) {
debugger;
throw new Error("detected invalid cast, value is not a Map or null\n...");
}
return $v;
}(db))) {
}__noconvert__ キーワードは現在のところアンオフィシャルな存在です。
js の Object.keys は、 JSX では Map.#keys() になります。
最新のJSXで利用可能になりました。
// js
var obj = { key: "name" };
var keys = Object.keys(obj); // ["key"]// JSX
var obj = { key: "name" };
var keys = obj.keys(); // ["key"]JSX は、Object.constructor や Object.prototype にアクセスできません。
constructor は JSX の予約語なので回避も必要になります。
// js
if (Object.constructor === value.constructor) {
}// JSX
if (Object.constructor == value.constructor) {
}
// Compile
'Object' does not have a property named 'constructor'
if (Object.constructor == value.constructor) {
^^^^^^^^^^^(Object as variant)["method"] として、コンパイラを黙らせることで、意図した JavaScript コードを生成できます。
// JSX
if ( ( Object as variant )["constructor"] == ( value as variant )["constructor"] ) {
}
// Compile -> js
if (Object.constructor == value.constructor) {
}jsでは、スコープを評価/実行する前に、スコープ内の関数や変数の巻き上げが行われ、スコープの先頭でそれらが宣言されたものとして扱われますが、 JSX にはその機能がなく、コードに現れた順にコンパイラが都度評価するため、まだ登場していない変数や関数は未定義の物としてコンパイルエラーになります。
// JSX
function hoisting() {
var xhr;
xhr.onreadystatechange = onreadystatechange; // onreadystatechange が未定義のためコンパイルエラーになる
function onreadystatechange(event:Event):void {
}
}こうします。
// JSX
function hoisting() {
function onreadystatechange(event:Event):void {
}
var xhr;
xhr.onreadystatechange = onreadystatechange;
}最新のJSXコンパイラはケツカンマに対応済みです (by @gfx san)
{ a:1, b:2, } などの Object(Map) の末尾の余計なカンマを「ケツカンマ」と呼びます。
JavaScript は ES5 でケツカンマが許容されるようになりましたが、 ES3 をターゲットに設計された JSX はケツカンマでコンパイルエラーになります。
末尾の余計なカンマを除去してください。
// JSX
var obj = {
a: 1,
//
}:Map.<variant>;// JSX
var obj = {
a: 1
}:Map.<variant>;JSX にはこれらがないため、thisを明示的に指定するJavaScriptライブラリはそのままでは移植できません。
公式ドキュメントに存在しない js.invoke や __noconvert__ を使う必要があります。
// js
// String.fromCharCode.apply を使い、数値の配列から文字列化を低コストで生成する
var binaryString = [0x41, 0x42, 0x43, 0x44, 0x45]; // "ABCDE"
var s = "";
s = String.fromCharCode.apply(null, binaryString);
console.log(s); // "ABCDE"// JSX
var binaryString = [0x41, 0x42, 0x43, 0x44, 0x45]:Array.<int>; // "ABCDE"
var s = "";
s = js.invoke(String, "fromCharCode", binaryString as __noconvert__ Array.<variant>) as string;
log s; // "ABCDE"// js
// Array.prototype.push.apply を使って、array1 に array2 の内容を追加する(破壊的な操作)
var array1 = ["a", "b"], array2 = [3, 4];
Array.prototype.push.apply(array1, array2);
console.log(array1); // "a, b, 3, 4"JSX で、Array.prototype.xxx.apply は書けません。Array#concat で代用します。
// JSX
var array1 = ["a", "b"]:Array.<variant>;
var array2 = [3, 4]:Array.<variant>;
array1 = array1.concat(array2);
log array1; // "a, b, 3, 4"Array.<variant>ではなく、Array.<string>やArray.<int>のように配列に型を明示しているとコンパイルエラーになります。
Array.<variant>にしなければなりません。
// JSX
var array1 = ["a", "b"]:Array.<string>;
//////
var array2 = [3, 4]:Array.<int>;
////
array1 = array1.concat(array2); // コンパイルエラー
JSX では扱えません。js.invoke でなんとかなるかもしれません。
JSX は可変長引数が扱えないため、実行結果によりコールバック引数の数が変化する関数やメソッドは正しく機能せず、そのままではコンパイルもできません。
String#replace の場合は、マッチする文字列全体をキャプチャーする事しかできず、(\d) でマッチした文字列を個別にキャプチャーできません。
// js
"1,2,3".replace(/(\d)+,(\d)+,(\d)+/g, function(_, a, b, c) {
console.log(_, a, b, c); // "1,2,3 1 2 3"
});// JSX
// JSX/lib/built-in.jsx にある String#replace の定義
// function replace(searchValue: RegExp, replaceValue: function(matched:string):string): string;
// と異なるため、a:string, b:string, c:string としてもコンパイルが通らない
"1,2,3".replace(/(\d)+,(\d)+,(\d)+/g, function(_:string, a:string, b:string, c:string):string {
log _, a, b, c; // "1,2,3 1 2 3"
return "";
});キャプチャーを使わなければ、コンパイルが通るコードは書けますが、期待した動作はせずコンパイルエラーにもなりません。
ロジックから考え直す必要があります。
// js
"1,2,3".replace(/\d,\d,\d/g, function(_:string):string {
log _; // "1,2,3"
return "";
});js は、 && や || などの比較演算子を使い、ショート・サーキットを活用したコーディングができますが、JSX ではそのような事はできません、if 文で記述する必要があります。
// js
var a = function() { };
a && a(); // a が truthy なら a() を実行する。falsy なら何もしない
var b = 0;
b || (b = 111); // b が truthy なら何もしない、falsy なら 111 を代入する// JSX
var a = function():void { };
if (a) {
a(); // a が truthy なら a() を実行する。falsy なら何もしない
}
var b = 0;
if (!b) {
b = 111; // b が truthy なら何もしない、falsy なら 111 を代入する
}localStorage.getItem() が "" や null を返してきた場合に備えて、localStorage.getItem("KEY") || "" としてしまうと、
不幸にも "true" または "false" にキャストされてしまいます。
// JSX
var value = (localStorage.getItem("KEY") || "") as string;// JSX -> js
var value = !! (localStorage.getItem("KEY") || "") + ""; // "false" or "true"
//この場合は、a = b ?: c 構文を使います。
// JSX
var value = localStorage.getItem("KEY") ?: "";// JSX -> js
var value = localStorage.getItem("KEY") || "";TypeError や SQLError は Error から派生した型ですが、Error型の変数でこれらのオブジェクトを受け取ることはできません。
一度 err.message を取り出し、Errorオブジェクトに仕立て直す必要があります。
// JSX
function remove(fn:function(err:Error):void):void {
function onerror(err:SQLError):void {
fn(new Error(err.message)); // SQLError から Error に仕立て直す
}
}// JSX
var message = "";
var window = js.global["window"] as __noconvert__ Window;
window.alert(message as string); // 文字列にキャストしないとコンパイルエラーになるかもご検討いただけると幸いです。
JavaScript で && と || を使ったコードをそのまま移植すると、実行時に意図しない挙動が起きてしまう事が何度かあったので、
コードに && と || が現れた際の安全性を高めるようにしてほしいです。
constructor に戻り値の型を記述するとコンパイルエラーになりますが、
他のメソッドの記述と対称性が失われている気がするので、コンパイルエラーで制限するのではなく、
:void を許容してくれると余計な説明を減らせる気がします。
コンストラクタの中で return を使うと、「constructor は void function なのでエラー」が出てしまうため、辻褄をあわせる意味でも
// JSX
class Hoge {
function constructor() {
return "aaa";
}
}
cannot return a value from a void function
return "aaa";
^^^^^^JavaScript のソースコードを JSX に変換すると、 コールバック関数を定義する部分の視認性が悪くなると感じています。
型チェックに必要な全ての情報をインラインで記述しなければならないという制約が、 コードが横長になり、視認性が悪くなる理由だと思うので、 型を定義し再利用する仕組みが欲しいところです。
たとえば、以下のコードを…
// JSX
function hoge( callback: function( err: Error, result: string ): void ): void {
}
function huga( callback: function( err: Error, result: string ): void ): void {
}
function piyo( callback: function( err: Error, result: string ): void ): void {
}このような感じに書けると助かります。
// JSX
typedef typicalCallback function(err: Error, result: string): void;
function hoge( callback: typicalCallback ): void {
}
function huga( callback: typicalCallback ): void {
}
function piyo( callback: typicalCallback ): void {
}C# では typedef を廃し、代わりにスコープ内でのエリアスを定義する機能( usingディレクティブ )があるので、そちらの形でも大助かりです。
// JSX
using typicalCallback function(err: Error, result: string): void;
function hoge( callback: typicalCallback ): void {
}
function huga( callback: typicalCallback ): void {
}
function piyo( callback: typicalCallback ): void {
}JavaScript には、ループ展開や変数の参照コストを最小化したコードを人力で生成し、
new Function(文字列) に喰わせる事で、
通常のコードに比べ、実行速度を 10%〜20% ほど高速化するダークサイドな技法がありますが、
現在の JSX では new Function が使用できないため(残念ながら)利用できません。
使えるようになると嬉しいですね
ゆるキャラがほしいです。
ゆるキャラがほしいです。
jsx/JSX#9