Firefox 27(Nightly) における新たな String.prototype["@@iterator"]

ちょいと面白いものが実装されて、String.prototype["@@iterator"]がアップデートされてた。

"@@iterator" というのは、ECMAScript 6th におけるイテレータとなるメソッドで、最近注目のジェネレータオブジェクトを返す。*1

で、このジェネレータオブジェクトのnextメソッドが、従来であればインデックス値に沿って順に値を返していたのだが、これからはUnicodeポイントに沿って値を返すようになる。UTF-16 においてサロゲートペアとなるような文字は従来は2文字として扱われていたのが、Unicodeポイントとして1文字として扱われるようになるのだ。

変更されたのはあくまでジェネレータ部分であって、インデックス値の扱いは変わっていないし、length プロパティも従来通りUTF-16換算なので注意。*2

文字 コードポイント UTF-16
𠀋 U+2000B \uD840\uDC0B
𡈽 U+2123D \uD844\uDE3D
𡌛 U+2131B \uD844\uDF18

上記の文字を例にしてみると
あ...orz はてなダイアリーだと &#... に変換されちゃうのね...orz 𠀋=𠀋 𡈽=𡈽 𡌛=𡌛 です。

var str = "𠀋𡈽𡌛"

console.log("length", str.length);
console.log("count", [...str].length);

for (var c of str) {
  console.log(c, c.length);
}

これが、以下のように出ることになる。

"length" 6
"count" 3
"𠀋" 2
"𡈽" 2
"𡌛" 2

今まではサロゲートペアまで考慮した文字数のカウントは面倒だったが、上記コードのように[...str].lengthと簡単に書ける。

2014-01-06 追記

まだ実装されてないけど、ものかの » Unicodeの特殊な文字 “結合文字列”にあるようなものがると駄目になるので、String.prototype.normalize で正規化する方が良さそう。

よって、もう少しきちんとカウントするには [...str.normalize()].lengthが良さそう。

*1:本来なら Symbol という型のプロパティ(文字列ではない)であるが、FirefoxJavaScript エンジンには Symbols が実装されていないので、文字列プロパティとして存在している。

*2:ここの部分に変更が入ることはないと思う。Break the web って怒られちゃう

addons.mozilla.org に登録していたアドオンを無効化した

Firefox 用の Pano とか Thunderbird 用の WAT とかを無効化した。

理由は、随分前からメンテできてなくて、これからもするかどうか怪しいから。そのくせ、たまに質問メールらしきものが来る。いい加減面倒になってきたのだ。

ソースは GitHub にあるわけだが、本当はこちらも削除しようかと思っているくらい。これだけ放置されていてフォークしてどうのこうのしようとする人が現れないわけで、オープンソースとして公開しておけば誰かがメンテしてくる、なんて一部の人気ソフトに限られた幻想なんだなと思う。

new 演算子と Construct 内部メソッド

var o1 = [],
    f1 = function(){ return o1; };
(new f1()) === o1; // true

var o2 = 1,
    f2 = function(){ return o2; };
(new f2()) === o2; // false

これってどういうことだってばよ? という話。

例によって仕様から解説してみようかと。

new 演算子

new 演算子自体は実は大したことはしていない。

  • 対象が function であること
  • 対象の function に [[Construct]] 内部メソッドがあること
  • [[Construct]]を呼び出した結果を返す

要するに[[Construct]]が肝である。

[[Construct]] 内部メソッド

  1. 空オブジェクト obj を作成
  2. obj の [[Prototype]] にコンストラクタのprototypeプロパティをセット
  3. obj が this となるようにコンストラクタをcallし結果を result にセット
  4. result が Object なら result を返す
  5. obj を返す

だいたいこんな流れ。最後の条件分岐が今回の肝。

最初のコードに戻ると、f1 では Arrayオブジェクト(typeof [] === "object")を返すのでそれを返し、f2 ではプリミティブなnumberを返すので、それは無視されて作成したobjを返すという仕組になっている。

コードにしてみる

言葉で書くと、分かりにくいかもしれないのでコードにしてみる。

Function.prototype.new = function () {
  var obj = Object.create(this.prototype);
  var result = this.apply(obj, arguments);
  if (typeof result === "object")
    return result;

  return obj;
};

こんな感じ。

Mac で特定プロファイルの Firefox を起動するアプリを作る

もの凄く基本的なことなんだけど、忘れて Google 先生のお世話になることが多いのでメモっておく

コマンドラインからだったら、

open -a Firefox --args -P profileName -no-remote

でOKだが、いちいちコマンドラインから起動するのは面倒なこともあって、自分は Hoge.app のアプリケーションの形で作っておくのが好み。Spotlight から検索して実行もしやすいし。

APP_NAME=FirefoxHoge.app
FIREFOX_PATH=/Applications/Firefox.app

mkdir -p ${APP_NAME}/Contents/{MacOS,Resources}
# FirefoxHoge.app
# └── Contents
#     ├── MacOS
#     └── Resources

cp ${FIREFOX_PATH}/Contents/Resources/firefox.icns ${APP_NAME}/Contents/Resources

vi ${APP_NAME}/Contents/MacOS/firefox.sh # ... 起動シェル

vi ${APP_NAME}/Content/Info.plist # ... プロパティリスト

touch ${APP_NAME} # 最後に touch しておくとキャッシュがクリアされる?

起動シェル

