Firefox 33でES6のSymbolが実装された
不完全ながら ES6 の Symbol が実装された。
Symbolは、Undefined, Null, Boolean, Number, String に続く新たなプリミティブな型である。
既にChromeのV8エンジンには実装されていて、Symbolについて - JS.nextにて記事になっているので、どんなものかは、こちらを参照すると良いと思う。
ただ、一点、V8も含め、Object.getOwnPropertySymbols()
が実装されていて、完全にプライベートなプロパティを作れるわけではないことには注意。
var obj = (function(){ var o = {}; var sym = Symbol(); o[sym] = "foo"; return o; }()); obj[Object.getOwnPropertySymbols(obj)[0]]; // === "foo";
Firefox 33 で ES6 の TemplateLiteral の一部が実装された
- 1021368 - Implement NoSubstitutionTemplate as described in ES6 draft 11.8.6
- 11.8.6 Template Literal Lexical Components
ついにきた。
「JavaScript にはヒアドキュメントがない」などと言われてきた。そんなディスりとはおさらばだ。
JavaScriptの文字列リテラルは"
や'
でくくることだった。が、これらで複数行を実現するには少々面倒であった。
しかし、今回の実装でもう一つ、`
(バッククォート)でくくるという表現ができるようになった。
var str = `a
b
c
d`;
と書けるようになる。
TemplateLiteralには他にも機能があるのだが、今回の実装はここまで。
最終的には、以下の様に、${}
内に式を埋め込めたり、関数`...`
という記述で関数に値を渡すことができるようになるはず。
var obj = { tag: "p", class: "fooClass" }; html`<${obj.tag} class="${obj.class}">hoge foo bar</${obj.tag}>` // <p class="fooClass">hoge // foo // bar</p>
追記(2014-06-25)
${expr}
による、式の埋め込みが実装された。
Firefox 32(Nightly) で Array.prototype.copyWithin が実装された
ECMAScript 6th 仕様の Array.prototype.copyWithin 実装。
Array.prototype.copyWithin (target, start [, end])
引数は
- target(Number): コピー先の開始Index値
- start(Number): コピー元の開始Index値
- end(Number)(Optional): コピー元の終了Index値。省略時は、その配列のlength
となっている。
startからend間の要素をそれぞれtargetから連なる要素へ可能な限り*1代入するメソッドである。新たな配列に対してではなく、対象オブジェクトに対して実施するので破壊的である。また、Array特有ではなく、Array-likeなオブジェクトに対して使用できるように設計されているみたい。
例
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.copyWithin(0, 8); // 8,9番目の値を、0, 1 番目へ代入 // => [8, 9, 2, 3, 4, 5, 6, 7, 8, 9]; arr.copyWithin(0, -2); // マイナス値も受け付ける // => [8, 9, 2, 3, 4, 5, 6, 7, 8, 9]; arr.copyWithin(0, 3, 5); // 3,4番目の値を 0,1番目へ代入 // => [3, 4, 2, 3, 4, 5, 6, 7, 8, 9]; arr.copyWithin(0, 5, 3); // start > end の場合、何もしない
最後、start > end の場合、何もしない
と書いた。あくまで現行Draft rev25仕様の話なので今後どうなるか分からないけど、ちょっと不親切な感じがする。
*1:配列のlengthを超えて代入はしない
Firefox 32 で Array.from が実装された
Array-like な値をArrayに変換するメソッドです。
引数は
- arrayLike: Array-likeなオブジェクト
- mapfn:(Optional) Array.prototype.map に渡すような function
- thisArg:(Optional) 同上
Array-likeなオブジェクトとは以下の様なもの
@@iterator
のイテレータを持つものlength
プロパティを持つもの
length
プロパティを持つものより、@@iterator
の方が優先度が高いので注意
使用例
思いつくものをテキトウに
HTMLCollection, NodeList に対して、forEach, map, filter 等の適用
jQueryとやらがあるから別に…と思われそうだけど。
// fooClassの要素のタグ名を抽出 var tagNames = Array.from(document.querySelectorAll(".fooClass"), node => node.localName); // 普通にmapを使っても良い var tagNames = Array.from(document.querySelectorAll(".fooClass")).map(node => node.localName); // 属性を付けてみたり Array.from(document.querySelectorAll(".fooClass")).forEach(node => node.setAttribute("hidden", "true"))
文字列生成
forEach, map, filter は Sparse-Array(lengthのみを持つArray, Array(10) とかか生成されたArrayのこと)との相性が悪かったが、Array.fromを使用するとindex値の方は与えられるので以下の様なことも。
Array.from(Array(26), (v, i) => String.fromCharCode(i + 97)).join(""); // => "abc....xyz"
ま、ArrayComprehensionを使うともう少し短くかけるんだけどね。
[for (i of Array(26).keys()) String.fromCharCode(i + 97)].join("")
はてなブックマーク拡張のコメント表示機能を修正してみた
パッチ
diff --git a/chrome/content/browser/15-CommentViewer.js b/chrome/content/browser/15-CommentViewer.js index 9edd1d1..875b246 100644 --- a/chrome/content/browser/15-CommentViewer.js +++ b/chrome/content/browser/15-CommentViewer.js @@ -15,7 +15,7 @@ elementGetter(this, 'commentFooter', 'hBookmark-comment-footer', document); elementGetter(this, 'listContainer', 'hBookmark-comment-list-container', document); elementGetter(this, 'list', 'hBookmark-comment-list', document); elementGetter(this, 'listDiv', 'hBookmark-comment-div', document); -elementGetter(this, 'addonbar', 'addon-bar', document); +elementGetter(this, 'bottombox', 'browser-bottombox', document); elementGetter(this, 'faviconImage', 'hBookmark-comment-favicon', document); elementGetter(this, 'titleLabel', 'hBookmark-comment-title', document); @@ -141,7 +141,7 @@ var CommentViewer = { data.publicCount = data.bookmarks.length; data.privateCount = data.count - data.publicCount; panelComment.setAttribute('hTransparent', true); - panelComment.openPopup(addonbar, 'before_end', -20, 0, false, false); + panelComment.openPopup(bottombox, 'before_end', -20, 0, false, false); CommentViewer.updateViewer(data); commentButton.setAttribute('loading', 'false'); },
追記
とりあえずのコミットをしてみた。
Australisでこの手のアイテムは上層部になってしまうので、コメント表示のパネルは吹き出し付きの<panel type="arrow" ...>にした方が良いかもなあ。
追記(2014-05-25)
- はてなコメントビューワーの改善 · af12ef8 · teramako/hatena-bookmark-xul
- 元ステータスバーのアイテムをパネル化 · 9d45b54 · teramako/hatena-bookmark-xul
少々トリッキーだが、なんとかAustralisのパネルに対応した。アイテムがパネル内にある場合とツールバーにある場合とで挙動を分けるのに苦労した。
元ステータスバーのアイテムは3つのUIが固まっている。
- ブックマーク追加・編集ボタン
- ブックマーク数、兼、はてなブックマークページへジャンプするボタン
- コメント表示ボタン
UIが2つだったら、Firefoxのブックマークボタンと似た作りにすれば良かったが、3つの場合は無理だった。
仕方なく以下の様なことをした。
- 更に1つ、パネルボタン用UIを追加
- パネル内にある場合は他の3つを非表示、ツールバーにある場合はボタンを非表示するスタイルを追加
- パネルボタン用UIがクリックされたら自力でパネルのメニューを表示するコードを追加
バグ
追記(2014-05-26)
どこに表示すべきか迷ったが、結局パネルのヘッダー部分にブックマーク数を表示することにした。
よくあることだが、id 指定で1つしか無いことを前提に書かれているのを複数対応できるようにするのはなかなかに苦労する。
ついでに、データ同期のところで失敗しているっぽかったので、修正した。
PanoのソースをGitHubに再アップした
自分自身が再度使う気になったので https://github.com/teramako/Pano へ再アップした。
GitHubにアップしたとはいえ、自閉モードにするつもり。
- 要望等のメール無視
- Issue管理しない
- PullRequest、たぶん無視
- バイナリの提供はしない
- xpiファイルは自分でビルドしてね
- 他の人が勝手に配布するのは自由
今後の予定
- 必要なさそうな機能の削除
- クローズボタンの削除
- セッションのexport/import機能の削除
- シングルクリックで選択のみに
- 複数選択できる機能の削除
- タブ選択履歴の戻る/進む機能削除
前は調子に乗って、要望に答えたりして自分が管理しきれる範囲を超えてしまった反省がある。要らないと思ったものは削除してきたい。
問題は 836758 - Convert Panorama into an add-on and remove it from Firefox であるが、、Panoramaが拡張機能化したら、その時考える。
String repeat のアルゴリズムとパフォーマンス
ES6になると、String.prototype.repeatのメソッドが追加されるわけだが、そのアルゴリズムとパフォーマンスを追ってみている。
ES6 String.prototype.repeat の仕様では以下の様な感じでシンプルな書き方をしている。
- countが 0 より小さい、または 無限大である場合は RangeError
- count 0 ならば、空文字列
- そうでない場合は、count回、文字列を繰り返して連結する
単純に実装すれば、以下の様な感じで済む。
String.prototype.repeat = function (count) { if (count < 0 || !Number.isFinite(count)) throw new RangeError(); var result = "", str = this; for (var i = 0; i < count; ++i) { result += str; } return result; };
他にも配列を作ってjoinする方法も考えつく
String.prototype.repeat = function (count) { var arr = new Array(count), str = this; for (var i = 0; i < count; ++i) { arr[i] = str; } return arr.join(""); };
String.prototype.repeat = function (count) { return (new Array(count + 1)).join(this); }
しかし、これらは結局、count回繰り返す処理であり、ループ数が線形的に増加し非効率である。
スピードを求めるならループ数の一次的増加は悪である。
もう少し工夫をしよう。
Firefox(SpiderMonkey)の初期実装
細かいチェックを省くと概ね以下の様な実装であった
String.prototype.repeat = function (count) { if (count === 0) return ""; var str = this, result = str; for (var i = 1; i <= count / 2; i *= 2) { result += result; } for (; i < count; i++) { result += str; } return result; };
- 2, 4, 8 ... と 2のべき乗で収まる範囲の回数までは、結果となる値自身を積み上げていく
- 512回なら 9回のループ、1024回なら10回のループで済む
- あまりは、単純ループで追加していく
というアルゴリズムであった。
最初のループでは高速道路に乗った気分で気持よく進み、256, 512, 1024 などの綺麗な数値だと速攻で処理される。しかし、1023回などになると、512回までは気持ちよく進んだ後に2番めのループで511回もループするはめになってスピードが極度に下がってしまう。
修正された実装
落差の激しいアルゴリズムだった実装が次の変更で改善される
String.prototype.repeat = function (count) { var result = "", str = this; for (;;) { if (count & 1) { result += str; } count >>= 1; if (count) { str += str; } else { break; } } return result; };
わけがわからない…
が、ビット演算をしているので2進数で考えてみたらなんとなく見えてきた。
13回ほどのリピートを考えてみる。13は 1101(2) である。ビットが立っているところでstr += str
で積み上げた文字列をresultに追加して行こうという処理である。
結果、ループ数は2進数の桁分で済む。
すばらしい…、自分のような凡俗にはとても思いつかないアルゴリズムだ。感動した。
一方、Chrome(V8)の実装
そうなると、V8エンジンでの実装が気になり始める。もっと凄いことをしているのでは!?
- コミット: https://codereview.chromium.org/21014007/patch/12001/13003
- 該当コード: http://code.google.com/p/v8/source/browse/trunk/src/harmony-string.js#38
String.prototype.repeat = function (count) { var elements = new Array(count), str = this; for (var i = 0; i < count; i++) { elements[i] = str; } return elements.join("") };
なんか…とても残念でした。
次に期待することにしましょう。
パフォーマンス
そんなこんなを思いながら、書いてみた jsperf
いろいろ試行錯誤していたので、綺麗なグラフではないけど、String.prototype.repeatにおけるFirefox(SpiderMonkey)の優秀さ/Chrome(V8)の残念さ、1024リピートという綺麗な数値での爆速さがうかがい知れる感じで面白いと思う。