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 という型のプロパティ(文字列ではない)であるが、Firefox の JavaScript エンジンには 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]] 内部メソッド
- 空オブジェクト obj を作成
- obj の [[Prototype]] にコンストラクタのprototypeプロパティをセット
- obj が this となるようにコンストラクタをcallし結果を result にセット
- result が Object なら result を返す
- 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。
ただし
- この変更にfor-of等の構文が追いついていない
- ビルトインで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 オブジェクトが実装され次第、順次変わって行くと思われ、変更が目まぐるしい箇所になりそう。
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" あたりから見ると良いよ