タグ「JavaScript」が付けられているもの

だいぶ間が空いてしまいましたが、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/

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

突然ですが、Markdownでプレゼンスライドを作りたいと思いました。LibreOffice Impressを使うのは楽しくありません。最近使い慣れてきたMarkdownでさらっと作りたいです。そう、このブログもMarkdownで書いています。

Markdownで手軽に書く代わりに、見た目はそれなりに見えるものがいい、ということで、最近流行り(?)のHTML/CSSのプレゼンツールを調べてみました。

よさそうだなと思ったのは、

です。

プレゼンをmarkdownで書いたらええやん

にmarkdown2impress.plというのが紹介されています。Markdownを変換してHTMLにするようで、スライドの位置座標の指定にも対応しているようです。

さらに、

markdown2impress.plでついでにpdfも出したらええやん

という別のブログでPDFに出力する拡張が紹介されています。

これでやりたいことがすべてできると思って喜んでいたのですが、Reveal.jsを見たらもっとすごかった。

  • Markdownをそのまま読み込める
  • PDFの出力も標準サポート(Chrome only)

それだけでなく、Perl依存なしになるし、使ってみたところimpress.jsよりReveal.jsの方が動作が軽い気がしました。 impress.jsの方が表現力は豊かだと思いますが、もともとの発想がMarkdownで楽したいというものなのでReveal.jsを使うことにしました。

