「JavaScript」と一致するもの

初めに断っておきますが、今回の調査は、node v0.11.5 (Linux)で試したもので、他の環境では異なるかもしれません。さらに、今後のバージョンアップによっては全く異なる結果となることも十分ありえます。ご注意を。

ES6にgeneratorsが入るとのことで、JavaScriptでcontinuationが使えないかなぁと思っていた自分としては、興味を持ちました。

ちなみに、generator, coroutine, continuationの順に記述力が上がるそうです。こちらが参考になるかもしれません。

さて、generatorsの説明は他に任せるとして、いきなりコードです。

function* es6_generator() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  return 5;
}

これを複数回実行して時間を計測するコードはこちら。

console.time('es6_generator');
for (i = 0; i < 1000000; i++) {
  iterator = es6_generator();
  obj = {};
  while (!obj.done) {
    obj = iterator.next();
  }
}
console.timeEnd('es6_generator');

実行結果は、

es6_generator: 360ms

でした。うちのマシンは非力なので、最近のマシンならもっと速いかもしれません。

これと同等のコードをgeneratorsを使わずに書くとどうなるでしょうか。おそらく、

function hand_generator1() {
  var iterator = {};
  iterator.next = function() {
    iterator.next = function() {
      iterator.next = function() {
        iterator.next = function() {
          iterator.next = function() {
            return {value: 5, done: true};
          };
          return {value: 4, done: false};
        };
        return {value: 3, done: false};
      };
      return {value: 2, done: false};
    };
    return {value: 1, done: false};
  };
  return iterator;
}

こんな感じになるのでしょう。 これを同じように実行したら、

hand_generator1: 4529ms

となりました。これが等価なコードなのかは問題ですが、 少なくともhand_generator1のように書くよりは、 es6_generatorの方が速いとうことですね。

しかし、この単純な例だとfunctionをイテレーション毎に生成する必要はなくて、もっと効率的に書けます。

function hand_generator2() {
  var array = [1, 2, 3, 4, 5];
  var index = 0;
  var iterator = {
    next: function() {
      return {value: array[index++], done: array.length <= index};
    }
  };
  return iterator;
}

この場合の実行結果は、

hand_generator2: 265ms

となりました。es6_generatorと比べてどちらが読みやすいと思うかは人次第でしょうか。できることが違うので比較の対象にならないですが。

さて、少し話がややこしくなりますが、もともとは末尾再帰をやりたかったのです。末尾再帰の説明は他に任せるとして、factorialをgeneratorsを使って書いてみました。

function fact_tail_loop1(x) {
  var fact_tail_sub = function* (x, r) {
    if (x === 0) {
      return r;
    } else {
      yield fact_tail_sub(x - 1, x * r);
    }
  };
  var c = fact_tail_sub(x, 1);
  var d = {};
  while (!d.done) {
    d = c.next();
    c = d.value;
  }
  return d.value;
}

これを見て、ふーん、とか、おー、とか思わない場合は、この先を読んでもつまらないかもしれません。

実行コードはこちら。

console.time('fact_tail_loop1');
console.log(fact_tail_loop1(3));
console.log(fact_tail_loop1(5));
console.log(fact_tail_loop1(10));
console.log(fact_tail_loop1(100));
console.log(fact_tail_loop1(1000));
console.log(fact_tail_loop1(10000));
console.log(fact_tail_loop1(100000));
console.log(fact_tail_loop1(1000000));
console.timeEnd('fact_tail_loop1');

実行すると次のようになりました。

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop1: 217ms

Infinityになってますが、ちゃんと計算はしているようです。ちなみに普通の再帰やproperでない末尾再帰の場合は、最後の方は計算できずにエラーになります。

さて、generatorsを使わないで書き直してみました。

function fact_tail_loop2(x) {
  var fact_tail_sub = function(x, r) {
    return {
      next: function() {
        if (x === 0) {
          return {
            value: r,
            done: true
          };
        } else {
          return {
            value: fact_tail_sub(x - 1, x * r),
            done: false
          };
        }
      }
    };
  };
  var c = fact_tail_sub(x, 1);
  var d = {};
  while (!d.done) {
    d = c.next();
    c = d.value;
  }
  return d.value;
}

同じように実行すると、

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop2: 796ms

