Introduction

ブログ内検索

  • このサイトの記事を検索 by Google

おすすめの一冊!

無料ブログはココログ

« 2011年2月 | トップページ | 2011年7月 »

2011年5月

2011-05-14

微妙な配列リテラル


ちょっと気になる JavaScript ネタを発見。

    Javascriptが難しくてわからないので誰か教えてください
      --  Aduca

これは JavaScript の配列リテラルの仕様から説明ができます。

JavaScript の仕様書(というかベースとなっている ECMAScript の仕様書)を
ちょっと見てみましょう:

    ECMA-262
    ECMAScript Language Specification
    5th Edition / December 2009
    http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf


p.63 の "11.1.4  Array Initialiser" の第2段落にそれらしきことが書いてあります。

ざっくり訳してみます。
配列要素は、要素リストの先頭でも、中間でも、末尾においても、省略されうる。 有意な式がコンマの前に置かれていない場合、つまり「いきなりコンマで始まる」 とか「別のコンマのすぐ後に続けてコンマが置かれた」というケースでは、 省略された要素は配列の長さに含まれ(配列の長さが増加し)、それに続く要素に 割り当てられる添字がそのぶんだけ大きくなる。省略された要素は定義されない (undefined となる)。もし、要素リストの最後の要素が省略された場合、 その要素は配列の長さに含まれない。
つまり、次のようになると考えられます。
> [] ⇒ 最終要素しか無くて、その要素が省略されているので、長さ 0 の空配列。 > [,] ⇒ カンマの前にある先頭要素が省略されているので、その値は undefined で、 最終要素は省略されているので無視。結果として長さ 1 の配列。 > [,,] ⇒ カンマの前にある先頭要素が省略されており、次のカンマも連続しているので、 先頭の2個の値はいずれも undefined である。 最終要素は省略されているので無視。結果として長さ 2 の配列。
処理系の表示によってはまぎらわしいのですが・・・
> [] [] > [,] [] > [,,] []
空の要素に見えても要素数はちゃんと保持されています。
> [].length 0 > [,].length 1 > [,,].length 2
次に、toString() の結果についてですが、 上記仕様書の "15.4.4.2 Array.prototype.toString()" (p.123) を 見てみると、内部的には join を呼んでいるようです。 つまり、[].toString()[].join() と同じ。 仕様書の "15.4.4.5 Array.prototype.join(separator)" (p.125) を 見てみると、次のようなルールがわかります。   ・空の配列には空の文字列が返る   ・1要素の配列ではその要素そのものが返る   ・複数の要素があればカンマで連結したものが返る   ・undefined な要素は空文字列になる。   ・区切り文字 (separator) が省略された場合はコンマが使われる。 というわけで、次のような挙動となります。
> [].toString() ←空の配列なので空文字列 "" > [,].toString() ←長さ1の配列なのでカンマで区切られない "" > [,,].toString() ←長さ2の配列なのでカンマで区切られる ","
これで仕様通りなのです。 結局のところ、undefined な要素しか含まない配列の 簡易表示がイマイチ、という話なのかもしれませんね。
> [,,,,] [] > [,,,1,] [undefined, undefined, undefined, 1] > [,,,1,,,] [undefined, undefined, undefined, 1] > [,,,1,,2,] [undefined, undefined, undefined, 1, undefined, 2]
どうも、配列の末尾の undefined な要素は省略されるお約束っぽい。 まぁ、new Array(50) とか書いたときに "undefined" と 50個も表示されるのも困るので、まぁ、妥当といえば妥当かもですが・・・ p.s. 処理系の実行例は Google Chrome のコンソールのものです。 Firefox (Firebug) では undefined な要素がきちんと全部表示されました。

2011-05-05

iframe 要素の onload ハンドラ


開発中のブックマークレット「PruneBeforeClip」に
AutoPagerize みたいな機能を追加しようとしています。


ニュースサイトの記事で複数のページに分割されているものがあります。
最初のページだけクリップすると結論が書かれていないし、
途中のページだけクリップすると話の流れが把握できないし、
とにかくクリップして保存するには不便な状態になってしまっています。
ロイターには「記事を1ページに表示する」というリンクがあって
とても便利なのですが、他のサイトには残念ながらありません。

ところで、AutoPagerize という有名なユーザスクリプト (GreaseMonkey) があります。
Google の検索結果のページとかでページの末尾にスクロールすると、自動的に
次のページを読み込んで、ページ末尾に「継ぎ足して」くれるモノです。
Seaoak は使っていませんが、かなり便利だという評判。

で、考えてみると、ニュース記事の単一ページ化も、AutoPagerize と
同じ仕組みで実現可能なハズです。違いは、スクロールに関係なく
すべてのページを一気に展開してしまうところだけ。


とりあえず、実験ということで、あるニュースサイトで記事の単一ページ化が
できるようにしてみました。

処理はそんなに複雑ではなくて、次のような流れ:

  ・ページ内に「次のページ」や「前のページ」へのリンクがないかチェック。
  ・あったら、リンク先のページをバックグラウンドで読み込む。
  ・読み込んだページの本文部分を抜き出して、元のページのリンクの代わりに埋め込む。
  ・以上の処理をリンクがなくなるまで繰り返す。

「本文部分の抜き出し」はすでに PruneBeforeClip の基本機能として実装しているので、
問題になるのはリンク先のページの動的な読み込みです。

