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 の一部が実装された

ついにきた。

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])

引数は

  1. target(Number): コピー先の開始Index値
  2. start(Number): コピー元の開始Index値
  3. 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に変換するメソッドです。

引数は

  1. arrayLike: Array-likeなオブジェクト
  2. mapfn:(Optional) Array.prototype.map に渡すような function
  3. 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("")
文字列分割

Array-likeなオブジェクトと言ってきたけど、文字列も行ける

var charList = Array.from("abcd");

文字列も@@iteratorを持つので、イテレータが使用され、サロゲートペアにも対応することになる。その分、"str".split("")より良いと思われる。

はてなブックマーク拡張のコメント表示機能を修正してみた

キャプチャ画像

パッチ

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)

キャプチャ画像 キャプチャ画像

少々トリッキーだが、なんとかAustralisのパネルに対応した。アイテムがパネル内にある場合とツールバーにある場合とで挙動を分けるのに苦労した。

元ステータスバーのアイテムは3つのUIが固まっている。

  • ブックマーク追加・編集ボタン
  • ブックマーク数、兼、はてなブックマークページへジャンプするボタン
  • コメント表示ボタン

UIが2つだったら、Firefoxのブックマークボタンと似た作りにすれば良かったが、3つの場合は無理だった。
仕方なく以下の様なことをした。

  • 更に1つ、パネルボタン用UIを追加
  • パネル内にある場合は他の3つを非表示、ツールバーにある場合はボタンを非表示するスタイルを追加
  • パネルボタン用UIがクリックされたら自力でパネルのメニューを表示するコードを追加
バグ
元からでは無い気がするが、コメント表示のポップアップがクリック一回目は一瞬で閉じてしまう。もう一度クリックすると表示できるのだが…。 パッと見で修正できそうになかったので、とりあえず放置。誰か治して−。

直した: コメントビューのバグ修正 · be3df8c · teramako/hatena-bookmark-xul

追記(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エンジンでの実装が気になり始める。もっと凄いことをしているのでは!?

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リピートという綺麗な数値での爆速さがうかがい知れる感じで面白いと思う。