になりました。しかし、このコードはオブジェクトの生成が多すぎます。改良版はこちら。

var LoopCont = function(f) {
  this.f = f;
};

function fact_tail_loop3(x) {
  var fact_tail_sub = function(x, r) {
    if (x === 0) {
      return r;
    } else {
      return new LoopCont(function() {
        return fact_tail_sub(x - 1, x * r);
      });
    }
  };
  var c = fact_tail_sub(x, 1);
  while (c instanceof LoopCont) {
    c = c.f();
  }
  return c;
}

これを実行すると、

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop3: 63ms

となりました。いかがでしょう。

私見ですが、generatorsのパフォーマンスは状況次第といったところでしょうか。v8の最適化が進めば、様々なケースで改善されるかと思われます。

しかし、generatorsを使って末尾再帰やcontinuationを実装できないかなぁ、と期待したのでちょっと残念でした。やっぱり言語レベルでサポートしてほしいですね。

個人的には、yield expressionは構造が分かりにくく感じ、好きになれそうにないので、直接使うことはないかと思います。うまく隠蔽してくれるライブラリがあればいいのかもしれません。

ここまで読んでくれた方は、 https://github.com/dai-shi/continuation.js にも興味を持っていただける可能性がありますので、 よろしければご覧ください。

jsdomへのpull request: XHRサポート

  • 投稿日:
  • by

connect-prerendererをちゃんと動かすためにやったjsdomの修正その2をpull requestにしました。

https://github.com/tmpvar/jsdom/pull/654

実はXHR自体は、 node-XMLHttpRequest をそのまま使うだけでほとんど苦労はありませんでした。

大変だったのは、クッキーを引き継ぐところでした。 そもそもXHRを使わない場合も、jsdomはクッキーの引継ぎをサポートしていませんでした。XHRを使わない場合というのは、JavaScriptやCSSや画像ファイルなどを読み込む場合です。

まず、JavaScriptのロード時にもクッキーを引き継ぐようにするコーディングをしてから、XHRでもクッキーを引き継ぐようにしました。XHRはnode-XMLHttpRequestの内部のコードまで理解しなくてはならず、苦労しました。ちょっと強引に実装したため、もしかしたら将来のバージョンのnode-XMLHttpRequestでは動かないかもしれません。

pull requestは無事マージされて、jsdom v0.8.1がリリースされました。興味がある方はお試しください。

https://npmjs.org/package/jsdom

connect-prerenderer もこのバージョンを使うように修正しました。

https://npmjs.org/package/connect-prerenderer

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

Googleリーダがなくなるから、それの代替として作ろうと始めましたが、Googleリーダの期限までには完成しそうにありません。

今日は、RSSの記事のリンクを開く機能を作りました。 CanvasではHTMLは表示できません。そこで、Canvasの外側に独立して iframeを使うことで表示しました。 iframeの表示はjQueryを使いました。KineticJSのようなpure JavaScriptを使っていると、DOM操作はちょっと古びたものに感じます。

さて、RSSのリンクを開くためのボタンも作ってみました。 右上の方に矢印のアイコンを置いて、それを押すとiframeが開きます。 その矢印アイコンですが、Kineticのshapeで作成するのは面倒だったので、inkscapeで作成してSVG DataをKineticのpathで読み込みました。

コードはこんな感じです。

var path = new Kinetic.Path({
  x: 0,
  y: -6,
  data: 'M 23.618434,50.171286 41.144607,36.346769 23.23155,22.400304 l 0.0092,8.18132 c 0,0 -7.445838,-1.03921 -11.864782,3.095364 -4.4188829,4.134559 -4.3930939,13.742678 -4.3930939,13.742678 0,0 2.442808,-4.269676 6.9240349,-5.841822 4.481182,-1.572147 9.583341,-0.746586 9.583341,-0.746586 l 0.127874,9.340028 z',
  fill: '#f0f0f0',
  scale: 0.43
});

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらです。

http://canvas-rss-reader.herokuapp.com/

未読既読の機能までできたら、とりあえずは使えるようになるかと思うのですが、どうでしょう。

JavaScriptでランダムに色を生成したいと思いました。

乱数はMath.random()で生成できます。 これを使って、RGBの値を生成することはできますが、 安直に生成すると灰色っぽい色になってしまいます。

