クライアントサイドの入力チェック用だったJavaScriptが今は主役
以前Titaniumを弄ったときはどうしてもJavaScriptに積極的に取り組む気にはなれなかったのですが、cordova(PhoneGap)の存在を知り[iOS/Android対応] HTML5 ハイブリッドアプリ開発[実践]入門という本を読んで、またJavaScriptを使ったandroidアプリ開発への興味が戻って来ました。Titaniumを弄ったときと違って自分にとってはCoffeeScriptの存在が大きい、JavaScriptは嫌いだけどCoffeeScriptならやる気が湧きます。なので試しに以前記事を書いたRuby製の三目並べのプログラムをCoffeeScript(最終的にはJavaScriptを使ったandroidアプリ)に移植して見ようと思い取り組み始めたらいきなり問題に遭遇したのでTips的に記事を書いてみます。
それにしても昔のWebシステムはブラウザのJavaScript機能がOffでも使えることを要求されたものなのに、リッチなUIを要求するユーザーの声がどんどん大きくなり、最近ではJavaScript(jQuery,Ajax)無しは考えられない感じですね。そしてどうせJavaScriptを避けることが出来ないのなら、どうにかしてJavaScriptを楽に生成しようという流れが続いてます。
Arrayクラスを継承したRubyのクラスをJavaScriptに
三目並べの盤を表現するArrayクラスを継承した以下のコードを
#tictactoe.rb
class Board < Array
def initialize(*args, &block)
super(*args, &block)
end
end
b = Board.new([1, 2, 3, 4, 5, 6, 7, 8, 9])
p b[3]
以下のようにCoffeeScriptに書き換えて実行したところ
#tictactoe.coffee
class Board extends Array
constructor: (args) ->
super(args)
b = new Board([1, 2, 3, 4, 5, 6, 7, 8, 9])
console.log b[3]
画面にはundefined
と表示されました。エラーにもなりません。コンストラクタに渡されたargsパラメータはどこに行ったのでしょうか?super(args)
の行を@ = args
とやったらエラーになります1
以下のようにパラメータを可変個引数にして、配列要素をバラバラのパラメータにして渡しても同じです。
#tictactoe.coffee
class Board extends Array
constructor: (args...) ->
super(args...)
b = new Board(1, 2, 3, 4, 5, 6, 7, 8, 9)
console.log b[3]
試しにArrayクラスに新しいプロパティを追加してあげるとそちらの方はちゃんと代入出来ました。
#tictactoe.coffee
Array::data = []
class Board extends Array
constructor: (args) ->
super(args)
@.data = args
b = new Board([1, 2, 3, 4, 5, 6, 7, 8, 9])
console.log b[3]
console.log b.data[3]
まず、JavaScriptでもクラスの拡張(オープンクラス?)が簡単に出来ることに驚きました。Rubyと同様にオブジェクト指向出来るんですね。でもこのCoffeeScriptをJavaScriptに変換した中身を見てみると
$coffee -c tictactoe.coffee
$cat tictactoe.js
// Generated by CoffeeScript 1.6.3
(function() {
var Board, b,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Array.prototype.data = [];
Board = (function(_super) {
__extends(Board, _super);
function Board(args) {
Board.__super__.constructor.call(this, args);
this.data = args;
}
return Board;
})(Array);
b = new Board([1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log(b.data[3]);
console.log(b[3]);
}).call(this);
なんじゃこりゃ〜。CoffeeScriptを介さずに元のRubyのコードからこのJavaScriptのコードを書ける気がしない。慣れの問題もあるでしょうが、CoffeeScriptが見事に抽象化してくれているのがわかります。
で、問題のコンストラクタに渡された引数の部分ですが、Board.__super__.constructor.call(this, args);
こうなっていて値を置き換えてくれても良さそうですが入りません。追加したdataプロパティの方はArray.prototype.data = [];
と定義されていて、JavaScriptではprototypeプロパティを介してデータを参照しているようです。なので@.prototype = args
とやったりしましたが値はセットされませんしエラーにもなりません。@ = args
はエラーになりますがpushメソッドは呼べるので、結局コンストラクタで@.push(args[i]) for i in [0...args.length]
とすることでRubyからCoffeeScriptを介してJavaScriptに移植することが出来ました。
#tictactoe.coffee
class Board extends Array
constructor: (args) ->
@.push(args[i]) for i in [0...args.length]
b = new Board([1, 2, 3, 4, 5, 6, 7, 8, 9])
console.log b[3]
配列の大括弧([]、bracket)さえもメソッドとして実装しているRubyとの違いかもしれませんが、とりあえず移植できてなによりです。今までJavaScriptに関して真剣に取り組んだことが無かったのですが、買ってきた本を見ながら移植を進めて、また何か問題が発生すれば記事を書いてみます。
-
CoffeeScriptでは@はthisの別名、::はprototypeの別名)。 ↩