Reveal.jsを使う時はMarkdownを読み込むためのHTMLが必要です。READMEを読めば分かると思いますが、参考までにサンプルを書いておきます。

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>slides</title>
    <link rel="stylesheet" href="css/reveal.min.css">
    <link rel="stylesheet" href="css/theme/default.css" id="theme">
    <script>
        document.write( '<link rel="stylesheet" href="css/print/' + ( window.location.search.match( /print-pdf/gi ) ? 'pdf' : 'paper' ) + '.css" type="text/css" media="print">' );
      </script>
  </head>
  <body>
    <div class="reveal">
      <div class="slides">
        <section data-markdown="content.md" data-separator="^\n\n\n" data-vertical="^\n\n"></section>
      </div>
    </div>
    <script src="lib/js/head.min.js"></script>
    <script src="js/reveal.min.js"></script>
    <script>
      Reveal.initialize({
        dependencies: [
          { src: 'plugin/markdown/showdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
          { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
        ]
      });
    </script>
  </body>
</html>

こんな感じでHTMLファイルを作っておいて、あとはcontent.mdを書けばそのままスライド表示されます。気をつけることは改行3つで区切ることくらいです。べんり~。

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を書くことになりそうですが。

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

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は、JavaScriptでフィルターを書く、RSSアグリゲーターです。

フィルターJavaScriptはブラウザ上で誰でも書くことができます。すなわち、バグ入りのコードや、悪意のあるコードも書くことができます。 そのような不正なコードにどう対処するかがポイントになります。

RSS PipesはNode.jsで動いています。Node.jsにはvmというモジュールがあって、サンドボックス環境を作ることができます。 Node.jsを使ったことがある方は分かると思いますが、モジュールを書いてrequireするとexportsしたもの以外は名前が衝突しません。 これはrequireでモジュールをロードする時にサンドボックス環境で評価しているからです。 RSS Pipesでもこの仕組みを使ってJavaScriptのコードを評価しています。 よって、ブラウザ上から入力されたJavaScriptコードによって、サーバ側のNode.jsにアクセスされることはありません。

ところが、vmモジュールのマニュアルをよく読むと書いてあるように、このモジュールを使うことで安全になるわけではありません。安全にコードを走らせるためにはサブプロセスを使うように、と書いてあります。

どういうことかと言うと、Node.jsはシングルスレッドで動作するため、vmでサンドボックス環境を作ったとしてもそこで使用されるリソース(CPUパワーやメモリ)を制限することはできず、リソースを不用意に使われてしまう可能性があります。簡単な例では、

while(true) {}

というJavaScriptを走らせるとNode.js全体が固まってしまいます。 とは言っても、herokuで動いているRSS Pipesでサブプロセスを使うわけにはいきません。(もしかして、使うこと自体はできるのかも?)

そこで、無限ループを判定するような仕組みをいれています。 先ほどの例では、

while(true) {
  counter++;
}

のようにして、counterが一定値を越えた場合に強制的に止めればよいのです。これで、どの程度の危険が回避できたかは定かではありません。正確にCPUの使用率を測っているわけでもありませんし、メモリについてはノータッチです。それでも、無限ループで固まることは回避できているので、少なくとも悪意のないコードでしたら、問題のない範囲と思っています。

将来的に、vmモジュールのサンドボックス環境でリソース制限までしてくれるようにはならないでしょうかね。ちょっと興味あります。

まだ誰にも使われていないRSS Pipesですが、機能を追加しました。

RSS PipesはRSSのアグリゲーターでフィルターをJavaScriptで編集します。編集はWebブラウザ上で行うので、いわゆるWikiのようなシステムです。

誰でも編集できてしまうので、そのうちSPAM行為をする利用者もでてくるかもしれません。まあ、そうなるくらいになったらうれしいわけですが。

SPAM行為をされてから対策すればよいと思っていたですが、SPAM対策がされていないことで、利用者が躊躇してしまうのは本望ではありません。そこで、簡単なロック機能を作りました。

編集するときにロックコードを入力すると、以降そのロックコードを入力しないと更新できなくなります。もちろん、ロックコードを忘れたら、本人でも更新できなくなります。そういうときは、あきらめて新しいフィルターとして再登録しましょう。「複製」ボタンがあるので、簡単です。

http://dai-shi.github.io/rss-pipes/

RSS Pipesを使ってJavaScriptの勉強をしよう、の第三弾です。

昨日、はてなブックマーク新着エントリからキーワードでフィルタリングするRSS Pipesの例を紹介しましたが、はてなブックマークの場合ははてなが提供するRSSでキーワードフィルタリングできてしまいます。

そこで今日は、キーワードが含まれていた場合タイトルを書き換える方法を例題にします。

フィードを流し読みするときは、タイトルに気になるキーワードが入っていると目に留まりませんか。自然と目でキーワードフィルタリングしているのかもしれません。そこで、それを助けるためにタイトルを強調表示してみましょう。★マークをつけるのはどうでしょうか。

次のようなJavaScriptコードになります。

function rssPipesFilterFunction(articles) {
  var keywords = ["JavaScript", "javascript"];
  var i, len = keywords.length;
  articles.forEach(function(article) {
    if (article.title) {
      for (i = 0; i < len; i++) {
        if (article.title.indexOf(keywords[i]) >= 0) {
          article.title = "★★★ " + article.title;
          break;
        }
      }
    }
  });
  return articles;
}

前回と同じように、indexOfでキーワードを探して、見つかった場合はarticle.titleに文字列を追加しています。

RSS Pipesで動くサンプルはこちらです。

RSS Pipes: hatena/JavaScript関連のブックマーク/タイトル強調表示

もう、ネタがなくなってきたかも。

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

既に存在するアイテムをコピーして新しく登録する「複製」機能が欲しいと思って実装方法を考えていたのですが、思いのほか簡単にできました。

備忘メモしておきます。

$routeProviderのコードは、

$routeProvider.when('/home', {...});
$routeProvider.when('/edit', {...});

のようになっています。/homeに複製ボタンがあり、それを押すと/editに遷移します。つまり、/homeから/editに複製元のデータを渡したいということです。

$rootScopeを使いました。

$rootScope.saved = {};

としておいて、/homeのコントローラで、

$scope.saved.data = data;

として保存し、/editのコントローラで、

$scope.data = $scope.saved.data;

として復元します。場合によりますが、今回は消したかったので、さらに、

delete $scope.saved.data;

としました。

もっとスマートな方法があるのかもしれませんが、今回はこれでよしとします。

RSS Pipesを使ってJavaScriptの勉強をしよう、の第二弾です。これ、連載にできるかしら。

フィードを流し読みするときは、タイトルに気になるキーワードが入っているかが重要だと思います。キーワードでフィルタリングするというのはありがちですね。

次のようなJavaScriptコードになります。

function rssPipesFilterFunction(articles) {
  var keywords = ["JavaScript", "javascript"];
  var i, len = keywords.length;
  var newArticles = [];
  articles.forEach(function(article) {
    if (article.title) {
      for (i = 0; i < len; i++) {
        if (article.title.indexOf(keywords[i]) >= 0) {
          newArticles.push(article);
          break;
        }
      }
    }
  });
  return newArticles;
}

キーワードが一つでも見つかったら、breakでforループを抜けて次に進むところがポイントです。Array.forEachを使うと、iとlenの変数が不要になるのですが、その場合JavaScriptだとループを抜け出せないのです。

今回はindexOfで書いてみましたが、ここを正規表現でマッチさせる方法もあります。今回の例だと、/javascript/iという簡単な正規表現で書けるので、そのほうが一般的かもしれません。正規表現を使う時はループの外側で事前に定義してprecompileするようにしましょう。

article.titleだけではなく、article.descriptionもチェックするとタイトルではなくdescriptionにキーワードが含まれているかもチェックできます。今回は、タイトルだけにとどめました。

実際これをRSS Pipesで適用した例が、下記にあります。

RSS Pipes: hatena/JavaScript関連のブックマーク

この例ははてなブックマークの新着エントリーを使いましたが、はてなブックマークを使うだけならはてなが提供するRSSで対応できましたね。

http://b.hatena.ne.jp/keyword/JavaScript?mode=rss

つづく。かな??

RSS Pipesで広告エントリの除去

  • 投稿日:
  • by

RSSを処理すると言えば、まずは広告エントリの除去でしょうか。 Yahoo! Pipesの利用例でもよくありそうです。

RSS Pipesの場合はJavaScriptでフィルターを書きます。

function rssPipesFilterFunction(articles) {
  var newArticles = [];
  articles.forEach(function(article) {
    if (article.title && article.title.lastIndexOf('AD:', 0) != 0 &&
        article.title.lastIndexOf('PR:', 0) != 0) {
      newArticles.push(article);
    }
  });
  return newArticles;
}

このような感じになります。

AD,PRエントリを除去するフィルター #rsspipes にも同じものを置いています。

今、思ったのですが、これってJavaScriptの勉強に結構使えるのではないでしょうか。比較的短いコードで実用的な機能が作れるので。

実際これをRSS Pipesで適用した例が、下記にあります。

RSS Pipes: news/itmedia&cnetjapan

このコードをコピペするだけで、他のRSSの処理もできるようになります。いかがでしょうか。

JQueryやAngularJSはGoogle Hosted Librariesが配信してくれて、 BootstrapはBootstrapCDNというのもあって 便利です。

しかし、マイナーなライブラリになるとどうしたものかと思っていました。手を入れないライブラリは自分でホスティングする理由もないので、CDNがないかなと思って探したところ、

を見つけました。cdnjsはGitHubのPull Requestsでライブラリの登録を依頼できるというのが面白いです。

しかし、どちらもcssファイルは登録されていない様子なので、結局すべてを外部CDNに頼ることはできないと。探せば他にもありそうですね。

Google Readerがなくなることが第一の理由ではないのですが、RSSってもっと便利にならないかと以前から考えていて、RSS PipesというWebサービスを作りました。

名前が似ているYahoo! Pipesから連想されるとちょっと困るような、困らないような感じですが、いわゆるRSSアグリゲーターです。フィードを表示するUIはなくて、RSSを出力します。 Feedweaver よりは汎用性があって、 Yahoo! Pipes よりは汎用性がないといったところです。

あまり比較することには意味がないですね。 RSS Pipesを作った理由は、

  • ログインせずにRSSを作れるアグリゲーターが欲しかった
  • JavaScriptでフィルターを書きたかった
  • みんなでRSSを作れるようにしたかった
  • 単に作りたかった

からです。まだできたてほやほやで、この段階でどれだけの人にアピールできるか分かりません。もし要望があればお知らせください。

RSS Pipes

上記リンクからどうぞ。

angular.jsはサーバとの通信にJSONを使うことが典型的ですが、別にテキストでもXMLでも大丈夫のようです。今回、XMLを取得してng-repeatで表示するサンプルを作ったので簡単に紹介します。

まず、コントローラ内で次のようにします。

  $scope.jQuery = jQuery;
  $scope.getXmlContent = function() {
    $http.get($scope.xmlUrl).success(function(data) {
      $scope.xmlDoc = jQuery(jQuery.parseXML(data));
    });
  };
  $scope.getXmlContent();

jQueryはロードされているものとします。getXmlContentが$scopeに入っているのは、ng-clickからも呼べるようにするためです。取得先のURLは同一ホストでないとブラウザにはじかれると思います。

次に、html側で次のようにします。

<div ng-show="xmlDoc">
  <ul>
    <li ng-repeat="itemEle in xmlDoc.find('item').get()">
      <span ng-bind="jQuery(item.Ele).find('content').text()" />
    </li>
  </ul>
</div>

ng-repeatのところでget()を使ってリストにすることがポイントです。

あまり利用シーンはないかもしれませんが、参考にどうぞ。

あまり使うことはないかなと思っていたのですが、ちょっと使うことにしました、PostgreSQL。

Rubyにはdata_mapperがありましたが、Node.jsでは何があるんだろうと思って調べてみました。

とりあえず、見つけたのは3つです。

これが現時点でのGitHubでのStar数順です。

でも、JugglingDBを使ってみることにしました。理由は、adapterにRedisやMongoDBなどのNoSQL系が入っていてちょっとわくわくしたからです。もしかして、将来新しいadapterを作れるのかもしれないと思いました。

しかし、このJugglingDBですが、ドキュメントが見つかりません。RailwayJSのサブプロジェクトみたいで、あまり外に目が向いていないのでしょうか。ところで、RailwayJSはcompound.jsに変わったのですね、client-sideで動くと書いてあります。面白そうですね。

さて、READMEを見ながらなんとなくやってみたら動きました。

var Schema = require('jugglingdb').Schema;
var schema = new Schema('sqlite3', { database: 'xxx.db' });
var SomeObject = schema.define('SomeObject', {
  name: {
    type: String
  },
  content: {
    type: String
  },
  flag: {
    type: Boolean
  }
});
schema.autoupdate();
SomeObject.create();

これで合っていますかね。

他の2つとの比較も知りたいですね。探せば誰かがやっているでしょう、きっと。

http://www.sequelizejs.com/はだいぶ詳しく書いてありますね。少し心が揺らぎます。


docsの下に少しドキュメントがありました。しかし、READMEに書いてあるfindOneが載っていなかったりと、網羅されていないようにも見えます。

JavaScriptにはJavaで言うところのString.startsWithがないです。みんなどうやっているかというと、

if (str.indexOf('prefix') === 0) {
    console.log('prefix found');
}

みたいな感じのコードをよく見ます。AngularJSのソースでも見ました。でも、これって良く考えると無駄ですよね。indexOfというのは文字列を最初から最後までなめて、一致する位置を探すのです。見つかれば一発ですが、見つからなければ最後まで行って-1を返します。本来、prefixを判定するのなら最初だけ検査すればいいのです。

str.substring(0, 'prefix'.length) === 'prefix'

という書き方も見ますが、これもいまひとつです。substringは新たに文字列を作ってしまうので。結論としては、

str.lastIndexOf('prefix', 0) === 0

が一番いいと思います。lastIndexOfは文字列の後ろから検査するのですが、検査開始位置を0にしているので、判定するのは一回だけです。Javaの内部実装には詳しくないですが、String.startsWithも同じような実装でしょう。

さて、これが本当かを確かめるために、benchmark.jsでベンチマークを書こうかと思いました。いや、せっかくなのでjsperfで書いてみようと思って、書き始めたら、あれ、既に存在する。世の中同じこと考える人はいますね。

http://jsperf.com/string-startswith/3

最後の一つのテストケースは私が追加したものです。 想定通り、lastIndexOfが速いです。ん?しかし、Chromeだとcompiled regexの方が速い。いや、そんなはずは、、。compiled regexが速いのは事実ですが、lastIndexOfが遅いのです。こんな単純な関数でChromeがFirefoxより遅いのは想定外でした。

Chromeで遅いということは、v8が遅いのかと思って、node.jsでもテストしてみました。

https://gist.github.com/dai-shi/4950506

案の定、node.jsでも同様の結果です。 それでも、私はlastIndexOfを使うことをおすすめします。 v8で遅いのはv8が直るべきです。ところで、これはissueになっているのかな?

http://code.google.com/p/v8/issues/list

どうやらなっていないようです。ちょっとソースを覗いてみますか。v8はsubversionです。bleeding edgeブランチが最新のようです。

http://v8.googlecode.com/svn/branches/bleeding_edge/

checkoutしてStringLastIndexOfのソースコードを追ってみましたが、特に単純な問題があるようには見えません。久しぶりのCのソースで頭が疲れます。そりゃそうですよね、天下のGoogleが作っているものがそんな自明なバグを残しているわけないですよね。というわけで、しばらくは様子をみることにします。

ところで、jsperf.comにstring-startswithがあったのが悔しかったので、真似してstring-endswithを作ってみました。

http://jsperf.com/string-endswith

傾向はstartsWithと同様です。ここまで読んでくれたみなさん、suffixチェックするときに、間違ってもnaive indexOfのような書き方はしないように。

JavaScriptで配列を結合したいと思いました。配列の個数は分からず、任意の場合です。

var x = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [], [10]];

を、

var xx = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

に変換したいということです。 SchemeだとSRFI-1にconcatenateというのがあるのですが、JavaScriptはどうするとよいのかと思って調べました。

結論から言うと、

var xx = [].concat.apply([], x);

がよさそうです。

jsperfにベンチマークがありました。

http://jsperf.com/multi-array-concat/9

Safariでは別の書き方(push.applyをループする)の方がよいケースもあるようですが、コーディングの長さも考えると、上記の書き方がベストでしょう。

ついでに、結合する配列が2個に限定される場合のベンチマークも見つけました。

http://jsperf.com/concatperftest/13

この場合は、Array.concatを使うより、Array.push.applyを使うほうがよさそうです。

AngularJSのangularInitを読みました。

<html ng-app="<mymodule>" class="ng-app:<mymodule>">

と書くことは、

angular.bootstrap(document, ["<mymodule>"]);

とたぶん同等です。 より正確には、readyで呼ばれるので、

angular.element(document).ready(function() {
  angular.bootstrap(document, ["<mymodule>"]);
});

となります。angular.elementはJQueryかJQLiteです。

分かったことは、それだけです。


追記。

よく読んだら、ドキュメントにちゃんと書いてありました。

http://docs.angularjs.org/guide/bootstrap

最近、テスト書きながらでコーディングしていますが、 https://github.com/dai-shi/connect-prerenderer のe2eテストで少し困ってます。

AngularJSのブートストラップは通常、

<html ng-app class="ng-app:<modulename>">

とアノテーションをつけることによって行われるのですが、 ある理由からブートストラップを自分で呼びたいと考えています。 これ自体は比較的簡単で、

angular.bootstrap(document, ["<modulename>"]);

を呼び出せばいいだけです。より正確には、

<script>
  angular.element(document).ready(function() {
    angular.bootstrap(document, ["<modulename>"]);
  });
</script>

といったことろです。

ところが!

このブートストラップの方法だと、testacularが動いてくれないのです。困りました。browser().navigateTo(...)を呼び出すと返ってこないのです。

解決策は今のところ見つかっていません。とりあえず、テストするときは、ng-appをつけておくしかありませんね。

https://github.com/dai-shi/connect-prerendererを開発中に出会った事象について書き留めておきます。

jsdomでAngularJSを使ったテストページにアクセスすることになったのですが、 angularが初期化されなくて困ってました。ぐぐったところ、

AngularJSがIE8で動かないときは

を見つけたのですが、この方法では解決せず。結局、次のようにしたら動きました。

<html ng-app class="ng-app">

id="ng-app"はあってもなくても変わらず。モジュールがある場合は、class="ng-app:<modulename>"でもうまくいくのかもしれません。

angularのソースコードを少し眺めると、

function angularInit(element, bootstrap) {
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
//-----snipped-----
  forEach(names, function(name) {
    names[name] = true;
    append(document.getElementById(name));
    name = name.replace(':', '\\:');
    if (element.querySelectorAll) {
      forEach(element.querySelectorAll('.' + name), append);
      forEach(element.querySelectorAll('.' + name + '\\:'), append);
      forEach(element.querySelectorAll('[' + name + ']'), append);
    }
  });

ってな感じで、どんな風に書いても動きそうです。なぜ、id="ng-app"でうまくいかなかったのか疑問が残りますが、追求しないことにします。


追記。

ng-appだけではダメだった理由は、ng-app="ng-app"に展開されてしまうからのようなので、ng-app="<modulename>"にすればうまくいくのかもしれません。

さらに、追記。

結局、ng-app=""で動いたので、ng-appだけだとjsdomでうまくいかないというのは正しそうです。class="ng-app:<modulename>"もうまくいきそう。id="ng-app"で動かなかった理由はおそらく、ng-appが残っていたからでしょう。

JavaScriptのお話です。node.jsに限らない話だと思いますが、node.jsでの動作を説明します。

一言で言うと、"g"オプションをつけたRegExpのtest()の呼び出しはループ(?)します。

説明するよりも、実際の動きを見てみましょう。

% node
> re = new RegExp('xyz', 'g');
/xyz/g
> s = 'aaaxyzbbbxyz';
'aaaxyzbbbxyz'
> re.test(s)
true
> re.lastIndex
6
> re.test(s)
true
> re.lastIndex
12
> re.test(s)
false
> re.lastIndex
0
> re.test(s)
true
> re.lastIndex
6

という感じで、re.lastIndexがマッチを開始するインデックスを保持しているようです。で、最後まで行ったら初めに戻ると。

"g"オプションをつけなければこんなことにはならず、lastIndexも常に0のままです。


ついでに、RegExp.test()とRegExp.exec()とString.match()とString.search()のベンチマークもしておきました。

一つは、node.js用。
https://gist.github.com/dai-shi/5169296

もう一つは、ブラウザ用。自分で作ろうかと思ったら、既にありました。
http://jsperf.com/regex-test-or-exec-or-string-search-or-match

やはり、正規表現のマッチを確認するだけなら、RegExp.test()が一番よさそうですね。

ちょっと面白い結果だったのはFirefoxのケースで、RegExp.test()とRegExp.exec()の速さがほとんど変わりませんでした。つまり、test()の内部でexec()を呼び出しちゃってる感じです。Chromeとnode.jsでは(どちらもv8だけど)差が出ているので、FirefoxのJavaScriptエンジンは改善の余地があるということでしょう。

前から思っていましたが、Chromeの正規表現の処理は速いですねぇ。Firefoxとは比べものにならないです。