ここはやはり、明るさや彩度は固定して色相だけどランダムにしたいところでしょう。

JavaScriptで色を操作するライブラリを探しました。

http://stackoverflow.com/questions/8022885/rgb-to-hsv-color-in-javascript

を参照して、見つけたよさそうなライブラリ2つ。

TinyColorは現在もアクティブに開発されているようで、安心できます。 一方、color.jsは3年前のコミットが最後です。正確には、README.mdは更新されているので、放置されているわけではなく、コードが枯れているのだと思います。

今回は、color.jsを使いました。理由は、今回やりたことについては十分機能があり、コードもきれいで短いからです。

多機能を求める場合は、TinyColorを使うと思います。こちらは、node.jsでも使えるように書かれています。

ランダムに色を生成するには、次のようにしました。

function genRandomColor() {
  var hue = Math.random();
  var saturation = 1.0;
  var lightness = 0.5;
  return Color.hsl(hue, saturation, lightness).hexTriplet();
}

色操作のライブラリはもっと探せばいろいろあるかもしれません。また、機会があれば。

だいぶ間が空いてしまいましたが、HTML5を使ってどこまでスマホアプリが作れるのか、挑戦しようと進めています。しかも、フルキャンバスで。

題材はRSSリーダーです。RSS Pipesの補完機能として考えています。

前回はHellow Worldを表示するところまででした。今回は、RSSを取得してテキスト表示してみました。ただし、まだスクロールできません。当然クリックもできません。

RSSはjQueryを使って読み込むことにしました。コードはこんな感じです。

$.ajax({
  type: 'GET',
  url: rssurl,
  dataType: 'xml',
  success: function(xml) {
    var $xml = $(xml);
    var items = [];
    $xml.find("item").each(function() {
      var $this = $(this);
      var item = {
        title: $this.find("title").text(),
        link: $this.find("link").text(),
        description: $this.find("description").text(),
        pubDate: $this.find("pubDate").text()
      };
      items.push(item);
    });
    updateRssContent(items);
  },
  error: function() {
    alert('failed to get the rss: ' + rssurl);
  }
});

updateRssContent()でキャンバスにかきますが、座標も自分で計算しないといけません。

function updateRssContent(items) {
  $.each(items, function(index, item) {
    var text = new Kinetic.Text({
      x: 5,
      y: 5,
      text: item.title,
      fontSize: 12,
      fontFamily: 'Arial',
      fill: '#aaaaff'
    });
    text.setOffset({
      y: -index * (text.getHeight() + 5)
    });
    layer.add(text);
  });
  layer.draw();
}

setOffsetを使うよりいい方法があるのではないかと想像しますが、まだ探していません。

実行結果はこんな感じです。

実行結果20130519

最新版はherokuでも実行可能です。

http://canvas-rss-reader.herokuapp.com/

今回は、GitHubにコードをアップしました。

https://github.com/dai-shi/canvas-rss-reader/tree/42ddd815e88304c6217c8c5f9f978d97c39fc34a

そう、プロジェクト名もcanvas-rss-readerにしました。

しかし、キャンバスを使うとあたりまえですが文字列選択ができないですね。JavaScriptでクリップボードって制御できるのでしょうか。

http://www.w3.org/TR/clipboard-apis/

このあたりをウオッチすることになるのでしょうか。

Bootstrapもいいのですが、ちょっと巨大すぎるので、代替品がないかなと思って探しました。

要件は、

  • スマホでも使えるようにレスポンシブ対応
  • あまりできることは多くなくてよい、ボタンとかフォームとかの見た目がきれいになればよい
  • できれば、CSSだけでJavaScript不要のフレームワークがよい

といったところです。

色々探しましたが、有名どころでJavaScript不要と言う要件を満たすものはあまりなく、結局見つけたのがこれです。

レスポンシブ対応のサイトをサクッと作れるフレームワーク『InK』

http://ink.sapo.pt/

この「InK」というネーミングですが、何が困るって、検索キーワードとして使えないことです。Google検索すると、"do you mean link?"とかなってしまう。そんなわけで、使用感などのレポートが見つからなかったです。どれだけ有名なのかもよく分かりません。

また、日本だと、

http://d.hatena.ne.jp/keyword/InK

と混同しそうです。

