imthinker::net

ふらふらうぇぶろぐ

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"

参考資料