最初は XMLHttpRequest を使おうかと思ったのですが、読み込みたいのはテキストデータとか
XML ドキュメントではなく、ふつーの HTML ドキュメントなので、iframe を使うことにしました。

  ・iframe 要素を動的に生成して、元ページの DOM ツリーに追加する。
  ・iframe 要素の src にはリンク先の URL を指定する。
  ・iframe 要素の onload イベントハンドラで処理を行う。
  ・読み込んだドキュメントは iframe.contentDocument で参照できる
   (これをふつうの window.document と同じように扱えば OK)。

というわけで、出来たコードが以下のモノ:
function loadUrl(url, onload, onerror) { var iframe = document.createElement('iframe'); iframe.style.visible = 'hidden'; iframe.setAttribute('height', '0'); iframe.setAttribute('width', '0'); iframe.setAttribute('src', url); if (onload) { iframe.onload = function(event) { try { onload(iframe); } catch(e) { if (isDebug) alert('FATAL: ' + e); } }; } if (onerror) { iframe.onerror = function(event) { try { onerror(iframe); } catch(e) { if (isDebug) alert('FATAL: ' + e); } }; } document.body.appendChild(iframe); } (function() { var ctrlDom = document.getElementsByClassName('ctrl')[0]; var url = ctrlDom.getElementsByTagName('a')[0].href; loadUrl(url, function(iframe) { var target = iframe.contentDocument.getElementById('tmplBody'); if (! target) throw 'unexpected content'; removeGarbage(target); arrayEach(function(elem) { ctrlDom.parentNode.insertBefore(elem.parentNode.removeChild(elem), ctrlDom); }, toArray(target.childNodes)); iframe.parentNode.removeChild(iframe); removeAll(ctrlDom); myYield(_self); // もう一度最初から処理をやる }, function(iframe) { throw 'can not load next page: ' + url; }); })();
実際のコードは、もっとゴチャゴチャしてます。 先頭ページや最終ページに到達したときは処理を変えないと行けないし、 できれば「次ページへ」みたいなリンクは削除しておきたいわけです。 ちなみに、IE のどれかのバージョンだと、iframe の onload ハンドラがうまく呼ばれない ことがあるみたいです。そのときは onreadstatechange ハンドラを使えばよいらしい。 まぁ、今回は IE には対応できないと思うので、無視ですが・・・

CSS ルールの動的な追加


スタイルシート (CSS) のルールを動的に追加したい、と思って、
やり方を調べてみました。


ユーザスクリプト (GreaseMonkey) で DOM を操作して動的に div 要素とかを
作ったりしているのですが、見た目を変えるために各要素の style プロパティを
ひとつひとつ指定していると、だんだん面倒になってきます。

特に、ユーザの操作によって見た目の装飾を ON/OFF したい場合、
背景色はちゃんと切り替わるけど文字の色を変え忘れたりとかして、
ちょっと悲しくなったり・・・

できればクラス名をひとつ付け外しするだけで済ませられないかなぁ、
と思ったわけです。


で、Mozilla のリファレンスを眺めていたら、まさにやりたいことが書いてありました。
stylesheet.insertRule -- Gecko DOM Reference
具体的には、以下のようなコードでできます。
function addStyle(cssText) { var elem = document.createElement('style'); document.getElementsByTagName('head')[0].appendChild(elem); document.styleSheets[document.styleSheets.length-1].insertRule(cssText); } addStyle('.grayOut { opacity: 0.5; }');
ちなみに、Mozilla (MDC Docs) のリファレンスはかなり使えます。 ただし、日本語版サイトは多くのリンクが死んでるので、 英語版サイトがオススメです。   ・HTML element reference   ・CSS Reference   ・Gecko DOM Reference   ・JavaScript Reference ご参考まで。

script 要素の charset 属性


ちょっとしたブックマークレット (bookmarklet) を作ってます。

  ⇒ PruneBeforeClip

Evernote の web clipper で「本文だけをクリップ」するために
ページを整形してくれる、というだけのモノですが、なかなか便利。


で、まだ対応サイトが少ないので、毎日ちょっとずつコードを追加しているのですが、
今日になって突然、Firefox で動かなくなってしまいました。

このブックマークレットでは、外部 JavaScript ファイルを読み込む script 要素を
ページに埋め込んでいます。実際の処理(ページの加工)は、この外部 JavaScript
ファイルに書かれています。Firebug で調べてみると、script 要素の埋め込みは
できているみたいですが、JavaScript ファイルが実行されなくなってしまったみたい。

Google Chrome では動くのでしばらく悩んだのですが、「もしや」と思って
コメントを全部削除してみたら、Firefox でも動きました。


犯人は文字コード。


JavaScript ファイルの文字コードは UTF-8N にしていたのですが、
加工したいページの HTML ファイルは Shift-JIS になっていて、
Firefox は Shift-JIS だと思って JavaScript ファイルを処理して、
たまたま不正なコードを検出してお亡くなりになっていたようです。
昨日まで動いていたのは、たまたま不正なコードとなりうる漢字を
使っていなかっただけみたいです。運が良かったのか悪かったのか。

さっそくググってみると、script 要素には charset 属性が指定できる、
ということがわかりました。
文字コードの違う外部JavaScriptファイルを読み込む -- cafe-lab:lablog
ブックマークレットで生成する script 要素にも charset 属性を 指定するようにしたところ、Firefox でも動くようになりました。
javascript:(function(){ var elem = document.createElement('script'); elem.setAttribute('type', 'text/javascript'); elem.setAttribute('charset', 'UTF-8'); elem.setAttribute('src', 'http://prunebeforeclip.seaoak.jp/bookmarklet_01_00_00_00.js'); document.body.appendChild(elem); })();
カンペキです。 しかし、文字コードはホントにやっかいですね・・・

« 2011年2月 | トップページ | 2011年7月 »