さて、話を戻すと、Inkは要件を満たしています。全体的な設計がシンプルで、CSSだけで構成されていていい感じです。早速ちょっと使ってみて感じたのは、レイアウトの指定が面白いということです。Bootstrapだと12 spanでfixedとfluidがありますが、Inkだとパーセント指定です。また、fixedはなくてfluidだけのようです(未確認)。fixedも使いたいケースがあるので、それは残念なところです。で、面白いのは、「Multiple Layouts」というレイアウトの指定方法です。これは、CSSのメディアクエリを3通り用意して、それぞれのレイアウトをCSSのクラス指定で定義するのです。

うまく説明できないので、サンプルを。

<div class="ink-l50 ink-m50 ink-s100">
  エリア1
</div>
<div class="ink-l50 ink-m50 ink-s100">
  エリア2
</div>

このようにすると、大画面と中画面では、エリア1とエリア2が横に並んで2カラムのレイアウトになり、スマホなどの小画面では、エリア1とエリア2が縦に並んで1カラムのレイアウトになります。ちなみに、大画面、中画面、小画面が、それぞれ、"l", "m", "s"に対応します。

あと、Bootstrapで困っていたレイアウト時のスペース追加も、Inkではink-vspaceとink-gutterで解決しそうです。もっとも、細かい制御をするときは結局CSSを書くことになりそうですが。

とりあえずは、レイアウトだけ試して満足しました。

久しぶりにFacebookアプリを作ることにしました。以前作った時から仕様がちょっと変わったので、勉強し直しです。

ちなみに、以前作ったアプリは修正していないので、動かなくなってしまっています。Java & JSPでGAE向けに作ったのであまりやる気が出なくなってしまいました。Node.jsで書き直そうかしらと思いつつも、作り直すならもうちょっと全体設計から見直したいと思って手がつけられていません。

話を戻すと、FacebookのGraph APIを使うためのライブラリを調べました。

http://developers.facebook.com/tools/third-party-sdks/#nodejs

にいくつかリストアップされています。

リポジトリの名前が同じものがあり少しややこしいです。NPMのパッケージ名は違います。Star数が同じなのはたまたまのようです。

さて、少しずつ特徴があります。amachang/facebook-node-sdkとnode-facebook-sdkはオリジナルのFacebook PHP SDKを再現したライブラリになっています。Thuzi/facebook-node-sdkはブラウザ向けのFacebook JavaScript SDKと同じAPIをサーバ側でも実現したものです。これはとてもnode.jsらしいと思いました。fbgraphは、また路線が違ってJavaScriptっぽくJSON APIに書き直したような感じです。

今回はFacebookアプリと言ってもあまり複雑なことをするつもりがなかったのと、以前PHP SDKを見たことがある(そして、わざわざJavaに移植した)ので、PHP SDK互換のものを使おうと思いました。二者択一ならとりあえずStar数を信じてみようということで、amachang/facebook-node-sdkを入れてみました。

ところが、初めに作ったサンプルでつまずきました。applicationのaccess tokenを使ってGraph APIを呼ぶのですが、エラーになるのです。うーん、なぜだ、と思ってソースを眺めると、あれ?application access tokenを取得するAPIが想像していたもの(以前、Rubyのライブラリを使ったことがあった)と違います。こりゃ、動かないわけです。もしかしたら、Facebookの仕様が変わったのかもしれません。深追いするのは止めました。たぶん、user access tokenを使っている範囲では正常なのでしょう。

やはり、Star数の多いものから使おうと、Thuzi/facebook-node-sdkとfbgraphを天秤にかけます。README.mdを読んで比較しました。結果、fbgraphにはapplication access tokenを取得する方法が書いてなく、できるとしてもすっきりしないと予想しました。一方、Thuzi/facebook-node-sdkにはapplication access tokenを取得する方法が書いてありました。application access tokenはサーバ側のコーディングのみで使用可能なため、Facebook JavaScript SDKには入っていないnon-standardな機能です。

作ったサンプルも動作し、満足です。application access tokenを使おうとしている人には参考になるかもしれません。ただし、現時点での話であることをお忘れなく。将来的には状況は変わるかもしれません。

参考までに、コードの抜粋はこちらに。

RSS Pipesの一機能として、スマホ向けのRSSリーダーみたいなものを作ってみようかと思います。