#!/bin/sh
exec ~/opt/FirefoxNightly.app/Content/MacOS/firefox -P nightly -no-remote "$@"

プロパティリスト

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleName</key>
	<string>Nightly</string>
	<key>CFBundleExecutable</key>
	<string>firefox.sh</string>
	<key>CFBundleIconFile</key>
	<string>firefox.icns</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleSignature</key>
	<string>???</string>
  <!-- 以下、あってもなくてもOK -->
	<key>CFBundleIdentifier</key><!-- 定めておくと、keyRemap4Macbook で独自のカスタマイズが可能 -->
	<string>org.teramako.firefox.nightly</string>
	<key>CFBundleShortVersionString</key>
	<string>27.0a1</string>
	<key>CFBundleGetInfoString</key>
	<string>Nightly 27.0a1</string>
	<key>CFBundleDisplayName</key>
	<string>Firefox Nightly</string>
</dict>
</plist>

Promise + Generator

javascripter さんの記事ではPromise実装としてjQueryのを使用しているけど、Fx では DOM Promise が使えるので、それに合わせて書きなおしてみた。

serialExperimental.js

function serialExperimentalProceed (generator) {
  var thread = generator();
  serialExperimentalProceed.proceed(thread);
}
serialExperimentalProceed.proceed = function proceed (thread) {
  var { done, value } = thread.next();
  if (done)
    return;

  if (value instanceof Promise)
    value.then(
      result => {
        proceed(thread);
      },
      error  => {
        console.error(String(error));
      });
  else
    proceed(thread);
};

function async (ms, func) {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      try { 
        func();
        resolve();
      } catch (e) {
        reject(e);
      }
    }, ms);
  });
}

serialExperimentalProceed(function * () {
  var startTime = Date.now();
  echo("Start: " + startTime);
  yield async(1000, () => {
    echo("Hello");
  });
  echo("+ " + (Date.now() - startTime));
  yield async(1500, () => {
    echo("Serial Experimental");
  });
  echo("+ " + (Date.now() - startTime));
  yield async(1500, () => {
    echo("Proceed");
  });
  echo("End: + " + (Date.now() - startTime));
});
echo("start");

lain.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Serial Experimental proceed</title>
  <style>
#out { line-height: 1.5; }
#out > p { margin: 0; }
  </style>
</head>
<body>
<h1>Serial Experimental Proceed</h1>

<pre id="out"></pre>
<script>
var out = document.getElementById("out");

function echo (...args) {
  var msg = args.join(" ");
  var p = document.createElement("p");
  p.appendChild(document.createTextNode(msg));
  return out.appendChild(p);
}
</script>
<script src="serialExperimental.js"></script>
</body>
</html>

Firefox 27(Nightly) の yield が { done, value } を返すようになった

ちょっと前に、Generator のプロトタイプ で書いたが、function * () { yield ...; } な構文が導入された。

また、最近ユーザ作成の Generator.next() が { done: Boolean, value: Value } なオブジェクトを返すように変更された。*1

function * gene () {
  for (var i = 0; i < 3; ++i) {
    yield i;
  }
}

var geneObj = gene(),
    done, value;
while ({ done, value } = geneObj.next(), !done) {
  console.log(value);
}

と、まぁこんな感じに書ける*2

ただし

  1. この変更にfor-of等の構文が追いついていない
  2. ビルトインでgeneratorを返すものも、この仕様になっていない
    • → ビルトインのgeneratorとfor-ofは正常動作する

ということで、Bugzilla へ報告済み

追記(2013-09-28)

Mozilla JavaScript 1.7 あたりから使える*が付かない Generator の挙動は変更されていない様子。

function gene () {
  for (var i = 0; i < 3; ++i) {
    yield i;
  }
}

var geneObj = gene(); 
try {
  while (true) {
    console.log(geneObje.next());
  }
} catch (e) {
  console.error(e);
}
// 0
// 1
// 2
// [object StopIteration]

という結果になり、従来通りの挙動。

*付きGenerator は最近実装されたばかりだし、Firefox内部やアドオンでも使われていないだろうから、何かが動かなくなったとかそういうのはないと思われる。

追記(2013-10-05)

により、修正された。

また、同時に、今まで iterator というメソッドがあると for-of 等で iterable だと判断されていたものが、この修正により "@@iterator" というメソッド名に代わった。

ES6 では @@*** なプロパティはSymbol型*3であるが、この修正ではまだ文字列であり、ECMAScript 6th valid な変更ではない点に注意。このあたり、Symbold オブジェクトが実装され次第、順次変わって行くと思われ、変更が目まぐるしい箇所になりそう。

*1:該当バグやコミットが見つけられない…orz

*2:V8エンジンでは分割代入が実装されてないのでwhileの条件く部分は動かないよ

*3:typeof @@iterator === "symbol"

Firefox 27(Nightly) にて SpreadCall が実装された

func (a, b, ...iterableObject);

な感じで、イテレート可能なオブジェクトを展開して、それぞれの引数に分けてくれるもの。

func (a, b, ...[1,2,3,4]) とすると、func (a, b, 1, 2, 3, 4)としたのと同等になる。

テストコード

mozilla-central file search "js/src/jit-test/tests/basic/spread-call" あたりから見ると良いよ

SpreadOperator

ECMAScript 6th 的には SpreadOperator という...演算子でできることの一つとなる。

MDMも更新されている模様。