Titanium 3.0 と ECMA-262-5
Titanium 3.0 は Alloy とか Alloy とか Alloy が話題になりがちですが、実は ECMA-262-5 互換も大きく進化しています。面倒な書き方で対応していた箇所をスッキリと書けるようになったところもあります。いくつか見てみましょう。
短く書くために CoffeeScript で説明しますが、わざと CoffeeScript 的ではない書き方をしているのでご了承をば。
Object.create
Object.create を使うと継承のようなものを簡単に書く事ができます。
Hoge = -> @name = "Class Hoge" Hoge::getName = -> "#{@name}!"
例えばこんなクラスっぽい Hoge があって、 Foo で Hoge を継承したい場合、
Foo = -> Hoge.apply @, arguments Foo:: = Object.create Hoge::, greet: # greet というメソッドを定義する value: -> "I'm #{@name}! My parent is Hoge."
こんな感じで継承のようなものを書く事ができます。 Object.create は与えられたプロトタイプから渡されたパラメータを持つオブジェクトを生成します。
Object.defineProperty
オブジェクトにプロパティを追加したり、既存のプロパティの属性を変更したりします。
obj = Object.create Object:: # obj = {} 相当 Object.defineProperty obj, "name", value: "Titanium Mobile" writable: false # 追記できない enumerable: false # 列挙できない configurable: false # 再設定できない # 追記できない obj.name = "Titanium Alloy" # 列挙できない Object.keys obj # [] # 再定義できない Object.defineProperty obj, "name" value: "Appcelerator Cloud Services"
同時に複数のプロパティを操作できる Object.defineProperties というものもあります。
Object.keys
上でチラリと使っていますが、 Object のプロパティ名一覧を配列で取得することが出来ます。 defineProperty で enumerable を false にしているプロパティは列挙されません。
win = Ti.UI.createWindow title: "My awesome window" backgroundColor: "#FFFFFF" Object.keys win # ["title", "backgroundColor"]
Object.seal
オブジェクトを封印するためのメソッドです。封印。かっこいい。でも名前に惑わされてはいけません。封印前に定義したプロパティは変更可能です。また、オブジェクトの再定義は不可能になります。
- writable: true
- configurable: false
win = Ti.UI.createWindow title: "My awesome window" backgroundColor: "#FFFFFF" # 封印! Object.seal win # でも封印前に定義したプロパティは変更できちゃう win.title = "My super awesome window" # OK # 新しいプロパティは定義できない win.backgroundImage = "beautiful.png" # NG
Object.freeze
オブジェクトを凍結するためのメソッドです。カッチカチにします。
- writable: false
- configurable: false
win = Ti.UI.createWindow title: "My awesome window" backgroundColor: "#FFFFFF" # 凍結! Object.freeze win # 変えられないよ win.title = "My super awesome window" # NG
Object.preventExtensions
オブジェクトに新しいプロパティを追加できないようにします。 Object.seal と異なり、オブジェクトの再定義は可能です。
- writable: true
- configurable: true
win = Ti.UI.createWindow title: "My awesome window" backgroundColor: "#FFFFFF" Object.preventExtensions win # プロパティは変更可能 win.title = "My super awesome window" # OK # 新しいプロパティは定義できない win.backgroundImage = "beautiful.png" # NG
Object.seal, Object.freeze, Object.preventExtensions はどれも新しいプロパティは挿入できないという共通点を持っていて、既存のオブジェクトに対する書き込み権とオブジェクト自体の再定義権をどう設定するのか、という点が異なります。
これらに対応する確認用メソッドに Object.isSeal, Object.isFreeze, Object.isExtensible が用意されています。
Object.getOwnPropertyDescriptor
オブジェクトのプロパティの属性を記述するためのオブジェクトを取得します。
win = Ti.UI.createWindow title: "My awesome window" backgroundColor: "#FFFFFF" descObj = Object.getOwnPropertyDescriptor win, "title" # win.title の writable 属性を false に設定する descObj.writable = false
Object.getOwnPropertyNames
列挙可能、不可能に関わらず、オブジェクトのプロパティ名、メソッド名を配列で列挙します。列挙可能のプロパティ名だけを取りたい場合は Object.keys で。
obj = Object.create Object:: # obj = {} 相当 Object.defineProperty obj, "name", value: "Titanium Mobile" writable: false # 追記できない enumerable: false # 列挙できない configurable: false # 再設定できない Object.getOwnPropertyNames obj # ["name"]
Date.toISOString
ISO 形式の日付表現を取得します。
dt = new Date() dt.toString() # "Thu Feb 07 2013 22:21:30 GMT+0900 (JST)" dt.toISOString() # "2013-02-07T13:21:30.393Z"
Date.now
1970/1/1 00:00 からのミリ秒を取得。 (new Date()).getTime() からの解放。
Date.now() # 1360243497226
Array.isArray
とてもファニーなメソッドです。 new Array() しちゃった場合も、リテラルで書いた場合もキチンと配列として認識してくれます。
Array.isArray (new Array()) # true Array.isArray [1,2,3,4,5] # true
Function.prototype.bind (Android だけ!)
関数内の this から参照させるオブジェクトを決定することが可能です。使い方によっては大変便利なメソッドですが、使えるのは Android だけです。
hoge = greet: () -> "Hello, #{this.name}!" bindedHoge = hoge.greet.bind name: "Titanium Mobile" bindedHoge() # Hello, Titanium Mobile!
Array.prorotype.indexOf
Java の ArrayList を使ったことがある方はご存じかと思います。引数に渡した値が配列に存在している場合、最初に見つけた箇所のインデックスを返します。厳密比較です。
data = [3, 5, 7, 11] data.indexOf 3 # 0 data.indexOf 7 # 2
Array.prorotype.lastIndexOf
indexOf の後方探索版です。後ろから探索して、見つけた箇所のインデックスを返します。
data = [3, 5, 7, 11, 3, 5, 7, 11] data.lastIndexOf 3 # 4 data.lastIndexOf 7 # 6
Array.prototype.every
配列の各要素をテストして、全ての要素が合格すれば true が返ってきます。テストは自分で定義します。
data = [3, 5, 7, 11] test = (val, idx, arr) -> if val % 2 == 1 return true else return false data.every test # true
Array.prototype.some
every が全ての要素をテストして合格したときに true を返すのに対して、 some は1つでも合格したら true が返ります。
data = [2, 4, 7, 6] test = (val, idx, arr) -> if val % 2 == 1 return true else return false data.some test # true
Array.prototype.forEach
既に使ったことがある方もいると思います。配列の各要素に対して順番にコールバック関数を1回実行します。
data = [3, 5, 7, 11] data.forEach (val, idx) -> console.log "#{idx}: #{val}"
単純な for 文を使ったループよりも遅いことに注意してください。
Array.prototype.map
配列の各要素に対してコールバック関数を実行し、得られた結果の配列を返却します。
data = [3, 5, 7, 11] mapped = data.map (val, idx) -> idx * val * 2 console.log mapped # [0, 10, 28, 66]
こちらも for 文よりは遅くなります。
Array.prototype.filter
配列の各要素に対してコールバック関数を実行し、結果が true のもののみで構成された配列を返却します。
data = [3, 5, 7, 11, "foo", "bar", {key: "val"}] filtered = data.filter (val) -> return (typeof val == "string" || typeof val == "object") console.log filtered # ["foo", "bar", {key: "val"}]
こちらも for 文よりは遅くなります。
Array.prototype.reduce
配列全ての要素に対してコールバック関数を実行し、最後の呼び出し後にコールバック関数の結果を集積したものを返却します。
data = [3, 5, 7, 11, "foo", "bar", {key: "val"}] reduced = data.reduce (prev, current) -> return prev + current console.log reduced # "26foobar[object Object]"
Array.prototype.reduceRight
reduce 処理を後方から行います。
data = [3, 5, 7, 11, "foo", "bar", {key: "val"}] reduced = data.reduceRight (prev, current) -> return prev + current console.log reduced # "[object Object]barfoo11753"