まずは、KineticJSの勉強をしているところです。 HTML5 Canvas KineticJS Tutorials を参考にしながら作っていきます。

HTMLのコードを説明します。

初めに、KineticJSの読み込みます。

<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.4.2.min.js"></script>

試しに使う場合はこのままでいいでしょう。デプロイするときはローカルに持ってこようと思います。

次に、自分のスクリプトを読み込みます。

<script src="test.js" defer="defer"></script>

deferを指定しておくことで、HTMLのロード後に読み込まれます。

空のbodyにIDを指定しておきます。

<body id="container"></body>

こうしておくことで、KineticJSのstageをbodyの直下に作ります。

さて、ちょっとだけCSSが必要です。

html, body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

これを指定しないと、Canvasの周りにマージンができてしまって、Full Canvasになりません。また、overflowを指定しないと、ChromeやFirefoxでスクロールバーが出てしまいました。

続いて、本題のJavaScriptのコードを説明します。

KineticJSのステージを作ります。

var stage = new Kinetic.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight
});

widthとheightを表示領域一杯にすることで、Full Canvasとなります。

レイヤーを作ります。

var layer = new Kinetic.Layer();

表示領域一杯の四角形オブジェクトを作ります。

var rect = new Kinetic.Rect({
  x: 0,
  y: 0,
  width: stage.getWidth(),
  height: stage.getHeight(),
  fill: 'black'
});

黒にしてみました。

Hello Worldの文字オブジェクトを作ります。

var text = new Kinetic.Text({
  x: stage.getWidth() / 2,
  y: stage.getHeight() / 2,
  text: 'Hello World!',
  fontSize: 30,
  fontFamily: 'Arial',
  fill: 'lightgray'
});

ステージのサイズから計算して中央に配置しています。x,yは文字オブジェクトの左上の座標です。そこで、ちょっとずらして文字オブジェクトの中心に合わせます。

text.setOffset({
  x: text.getWidth() / 2,
  y: text.getHeight() / 2
});

最後に、四角形オブジェクトと文字オブジェクトをレイヤーに追加して、そのレイヤーをステージに追加して、終わりです。

layer.add(rect);
layer.add(text);
stage.add(layer);

これで、Canvasのみを使ってHello Worldを表示するページを作ることができました。アプリと呼べるようになるまでにはまだまだかかりそうですね。

実行結果はこんな感じです。

コードはGistにアップしておきました。 https://gist.github.com/dai-shi/5404537

RSS Pipesの一機能として、スマホ向けのRSSリーダーみたいなものを作ってみようかと思います。

RSSリーダーなんて世の中にたくさんあるので、普通のものを作っても面白くありません。そこで、勉強がてらHTML5でスマホアプリにしてみようと考えました。しかも、フルキャンバスで。canvasというのはグラフィック用の機能ですが文字も書けるわけで、それですべてを作ったら面白いのではないかと思いました。

さて、HTML5 Canvasは何年か前に少しだけ触りましたが、最近はライブラリも充実しているのではないでしょうか。検索してみると、

HTML5のcanvasを劇的に使いやすくするJavaScriptライブラリまとめ5つ

を見つけました。また、stackoverflowで、

Current State of Javascript Canvas Libraries(2012)?

を見つけました。使ってみないと分からないだろうと思いますが、これらの情報から、KineticJSを試してみることにしました。

今日のところはここまで。これから少しずつ進捗を書いていけたらいいなと思っています。

RSS Pipesは、JavaScriptでフィルターを書く、RSSアグリゲーターです。

RSSの登録数が増えてきたら検索機能を作ろうと思っていたのですが、 先に作ってしまいました。検索と言っても、表示されているものを絞り込むだけです。

AngularJSには標準でフィルターというものが組み込まれているので非常に簡単にできました。ソースコードに検索ボックス配置の1行と、フィルター追記の1行で済んでしまいました。Bootstrapのレイアウトでもう数行いじりましたが、トータルで5行の改変のみです。

興味ある方は、diffを見てみてください。

https://github.com/dai-shi/rss-pipes/commit/c250c8a7d9b03476503c78fda5a2f0ab5cef5894#views/partials/home.jade

お手軽です。詳しくは、AngularJSのチュートリアルを参照しましょう。

http://docs.angularjs.org/tutorial/step_03