Hatena::ブログ(Diary)

三等兵

2013-07-03

Amazon Popup Reviewの不具合を修正しました

たろきち 2013/05/28 00:12

いつも便利に使わせていただいており、ありがとうございます。

amazonの仕様変更のためか、最近読み込みがずっと続いたまま、レビューが出てきません。

申し訳ありませんが、ご確認お願いできないでしょうか?

http://d.hatena.ne.jp/sandai/20110216/p1

amazon側でhtmlが変更されていたので、その関係でうまく動作していなかったみたいなのでその部分を修正しました。ついでに時々popupのリンクが出てこない不具合も直したので使いやすくなったと思います。1ヶ月も放置してしまい申し訳ありませんでした。

2012-03-05

遠い知り合いが「はてダで一日44000文字以上は文字が消える」といっていたので

最大入力可能な文字数は、ブログの本文とコメント欄を合わせて1日分につき約6万5千文字までとなっております。

こちらを超えた場合、それ以後のテキストが削除された状態でブログが更新されたり、文字化けが発生することがございますのでお気を付けください。

http://www.hatena.ne.jp/faq/q?c=9

だそうです。44000ぐらいじゃなかったのか。もっとあったのかあれは。すさまじい。はてダで44000文字以上だと警告しますよと文字カウントしますよスクリプト。改行すっぱぬいただけ。

(function() {
var txtarea = document.getElementById("textarea-edit"),
    tc = document.getElementById("tabchange"),
    scoreelem = document.createElement("span"),
    alertelem = document.createElement("p");
    alertelem.style.color = "red";
    alertelem.appendChild(document.createTextNode("きえちゃううわーん!"));

    tc.insertBefore(scoreelem, tc.firstChild);
    txtarea.addEventListener('keyup', function() {
        var score = txtarea.value.replace(/\r\n/g, "").replace(/(\n|\r)/g, "").length,
            same = alertelem.isSameNode(document.getElementsByClassName("curve-middle")[3].lastChild);
        scoreelem.innerHTML = score;

        if(score >= 44000 && !same) {
            document.getElementsByClassName("curve-middle")[3].appendChild(alertelem);
        } else if(score < 44000 && same) {
            document.getElementsByClassName("curve-middle")[3].removeChild(alertelem);
        }
    }, false);
})();

2011-08-24

知ってて当然?初級者のためのJavaScriptで使う即時関数(function(){...})()の全て

(function(){...})()は、

(function($){
  $.hoge = function() { };
})(jQuery)

みたいに使われていたりするコード。GreasemonkeyとかjQueryのプラグインとか、あれこれ見かけることがあると思います。

この話題はいくつかWebでも取り上げられていますが何がどうなってんのかちょっと難しいですね。しかし、誰でも理解できるレベルではあります。というのも、こういう種の難しさは体系的な知識が備わっているか否かということなのです。


でも、この知識を体系化する作業って結構しんどくて、難しくて、まーハゲるほど悩むこともあるかもしれない。それはきっと、とても毛根に悪いかもしれない。スカルプDも真っ青の状況になるかもしれない。それは、悲しいことなのだと思う・・・っ!

毛根にはこれからもがんばってほしい!いつだって頭を温かいまなざしで見守ってて欲しい!そうだろ!だって、だって毛根だもん!毛根が頭から離れたら何になるのさ!井手らっきょになるのさ!?

いやだいやだ、ニコラス・ケイジやブルース・ウィリスならまだしも井出らっきょだけはいやだいやだ。やめておくれよハゲの神様。井手らっきょだけは、井手らっきょだけは・・・・・・ちょっといいかも(ポッ


そんな切ない即時関数の話。じゃなくて。普通の即時関数とそれに関係する言語仕様の話。


(function(){...})()の名前

その前に名前の話。

今まで(function(){...})()というコードには名前が定まっていませんでした。(個人で好きなように表現していた→”即実行関数”や”使い捨て関数”や"自己実行関数"などいろいろ)

個人的には(function(){...})()に対して名前を統一すべきだろうなあと思っていたところ、JavaScriptパターン ―優れたアプリケーションのための作法 で「即時関数」と名付けされているようです。

権威のある書籍に記述されているのであれば認知されやすいし、言葉としても分かりやすいのでこれからは即時関数という名が広まると良いですね。共通のイメージが持てれば会話にも出し易いし、検索もできますから。

タイトルや冒頭で既に使っていますが、以降から(function(){})()のことを即時関数とします。


※文中の用語について補足していただきました。ありがとうございます。
JavaScriptの関数(ラムダ関数とかクロージャーとか) - hogehoge @teramako


即時関数(function(){...})()ができること

即時関数は関数定義と関数呼び出しをまとめて行うことができるコード。

それを理解するためにまず普通の関数をおさらい。

function hoge() {
  alert(1);
}

これが関数定義。(正確には関数定義の関数宣言にあたる)


当然ですが関数は呼び出さないと何もおきません。呼び出すとは言い方を変えると「実行する」ってことです。じゃあ実行します。えいや。

function hoge() {
  alert(1);
}

hoge(); // 1

これが関数の呼び出し。定義した関数名の後ろに関数呼び出しの( )をつけます。さもありなん、みなまで言うなとパンチの構えはやめていただきたい。真剣なのです。

真剣に井手らっきょの進退についうぼげはぁあああ三○))゜д゜((○三

・・・えーfunction hoge( ) {...}が関数定義、hoge( )が関数の呼び出しにあたることを意識したうえで次。


関数は基本的に以上の段取りで利用するのですが、即時関数だとこれらをまとめて行なうことができます。

function hoge() {
  alert(1);
}

hoge(); // 1

// 上記の関数定義と関数呼び出しをまとめて書くと↓

(function hoge() {
  alert(1);
})();

見た目がなんかカッコの化けものになっちゃいましたが、それについては後述。

そういうわけで、即時関数とは、

  • 関数定義
  • 関数呼び出し

の2つをまとめて行なうことができるものです。


それで、このように1回実行するだけのケースだと、

(function() { // ←hogeっていう関数名は無くてもいい
  alert('教頭の頭ははっちゃけている');
})();

hogeという関数名をのぞいて無名関数(匿名関数とも言われている)で呼び出すのが一般的。多くの人が見たことあるのはこちらの無名関数でしょう。

再帰的に扱うケースやデバッグといった目的があれば名前をつけたりしますが、基本的に1回ぽっきりの野郎なんざぁ名前なんてどうでもいんだよふははは!ということで無名です。

「JSerの人たちは冷酷だよ。いつだって私たちを使い捨てなのさ。一回ポッキリで女ったらしあるいは男ったらしなんだよJserは。あううう。」

という嘆きを即時関数は利用されるたびに叫んでいることを我々は常に心にとどめておくことが大切です。どうでもいいです。


ともかく、このように即時関数は関数定義と関数の呼び出しをまとめて書ける非常に使い勝手の良いコード。

カッコのばけものな即時関数も関数定義と関数の呼び出しを意識してかんがえると、そりゃもう普通のことやってんじゃんあたりまえじゃんと思えます。

うんうん、でも、そもそも即時関数を使う必要があるんでしょうか。


定義と呼び出しがまとめて行えたらどういった効果(良いこと)があるのか?

即時関数は定義と呼び出しをまとめてできるってことはわかりました。

しかしながら、なんでそんなことする必要があるの?って疑問がわいてきます。先ほどの例でいえば、

function hoge() {
  alert(1);
}

hoge(); // 1

この形でも良いじゃないかと。

「確かに即時関数だとまとめて書けて良いかもしれない。でも別に定義と呼び出しを分けてやってもいいじゃないか!普通に書けばいいんだ!わざわざ即時関数にして私ってできるだろ?すごいだろ?結婚する?的なアピールやめろよ!あと結婚するよ!パンパカパーン!」

と思ったかもしれない。

即時関数男さんと結婚出来美さん、ちょっと即時関数男さんは生意気で高飛車なところもあるけれど、なにをやるにしても早いのが素敵なの。というかもう後がないの結婚しましょ!印鑑!印鑑!結婚届けどこおおおおおおお!

という感じですか?

まあともかく。いやこれはごもっともな話で、ではなぜ即時関数を使うのかと言えば、「グローバル変数の使用を抑えることができるから」という理由ですね。

って最初にこの理由を聞いて「ほうほう!そういうことかほうほう!」と納得できれば世の中みんな幸せなのですが、「グローバル変数の使用を抑えることができる」って抽象的な言葉でなんのこっちゃよくわからん。

そこで具体的にどう抑えられるかってのが次。理由は大きく2つあります。

1. グローバル変数に関数の割り当てがなされない

1つ目の理由は、即時関数だとグローバル変数に関数の割り当てがなされないことです。具体的な意味は、まず必要とする前提知識の確認のコード。

var hoge = 'ほげー';

alert(hoge === window.hoge); // true

なぜtrueになるか理解できれば大丈夫。グローバル変数は実際にはグローバルオブジェクトのプロパティです。

ブラウザの場合グローバルオブジェクトはWindowオブジェクトですね。Windowオブジェクトはwindowって自己参照のプロパティがあるんでwindowという小文字を使います。

なので、hoge === window.hogeはtrueということに。

ここでWindowオブジェクトやプロパティといった単語が理解できないと辛い。適当な入門書であれば言及しているはずなので確認してみてください。

そいで、これは関数でも一緒です。

function hoge() { alert('ほげー'); }

alert(hoge === window.hoge); // true

先ほどの普通の変数の例と一緒。

変数だけではなく関数も変数と同じようにグローバル変数に割り当てられています。つまり、グローバルの空間で関数定義すれば関数もWindowオブジェクトのプロパティになります。

関数ってどこか特別な感じがして、変数と区別して考えてしまうかもしれません。しかし、JSではどちらも同じように管理します。varやfunctionで定義しても、グローバルで宣言された変数や関数はWindowオブジェクトに属します。

変数だからとか関数だからって別々に区別しません。なので、

function hoge() { alert('ほげー'); }
alert(hoge); // function hoge() { alert('ほげー'); }

var hoge = 'ふげー';
alert(hoge); // ふげー

window.hoge = 'へげー';
alert(hoge) // へげー

代入するごとに値が変わっているのが分かります。この結果が成り立つのは当然ですね。

ここまでを踏まえて本題。


即時関数を使えば「グローバル変数に関数の割り当てがなされない」とは、今までを理解していれば次のコードですぐわかります。

(function hoge() { alert('ほげー'); })();

alert(hoge) // ReferenceError: hoge is not defined

ReferenceError: hoge is not definedは、「参照エラー: hogeは定義されていません」という意味。つまり、「hogeという関数はどっこにもありませんなー」と言われています。

関数にhogeって名前が付いているので「この関数もhogeというグローバル変数に割り当てられてるのかなー、window.hogeみたいになっているのかなー、とりあえず結婚しようかなー」と思ったら大間違い。しくしく。

即時関数は定義と呼び出しをまとめて行っています。だから、グローバル変数に設定する前に関数に定義された処理は役目を終えて消えました。なのでどっこにもねーよボケとエラーで怒られたんです。ぐすん。(関数宣言ではなく式として評価されたので割り当てもクソもないのです)

せっかくわざわざ名前つけてやってんのに意味がない。こんな放蕩薄情野郎に名前を付けたってしょうがないじゃん。

ってなことで、

/* 名前つけずに無名関数にする */
(function() { alert('ほげー'); })();

よく見る無名の形になる。

小見出しの「グローバル変数に関数の割り当てがなされない」という意味は、つまり、即時関数であればグローバル変数に割り当てられることなく関数が呼びだすことができるということです。

これが即時関数がグローバル変数の使用を抑えることができる1つ目の理由。


さて、これ以外にもう1つ抑えることができる理由がありまして。即時関数はそもそも関数なのですからスコープが提供されます。

2. 即時"関数"だから関数内部の変数はローカルに限定される

2つ目の理由は、即時関数を使えば内部で宣言した変数や関数はローカルに限定される、というものです。

これは前提知識としてJSのスコープを理解していなければなりません。すでにグローバル変数など単語を出しているのにいまさらですが。


スコープとは変数(関数も含む)の有効範囲です。有効範囲と言うとむつかしいイメージがしますが、変数や関数を参照できる場所(利用できる場所)を限定する仕組みのことですね。(プログラミングでは「変数の参照」とか「変数へのアクセス」とかって表現することがありますが、要は変数を使うってことです)

そしてJSのスコープは基本的にグローバルスコープとローカルスコープという2つのスコープに分けられ、ローカルスコープは関数で提供されています。


専門用語ばっかりじゃあれなんで、とりあえずスコープを簡単にレッツ体験。

function hoge() {
  var local = 'ローカル変数';
}

alert(local); // ReferenceError: local is not defined

関数の中で宣言したlocalという変数を、関数の外でalertしたらどっこにもないよーってエラーになりました。これは関数の中のlocalというローカルスコープを持つ変数が関数内に閉じ込められているためです。

このように関数内部で宣言された変数は関数の外部からは参照できません。(プログラミングではこのことを「変数がみえない」と言うことがある)

もちろん関数同士だって外部にあたりますので、

function hoge() {
  var local = 'ローカル変数';
}

function fuga() {
  alert(local); // ReferenceError: local is not defined
}

fuga();

localという名前の変数が無いじゃねえかゴルァーってエラーになります。なんで、関数内部で宣言された変数の有効範囲は、

function hoge() {
  var local = 'ローカル変数';
  alert(local) // ローカル変数
}

hoge();

その関数と同じ空間だけってことね。


関数外部の変数はグローバルスコープを持ち、その変数を総称してグローバル変数と言います。関数内部の変数はローカルスコープを持ち、総称してローカル変数と言います。

このようにJSで扱う変数は基本的に2つのスコープのうち、どちらかに属することとなります。


ここまでで専門用語に追いついていけなかった用にまとめておきますと、

<script type="text/javascript">
/* 関数の外で宣言された変数はグローバルスコープを持つ */
/* それらの変数を総称してグローバル変数と言う */
var global = 'だからこれはglobalという名前のグローバル変数だね';

function hoge() {
  /* 関数の内で宣言された変数はその関数のローカルスコープを持つ */
  /* 関数内部で宣言された変数を総称してローカル変数と言う */
  var local = 'だからこれはhoge関数のlocalという名前のローカル変数だぬぇい';
}
</script>

scriptタグがあると分かりやすいかな。(面倒なんでこれ以外はscriptタグを省略してます)


そしてグローバル変数はプログラム全体から参照できますが、ローカル変数はその関数内部からしか参照できません。

グローバル変数の「プログラム全体」とは、

var global = 'グローバル変数';

function hoge() {
  alert(global) // グローバル変数
}

hoge();

関数内部からでも呼び出せたりすることです。(ローカル変数の結果と混同しないように注意)

こんな感じで、ローカルだのなんだの関係なくどこからでも見えますよー参照できますよーってのがグローバル変数。


それに対してローカル変数は、もう分かりきっていることですが、

function hoge() {
  var local = 'hogeのローカル変数';
  alert(local) // hogeのローカル変数
}

/* 関数内部でalertすることはできるけど */
hoge();

/* 外部からだとどこにもないよーって怒られました */
alert(local) // ReferenceError: local is not defined

ローカル変数の有効範囲はその関数の内部だけです。

ここまでを前提に次から本題。


というかもう答えは最初から出てます。

関数内部で正しく宣言されたローカル変数は外部に漏れることがありません。それらはグローバル変数にはならないのです。

最初の例と同じコードですが、

function hoge() {
  var local = 'ローカル変数';
}

alert(local); // ReferenceError: local is not defined

関数外部からローカル変数は見えません。これは即時関数の形にしても同じ。

(function() {
  var local = 'ローカル変数';
})();

alert(local); // ReferenceError: local is not defined

カッコがついて別のものに見えるかもしれませんが、ただの関数ですから当然です。内部で宣言されたローカル変数は外部から参照できません。

もちろん内部で定義された関数も同じです。

(function() {
  function local() {
    alert('ローカル関数');
  }
})();

alert(local); // ReferenceError: local is not defined

関数も外部からは見えません。(こういった関数の中の関数はローカル関数とか内部関数と称されています)

このように変数も関数も閉じ込められます。

そういうわけで、小見出しの「即時"関数"だから関数内部の変数はローカルに限定される」の意味は、つまり、関数内部で正しく宣言された変数や関数はローカルに限定されグローバルに影響を与えることがなく、関数内部に閉じ込められるということです。

関数内部で完結するので、グローバル変数を1つも作らずに処理を行なうことができちゃうわけですね。わーいわーい。

これが2つ目のグローバル変数の使用を抑えることができる理由でした。


なお、この項目ではスコープの説明は不十分です。いろいろ省きましたが、関数内部でのvar無しの動作や重要なスコープチェーンとそれの発展としてクロージャというものがあったりします。

大切なことなので知らない人は入門書やWebで検索したりして学習してください。

スコープという概念は簡単なんだけど、それを言葉で表現するとなるとむつかしいです。早い話がコード書けば自然に身につくものなので、理屈で攻めても理解できないならコードを書くしかありません。


即時関数がグローバル変数の使用を抑えることができる理由まとめ

長いこと続いたので、以上の2つの理由をまとめると、

  • 即時関数を使えばその関数はグローバル変数に割り当てられない
  • 関数内部で宣言した変数やら関数はローカルに限定されグローバルに漏れない

というわけで、一切グローバル変数を利用することなくコード書けて便利!最高!結婚しよう!イエー!マリッジブルゥー!!!ってことでした。

まとめると簡単に言えちゃうことだったりしますが。理解しているからこそ簡単と言えるのであります。たぶんそんな落とし所です。

グローバル変数の使用を抑える意味

補足として。なんでどいつもこいつもグローバル変数を使わないことにこだわっているのかというと。


それは変数の名前が衝突すると厄介だからなのです。たとえば、404 Blog Not Found:javascript - ブログパーツ/ウィジェット開発者におねがいが良い例です。ちょっこしリンク先を見てください。

こちらでも簡単に流れを記述しておきましょう。js部分。

<script type="text/javascript" src="http://blogchart.jp/js/blogparts.js"></script>
<script type="text/javascript">

</script>

最初のscriptタグはhttp://blogchart.jp/js/blogparts.jsのjsファイルを読み込んでます。読み込んだコードは下記の通り。

function viewBlogparts(){
  var rs = Math.round(Math.random()*2147483647);
  var link = "http://blogchart.jp/blog?url="+blogurl;
  var imgsrc = "http://blogchart.jp/blogparts?type="+partstype+"&id="+id+"&referer="+document.referrer+"&rs="+rs;
  document.write("<a href='");
  document.write(link);
  document.write("' target='_blank' >");
  document.write("<img src='");
  document.write(imgsrc);
  document.write("' border=0 width=163 height=53 />");
  document.write("</a>");
}

この時点でviewBlogpartsというグローバルな関数が1つ割り当てられます。次にscriptタグの間にコードを書いている部分がありますね。

id="22";
blogurl="http://blog.livedoor.jp/dankogai/";
partstype="b";
viewBlogparts();

idとblogurlとpartstypeという変数は全てグローバル変数で値が代入されています。ブログパーツの設定を行なう値ですね。

設定値はブログパーツの外見やどのブログURLを対象とするのかといった設定でしょう。そしてこれらの変数はviewBlogpartsの内部で参照されていることが分かります。var linkとかvar imgsrcとかのとこです。

グローバル変数はプログラム全体から参照することができます。なのでviewBlogpartsという関数の内部からグローバル変数を参照することが可能です。


プログラムがどう動作しているのかまとめると、

  1. グローバル変数に設定値を代入
  2. viewBlogpartsの呼び出すわけですが、その時にグローバル変数を参照してlinkとimgsrcの値を生成
  3. 最後にdocument.writeでブログパーツを挿入している

という流れ。

これはやっちゃいけないですね。どこがいけないかというと、ブログパーツの設定値をグローバル変数で設定している部分です。


例えば他に読み込むjsファイルのプログラムでもグローバル変数を使っていて、その名前がidだのblogurlだのとviewBlogpartsで利用する変数とかぶっていたら値が書き換わってしまいますね。

すると予期せぬ動作を引き起こすかもしれません。というか、現に404 Blog Not Found:javascript - ブログパーツ/ウィジェット開発者におねがいの画像のような結果を招いています。

あとグローバル変数ばかり使ってると可読性がスパゲティ(コードがめちゃくちゃな感じ)になるから使うのやめようって意図もありしますが、それはまた別の話。


ちなみに、今回の問題の解決策は設定値は引数で渡すようにしたらどうかと提案してくれています。これだと、

function viewBlogparts(obj){
  var id = obj.id || 1,
        blogurl = obj.blogurl || 'http://blogchart.jp',
        partstype ~ obj.partstype || 'a';

  var rs = Math.round(Math.random()*2147483647);
  var link = "http://blogchart.jp/blog?url="+blogurl;
  var imgsrc = "http://blogchart.jp/blogparts?type="+partstype+"&id="+id+"&referer="+document.referrer+"&rs="+rs;
  document.write("<a href='");
  document.write(link);
  document.write("' target='_blank' >");
  document.write("<img src='");
  document.write(imgsrc);
  document.write("' border=0 width=163 height=53 />");
  document.write("</a>");
}

/* 使うとき */
viewBlogparts({'id': 22, 'blogurl': 'http://blog.livedoor.jp/dankogai/', 'partstype: 'b'})

オーソドックスな形ですがこうすれば引数で渡せます。これでありきたりな変数名のグローバル変数を使う必要がなくなりました。(が、こういうのはiframeでやっちゃうべきですね。たぶんね)

この事例では即時関数は必要ありませんでしたが、使って対処する方法もありますし、オブジェクト1つ用意してなんやかんやすることもありますし、手段はいろいろあります。

なんでもいいので一番しっくりくる方法がいいと思います。


なぜ即時関数はカッコ厨なのか

ここまでは、即時関数は「どういったことができるのか」ということと、「どういった効果があるのか」についてかんがえてきました。

これらの答えは、

  • 関数定義と関数の呼び出しをまとめて行なうことができる

ということができて、

  • グローバル変数の使用を抑えることができる

といった効果がある、ってことでした。


これだけ理解できれば扱う分には問題ないでしょう。

でも、「即時関数はなぜカッコ厨なのか!」という疑問が魂の叫びとしてふつふつと湧いてきてますね。きてますね!

つまり、(function(){...})()の形で即実行する理屈がわからん!とか、functionの前に" ( "があったり後ろで" )( ) "があったりして何なの!?結婚するの!?とか。

見た目で惑わされていないでしょうか。いや、惑わされているよきっと。あなたは、惑わされている!

なぜならば、この蠱惑的な姿形をもった即時関数はJS界におけるレディー・ガガ。最初に出会ったときから惑わされる(理由もなくどん引きする)様は同じなのです。

それでも、この世は実に素晴らしく、誰もが即時関数の惑わしから解き放れていないわけではありません。俗物から解脱した聖人もいらっしゃるのでございます。なのでWebで検索すればたいていどなたかがなんでカッコ厨か言及しております。

聖人バンザイ、ブッタバンザイでございます。般若心経を唱えたい一心を抑えて、聖人様方の言い分をまとめますと、

  • 関数を式として評価させるため

だからカッコ厨なのです。

これが解脱への第一歩ですね!さあ、解脱したらブッタの髪のボツボツをほどく作業に戻るんだ!バリカンで一度に剃ろうとすると固すぎて刃が折れちゃうからね!そのためにも関数が式だかなんだかをさっさと学習しなくっちゃね!

ところで式ってなんでしょう?


関数には、というかJSには式(Expression)と文(Statement)が存在する

関数というかプログラミング言語には(他の言語はいざしらす少なくともJSには)式と、それから文というものが存在します。

文といえばただの文章を思い浮かべたり、式というと数学で使う数式が思い浮かぶかもしれませんが、それだけのイメージだとだめです。具体的にどうだめなのかというと私と一緒です。


・・・ね、だめでしょ(´;ω;`)

少し話が膨らんで長くなりますが大事な概念なので式と文についてみていきましょう。

式(Expression)と文(Statement)って何だろう?

プログラムを構成する要素は式と文に分類できまして。JSにおける式と文は次の通り。

リンク先は意味わからんかも。最初のうちはわからないものです。慣れてくるとね、うん、やっぱりわからなかったりします/(^o^)\

もうちょっと具体的にわかるよう例を交えて示します。

式とは何だろう

式とは何かと言われて何を思い浮かべるでしょうか。

もしかしたら、

100 + 200 = 300

みたいな式を思い浮かべたかもしれません。これはいわゆる数式の類ですね。

このように「式=数式」といったようなイメージしか持っていなかった人。

大丈夫。そんな人たくさんいる。私とか、私とか、私とか、あと私とか私とか。私が10人くらいいるから。ある意味やばいけど安心!マイノリティじゃないよ!!大丈夫!NARUTO読んで勉強したし、スラムダンクでフンフンディフェンスのコツ考えてたし!


そいでJSにおける式とは、上記のような数式も含めて、

  • 値を持つもの

のことを意味します。


具体的に例を挙げると、

'あいうえお' // 文字列
100            // 数値
true            // 真偽値
null
[1,2,3]
{a:1, b:2}
count         // 変数の参照

これらは式です。

「え?そ、それ式?まじで?ま、まじで式なの?「100」ってあるだけで式になるの?え?数値だけで式?文字も式?ええ?それになに?変数名がぽつんとあるだけでもし、しぃきなのおおおおおおお!?えええええ!?とりあえず結婚するううううう!?」

って発狂してもおかしくない。と、経験者は語る。(はふー)

数式だけを式として認識しているとあかんのです。


それから、値を変数に代入するのも式です。

x = 100
y = 'あいうえお'

= みたいな記号があるから式だろうとまだ思えるかな。このように代入は式として扱います。(後述しますが、varをつけたりセミコロンつけたら文になります)


また、

100 + 100
'あいうえお' + 'かきくけこ'
50 < 100
'あいうえお' === 'あいうえお'

これらも式です。なんとなく式と言われたらそうだろうと思えますが。演算子あると式っぽい感じします。


「100」や「'あいうえお'」のように = や + や - などの演算子が無いただの数値や文字列も、「x = 100」や「100 + 100」といったものも全て「値をもっている」ので式です。

なんでこういったものが式なのかというと、そのように定義されているからです。それ以外に言葉がないですね・・・。うーん。たぶんこの概念はプログラミングだけじゃないと思うのですが・・・。

やっぱりわかりにくいですね。そういうわけでわかりやすいように考え方を変えましょう。「式とは値を持つもの」と言いましたが、言い換えると「式とは値を返すもの」とも言えます。

でも値を返すって具体的にどういうことなのでしょうか。これはChromeのコンソールやFirefoxのWebコンソールで試すとわかりやすい。


たとえば、100と入力して実行すると、

f:id:sandai:20110817162152p:image

といったように100と「返ってくる」でしょう。( > xxx の部分が入力した文字で、その下が返ってきた値)


他には、

f:id:sandai:20110817162153p:image

文字列の値が返ってきました。


見た目が式っぽいやつも、

f:id:sandai:20110817162154p:image

それぞれ値が返ってきています。


x = 100 も式だと例に挙げていましたが、

f:id:sandai:20110817162155p:image

こうして値を実際に返すからです。ちょっとよくわからないかもしれませんが、式全体は値を持っているのです。


そして、

f:id:sandai:20110817162156p:image

といったように x = y = 100みたいな記述が可能なのは代入が式だからですね。


このように式とは数値、文字列、オブジェクト、変数などそれぞれ1つ1つを表し、

100

さらに、それらは演算子によって代入や加算や比較などさらに「複雑な式」(これも値を持つもの、返すものの意味での式です)を構成し、

x = 100 + 300

また、これらは値を返すという特徴を持っていて、

f:id:sandai:20110818200924p:image

式とは値が返すもの、あるいは、値を持つものとまとめて言うことができます。


こういうわけですが、やっぱり難しいかもしれません。最初はこうして式とは値を持つものといわれてもなんのこっちゃ分からんと頭をかかえるものです。

そのような場合は、

x = 変数に格納できるやつだいたい式っす 

と思っていれば簡単。あるいは、

/* xに入れることができるのだいたい式っす */
if(x) {}

でも。ifの方が正確かな。式というのは結局値です。変数に格納したり条件式に使えますからね。

文とは何だろう

分類すると大きく4つに分けられます。(わかりやすいよう非公式に分けてるだけ)

  • 1. 式の後ろにセミコロンがついたら文
  • 2. 空文
  • 3. varをつけた代入文
  • 4. ブロック文, if...else 文, switch 文, for 文, do...while 文, while 文, label 文, break 文, continue 文, for...in 文, for each...in 文, with 文, コメント, throw 文, try...catch 文など

覚えなくてもいいので式との違いをつかんでください。

1. 式の後ろにセミコロンがついたら文

式のケツにセミコロンがつくと文となります。

/* 式 */
x = '式'
100
'あいうえお'
100 > 50

/* 文 */
x = '文';
100;
'あいうえお';
100 > 50;

セミコロンが無ければ式ですが、あれば文となります。なんか変な話ですけどそういうものなのと決められているので、そういうものだとしか言いようがありません。

納得したいのであれば、根本的な理由ではありませんが、

if(1 + 1;); // syntax error

これならなんとなく納得できますよね。条件は式しか記述できませんのでエラーです。

このような文を厳密には式文と言います。

上の例は全て文法上認められた文であるというだけの無意味な文です。代入の例はともかく、実際にプログラム中にこういった記述をすることはありません。たんなる例です。


JSでは制御文や関数宣言を除けばだいたいこの式文の集まりですが、" { "とfunctionキーワードが文の頭にある場合は式文として解釈されません。なぜなのかというと、それは書いてあります。

Block と曖昧になることから、 ExpressionStatement は大括弧 "{" で開始することはできないことに注意。また、 FunctionDeclaration と曖昧になることから、 ExpressionStatement は function キーワードで開始することもできない。

http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/12_Statements.html#ExpressionStatement

つまり、" { "はブロック文(ifやforや関数定義で使う { } のこと)と、functionキーワードは関数定義とかぶるのでそれらが文の最初にあるときは式文としないよーということです。

2. 空文

セミコロンだけでも文です。独り立ちしてます。一本足で立ってて立派です(´;ω;`)ブワ

;

この文は厳密には空文と言います。これが文かよとちょっとビックリしちゃいますが文です。

空文は何も行わないことを「行なう」文です。変な日本語ですが、文の定義上そう表現するのが適しているかなと。まあ結局何もしないってだけです。

文法として覚えていなくたって支障はありません。あとC系の流れを組む言語にはだいたい空文があるんでJSに限った文ではないようです。

3. varをつけた代入文

代入のうち、

x = '式'
y = '文';

といったようにセミコロンある無しで式と文に分けられましたが、varをつければいずれも代入文となります。

var x = 'これは文'
var y= 'これも文';

正しくはvar文(変数文)と言います。セミコロンが無い場合でもvar文は自動でセミコロンが挿入されることになります。(式文でも挿入されますね)


あと、これまでvar無しで代入のコードを使ってきましたが通常はvarつけます。つまり変数宣言(この場合宣言とはvarをさす)していないのに代入することはまずないということです。

var x = 'varをつかって代入しているのでおっけーです;

var y;
y = 'varで宣言されている名前への代入なので、varがこの行に無くてもおっけーです';

z = '単なる代入はまずしません';

どこでもよく言われていることなので蛇足かもしれませんが念のため注意書きしておきます。

4. ifやforなどその他愉快な仲間たちの制御文

ブロック文, if...else 文, switch 文, for 文, do...while 文, while 文, label 文, break 文, continue 文, for...in 文, for each...in 文, with 文, コメント, throw 文, try...catch 文などは文です。

Mozillaさんでまとめてあるので、

をご覧ください。この他にも制御文は存在しますが主旨と関係ないので特に言及しません。


以上がJSにおける文ですが、文とは何かと一言ではなんのこっちゃ表せなかったりします。

困ったので本でも確認したのですが、「副作用が伴うもの」とか「JavaScriptに何かさせるもの」が文だそうです。抽象的すぎて伝わりにくい。

またまた困ったので、wikiで調べたら、

プログラムにおける文(ぶん、statement)とは、コードの記述単位の1つ。1つの文が1つの手続きを表すことが多い。

.

.

.

大まかに言えば、一つ以上の式や関数呼び出しで作られる、手続き構造の分割できない基本単位が文である、と考えてほぼ差し支えない。

http://ja.wikipedia.org/wiki/%E6%96%87_%28%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%29

といったことだそうです。いくぶん分かりやすいかもいれない。

そんなわけで本も含めて得た情報をまとめると、

  • 文とはJavaScriptの実行における基本単位で1つ以上の式や関数呼び出しなどからなるもの

と、言えますが。んー、分かりやすく表現できなくて申し訳ない。言葉にするとなるとちょっと難しいです。


とりあえず、式と文の言葉の区別がつけば問題ありません。式文だの空文だのといったのはこれから出てくる用語の説明ためですので、無理に覚えたりしなくても大丈夫です。

個人的にはJSにおける文と式の境界線はさほど明確ではないように思える(実際には式文は式の値を返すし、ブラウザだと文でもundefinedを返すし)ので、ここまではっきり区別することもなかったかなというところですが・・・。


難しい。どう言い切ってよいものやら。とりあえず着地はしたので文は終わり。話膨らみすぎたかな(;´∀`)

ここまでの式と文のまとめ

長くなったのでおさらい!

えーと、即時関数がなぜカッコ厨なのかといえば関数を式として評価するためでした。ほいで式ってなんだよという話になり、JSにおける式と、それから文の概念を学びました。

  • 式とは値を持ち文や式の一部を構成するもの
  • 文とはJavaScriptの実行における基本単位で1つ以上の式や関数呼び出しなどからなるもの

まとめるとこんな感じです。


以上のことを頭において即時関数のカッコ厨の理由と、それに関連する関数の式と文について話を進めていきます。式と文の意味が分かっていれば簡単です。

あと、これまで式と文のうち関数は一切例として挙げていませんでした。というのも、関数には式と文の2種類あるからです。それらの見た目は全く一緒なので、見分けがしづらいというか、微妙なのです。

ぶっちゃけ面倒だったのでまとめて書きたかったという個人的な都合です。そんなわけで、次で即時関数のカッコ厨と関数の式と文ともろもろまとめて終わり。


なぜ即時関数はカッコ厨なのかリターンズ

同じ見出しが上の方にあるんでリターンズで。


関数には式と文の2種類あります。そのうち、関数定義と呼び出しをまとめて行うようにするには関数を式として評価させなければなりません。なぜなら文のままだとエラーになっちゃうからです。

まずはこれがどういうことなのか確認。

関数宣言で即時関数のような動作ができないわけ

関数の文とは関数宣言のことです。

function hoge() {
  alert('関数宣言です。関数の文という位置づけのものです');
}

即時関数は関数定義と呼び出しをまとめて行なうものですよね。関数宣言でもそうしたいのであれば、ケツに関数呼び出しの演算子である( )をつける形になります。

そうしてつけてみると、

function hoge() {
  alert(1);
}(); // syntax error

あひー!!エラーになっちゃった!syntax errorとは構文エラー。つまり「決められた通りに式と文が書かれていないよー」といった意味です。

なぜエラーになったかというと、JSでは、

function hoge() {
  alert(1);
}
();

このように改行が入っているみたいな解釈がなされているんですね。( )は関数呼び出しを行なう演算子ですが、文の先頭なので呼び出しもクソもないわけです。だから文法エラーになっちゃったのです。


でもなんで改行が入ったように解釈されたのでしょうか?井手らっきょのせいでしょうか?

うーん、それは否めないけれども、いやむしろそうであったら「裸になること禁止令」を出せば良いだけれども、これは( )だったからこうなったとか特別な動作ではなく、たとえばセミコロンでも同じ結果になります。

function hoge() {
  alert(1);
}; // セミコロン

/* どう解釈されるかというと↓ */

function hoge() {
  alert(1);
}
; // セミコロン

空文になっちゃいました。このように、関数宣言における終端の波括弧(" } ")で文は終わりとなっていまして。正確に言えば関数宣言そのもので文は完結しているのです。


んーと、これは波括弧だからいまいちイメージが沸かないかもしれませんが、改行が入る現象を別の文で表すと、

/* 関数ではありませんが、上記の改行が入る形になる現象は、 */
/* 結局こういうことになってるのと同じ */
var x = 1; + 2;

/* これがどう解釈されるかというと↓ */

var x = 1;
+ 2;

当たり前ですがセミコロンの後に続けて式が書けるわけがありません。

関数宣言に( )をつけて書くというのは、これほど無意味なことをやってるに等しく、文の後ろで式が(正確には文が)書かれていることと一緒です。(上記のコードは関数宣言における終端の波括弧とセミコロンが同じという主張ではなく、「文の終わり」という点だけが共通していることを示した例です)

なので、同じように( )でもセミコロンでも関数宣言とは別の文として解釈されたのです。なんとなくイメージが湧いたでしょうか。


こうして関数宣言ではできないため関数を式として評価させることで即時関数の動作を実現するに至り、めでたく井手らっきょはダグトリオに進化しました。目新しい!やったね!ドッゲドッゲブリュリュイイイイイ!!!

終わり。



じゃなあああくて。

関数宣言だと即時関数のような動作を実現することはできないとわかりました。だから、関数を式として評価させることで( )をケツにひっつけて呼び出せるようにするのです。

では具体的にどうであれば関数は式となり、あるいは、文となるのでしょうか。

関数における式と文の関係

その関数が式なのか文なのかが決まるのは、functionという文字が行の先頭にあるかどうかという点です。行の先頭にあればそれは関数宣言(関数の文)となり、それ以外であれば関数は式となります。


なぜそう言えるのかというと、はっきり書かれていませんが式文の項でこのように書かれています。

...また、 FunctionDeclaration と曖昧になることから、 ExpressionStatement は function キーワードで開始することもできない。

http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/12_Statements.html#ExpressionStatement

「functionキーワードが行の先頭にあると関数宣言とかぶるので式文としない」とあります。

つまり関数定義のうち「functionキーワードが行の先頭にあるならば関数宣言である」と言えますね。これを言い換えると関数定義のうち「関数宣言でないのであればfunctionキーワードは行の先頭ではない」とされ、関数は文と式の2種類しかありませんので、行の先頭にfunctionがなければそれは関数宣言ではなく式であると導けます。


そういうわけで、functionが行の先頭にあるなら関数宣言となり、

function hoge() {
  alert('functionが行の先頭にあるので関数は文です');

  function piyo() {
    alert('関数の中の関数でも、行の先頭にあるので関数は文です');
  }
}

function fuga() { alert('インデントがなくても行の先頭にあるので関数は文です'); }

それ以外なら関数は式であると言えます。

var hogera = function() {
  alert('functionの前に = や var があるので関数は式です');
};

[
  function() {
    alert('配列に格納されてるときもfunctionより先に [ があるので式です'); 
  }
];

if(function() {alert(1); }){
  alert('こんな不細工なことやりませんが式なので条件式にも使えます');
}

(function() {
  alert('(1+ 1)と同じ意味での( )で関数をくくれば、functionより先に ( があるので式となります');
});

雑な言い方になりますが、functionより前に何か文字がなければ関数宣言ですし、なんかあれば関数は式になるって感じです。

それから関数宣言で関数名は必要ですが関数の式では省略できます。(関数の式で名前をつけるとしたら何か目的を持ってつけます。特に付ける理由がないのであれば無名で問題ありません)


このようにその関数が式なのか文なのかといったことは単純な仕組みだったりしますが、慣れないうちは混乱することがあるかもしれません。しかしほぼ文法上の違いですから、関数は式でも文でも機能は変わらないと意識しておけば問題ないです。

即時関数の仕組み

ここまでで関数を式として評価させる理由と、その方法を確認してきました。

式として評価させる理由は、

  • 関数宣言のままだと関数呼び出し演算子の( )を使うことができないから

で、関数を式として評価させる方法は、

  • functionキーワードを行の先頭におかない

というものでした。


こういったことが理解できれば、カッコなばけものに見えた即時関数もどういった仕組みで成り立っているのかみえてこないでしょうか。


まず関数を式とするために (1 + 1) といったように利用するグループ化演算子の( )で関数をくくります。

(function() {
  alert('関数を( )で囲んでいます。この時点で関数は式となっています');
})

functionより前に ( があるので式となりますね。そうして、関数呼び出しの演算子( )を最後にくっつけることで、

(function() {
  alert('関数呼び出しの演算子である( )をくっつける');
})(); // ←関数呼び出しの( )ね

関数定義と関数呼び出しをまとめて行なうことができる即時関数ができました。

(1 + 1)といったように使うグループ化演算子の( )と、hoge()といったように使う関数呼び出し演算子の( )の区別がついていないと、単なるカッコ厨に見えてしまいますが区別できればわかりやすいですね。


さて、関数を式として評価させるために即時関数ではグループ化演算子の( )を使って関数全体をくくっています。でもよく考えたらこれって別に( )でくくらなくてもいいですよね。

関数を式とするにはfunctionキーワードを行の先頭におかなければ良いので、その他にも方法はあるわけです。

たとえば、

+function() {
  alert('これでも即時関数です。実はカッコである必要はないのです');
}();

こうしても問題ありません。functionの前に + があるので見た感じだとエラーになりそうですが、関数は正常に呼び出すことができます。その他同じような例としては、即時関数(function(){ ... })()の別の書き方いろいろ: Architect Noteにいくつか紹介されているのでどうぞ。


このように必ずしも即時関数はカッコ厨である必要はありません。カッコで関数をくくることは式として評価させるための、いくつかある方法のうちの1つにすぎないのです。

とはいえ一般的に使われているのはカッコでくくる方法なのでその習慣に習う方が良いでしょう。


なぜ即時関数はカッコ厨なのかまとめ

即時関数はなぜカッコ厨なのかいえばそれは関数を式として評価させるためでした。

即時関数は関数定義のあとにすぐ呼び出すコードです。関数宣言のまま関数呼び出しの演算子で呼びだそうとすると、それらは別々の文として解釈されてしまうためエラーになります。

function hoge() {
  alert('関数宣言');
}
(); // syntax error!!!

そこで定義の後にすぐ呼び出しができるよう関数を式として評価させてます。そして、その方法はカッコで関数をくくるというものでした。

(function hoge() {
  alert('即時関数どす');
})();

これが一般的に即時関数と称されるコードです。

ですが、関数を式として評価させる方法はカッコでくくる以外にも方法があり、

+function() {
  alert('これでも即時関数');
}();

このような書き方でも即時関数として利用することができるのです。

関数をカッコでくくるのはいくつかある方法のうちの1つにすぎません。ただし、一般的に使われているのはカッコでくくる方法なのでその習慣に習って利用した方が良いと言えるでしょう。


といったところで終わり。

ポイントは式と演算子と文ですね。これらが何となくでもわかれば文法上の悩みは消えるでしょう。黒魔術的なコードについては、えー黒魔術師を目指すのであればがんばってください。JSは黒魔術が結構あるので楽しいかもしれません。私は拒絶反応がでるので止しときます。たまねぎ剣士でいいんです。


ここまででかなりくどく書いてきましたが、なるべくコードではなく言葉で表現したつもりです。こうすると冗長になってしまってだいたいは読むのをやめるもんですが、ここまで読んでくれたあなたは優しい。

よし、井手らっきょ2号の称号をあげます。ええ、ええ、わかっています。そんな感謝だなんてあははは。同じ井手らっきょ仲間じゃないですかあ。あははははあ。

・・・というわけで、まあ、マイノリティに悩んでいる人のお役に立てれば幸いです。

厳密には関数宣言は文ではない

今までの流れをぶったぎるような内容だけれど、実際のところFunction Statementという語句はありません。

JSだとどうなのかなと検索してみると、2つほど見たことあるサイト名があがってきまして。MDCやmsdnですね。どうやらstatementとして扱っているようです。

JSでは文として扱っても問題ないということなのでしょう。というか文なのかな?静的な関数にそれはどうなのかとも思いましたが、文として広まっているし理解しやすい方がいいのでこの記事でも文としておきました。


関数定義の名前

定義については以下のページを参考。

機能としては全て関数を定義するって部分は一緒なのですが、それぞれ姿形が違ったり効果も微妙に違いがあるのです。

関数宣言

よく見かける普通の関数の構文。関数宣言と言います。

function hoge() {
//
}

ケツにセミコロンはいりませんので。関数式と比べるとパフォーマンスは良いみたい。特別な理由がない限りこの書き方が基本。

関数宣言の特徴として巻き上げが挙げられます。

alert(hoge); // function hoge() { return 1; }
var hoge = 1;

function hoge() {
  return 1;
}

alert(hoge); // 1

定義のタイミングが関数宣言で次が変数って流れ。

関数式

関数リテラルとも言われます。あとMDCではfunction演算子とかも言われてますが。んー、function operatorは良い表現とは思えませんので私は非推奨です。リテラルもなんか伝わりにくい。というわけで関数式おすすめ。


変数に関数を代入する構文です。JSらしい書き方ですね。

var hoge = function() {
//
};

無名関数でないケースもあります。「名前付き関数式」って言われていたりしますが、名前があろうが無かろうが関数式と抽象的に表現したりもするんで、ゆるゆるです。

var hoge = function fuga() {...};

この場合fugaはこの関数の内部からしか利用できません。だから通常は、

var hoge = function fuga() {
//
};
fuga() // error

となってしまうわけです。が、これJScriptでは漏れます。


あとね、セミコロンは必須です。無いと、たとえばだけど、

var hoge = function() {
  alert('セミコロンどこいったーヽ(`Д´)ノ');
}

(1 + 1)  * 2 ;

としたら関数が呼び出されます。こんなコード後ろに書かないけど、もしなんか書くことあったらふわっとつけときましょう。というか常につけます。。


これらは関数式というかそもそもはvar文なのですが、なんでもかんでもvar文つってたら分かりにくいので特徴を表している関数式の名前で良いと思います。

Functionコンストラクタ

これは関数定義というわけではありませんが、関数を生成します。明確な目的がない限りまず使いません。

var x = new Function('y', 'z', 'alert(y + z)');
x(5,3);

知らなくても大丈夫です。

メソッド

JSではオブジェクトのプロパティが関数のときメソッドと称します。

var hoge = {
  piyo: 123, // プロパティ
  fuga: 'あいうえお' // プロパティ
  hogera: [1,2,3]; // プロパティ
  hogehoge: function() {...} // メソッド
};

メソッドであっても実際はプロパティですとか言うとややこしくなるけど、まあそんなややこしさの爆弾をJSは抱えています。プログラミング言語ってここまで定義が曖昧というか整理されていないものなんでしょうか。


(function(){}())と(function(){})()について

かの有名なダグラス・クロックフォードさんは、

(function() {
//
}());

ってつかえよーうって推奨してます。Lispの面影を感じる今日この頃。たぶん意図はあると思うんです。

でも見辛い。従来に慣れたんだ。嫌だ。やめてくれ。ダグラスさん、まだあっしは、あっしはこの既成概念から逃れることのできねえ俗物なんでごぜえますだ。

といったように身も蓋もない思いでこざいます。

申し訳ないのですが、これからさきどちらが広まるか知りませんけれども、今回は従来の形式を利用させていただきました。皆様はぜひともこの俗物のごとく嘆かわしい人物とならぬよう身を清め生きることをお薦め致します。


くそったれ長い!

大丈夫だ!長いと思うから長く見えるんだ!長くないと思ったら・・・・・やっぱり長い!神様に誓って長い!でも良いんだ!そんなことより暑いからアイス食べよう!おいしい!ありがとう!アイスの神様!というか最近涼しい!たぶんきっとエアーマンのおかげだと思う!ロックマンありがとう!


参考

2011-03-08

typeof演算子から学ぶJavaScriptのデータ型の概念と関係する考察のまとめ

まずはtypeof演算子について。JavaScript Gardenというウェブページがありまして。こういう一文がありまして。

The typeof operator (together with instanceof) is probably the biggest design flaw of JavaScript, as it is near of being completely broken.

http://bonsaiden.github.com/JavaScript-Garden/#typeof

訳)http://efcl.info/adiary/Javascript/JavaScriptGarden#k95p17


要するにtypeof演算子は「ヒャッハーtypeof演算子ぶっ壊れてるぜー」ってことらしい。理由が下記の表。


[表1]
Value               Class      Type
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object (function in Nitro/V8)
new RegExp("meow")  RegExp     object (function in Nitro/V8)
{}                  Object     object
new Object()        Object     object

比較するとこの表からは一貫性が感じられない。よって、typeof演算子が使える唯一のケースはtypeof foo === 'undefined'だけ、ということらしい。というかキモいらしい。う◯こらしい。巻◯糞らしい。実はピーでピーなピーらし(自主規制

本当にそうなのか?

私は「ぶっ壊れてないと思うし使えるけど扱いがややこしい演算子」だと思う。でも使うなってほどbadな演算子でもない。


この表から読み取れる「一貫性が感じられない」ということを一番伝えたい部分は、

[表2]
Value               Class      Type
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object

この部分だと思います。

他にもFunctionだけTypeがfunctionなのはなんでだとか、new Array(1,2,3)がobjectなのはなんでだとか、v8のRegExpがfunctionとかどないやねんとか、nullやundefinedどこいったとか人によって思うことはまちまちあるでしょう。

でも内容が複雑になるのでここに要点を絞って考えてみます。

もう一度言うけれど、私はtypeof演算子はぶっ壊れていると言われるほど破滅的なものではないと思う。でも人によっては直感的でない部分はあるかもしれない。

細かいことでどうでもいいといえばどうでもいいことなのだけど、それぞれ役割というものがありできることとできないことがあります。typeof演算子は何のために存在するのかをはっきりさせ、また、JSにおけるデータ型の概念の本質を探ってみたいと思います。


JSのデータ型を復習

基本的なことですがJSにおけるデータ型は、String型、Number型、Boolean型、Null型、Undefined型、Object型とあり、それぞれ基本型(プリミティブ値や基本データ型とも称される)と参照型(オブジェクト型とかオブジェクトとも称される)に分けることができます。

分け方はString型、Number型、Boolean型、Null型、Undefined型が基本データ型にあたり、Object型が参照型です。配列、関数、オブジェクトなどはObject型に含まれます。(言葉の定義はそれぞれ違うのであれですが、概念的に整理しやすい言葉を選んでます)


全てのデータがオブジェクトというわけではない

というわけでJSにはプリミティブ値があります。全てのデータが何かのオブジェクトというわけではありません。

"foo".lengthとアクセスすることができるのでStringオブジェクトに見えますが、これは内部的に(暗黙的に)ラッパーオブジェクトを生成している(厳密には内部でToObjectを呼び出してオブジェクトに変換したもの)のでプロパティやメソッドにアクセスできるだけで、アクセスが終了すれば廃棄され文字列に戻ります。

オブジェクトのように振る舞うだけで本質的なオブジェクトではありません。

これはサイ本など熟読している人には当たり前のことですが、案外伝わっていないことだったりしますね。

そしてこれらの現象はNumber型、Boolean型も同じ。つまりプリミティブ値のうちnullやundefinedを除くラッパーオブジェクトとしてStringオブジェクト、Numberオブジェクト、Booleanオブジェクトが用意されていて、必要な場合に暗黙的に利用されるわけです。


プリミティブ値以外のデータ型はObject型

基本的にプリミティブ値以外のデータ型はObject型となります。プリミティブ値以外全てオブジェクトなんで当然といえば当然です。

様々なオブジェクトのデータ型は全てObject型としてまとめられているので、Array型やRegExp型といったようなオブジェクトのデータ型は実は存在しません。new Array()もnew RegExp()で生成されたオブジェクトのデータ型もObject型となります。


ラッパーオブジェクトでも同じこと

当然ラッパーオブジェクトも例外ではありません。ラッパーオブジェクトは暗黙的に利用されることを基本としていますが、他の組み込みオブジェクトと同じく明示的にコンストラクタによってオブジェクトを生成することができます。

その場合も当然生成されるのはオブジェクトなのでデータ型はobject型です。

[表3]
Value               Class      Type
-------------------------------------
[プリミティブ値]
"foo"               String     string
1.2                 Number     number
true                Boolean    boolean

[ラッパーオブジェクト]
new String("foo")   String     object
new Number(1.2)     Number     object
new Boolean(true)   Boolean    object

そういった背景から考えると、表のようにコンストラクタで生成されたデータ型(Type)がobjectになるのは健全だと言えます。

それでも納得できないのであれば、ここでもし仮にコンストラクタによって生成されたオブジェクトのTypeが直感的に正しいと思われるstringやnumberを返すことにしましょう。

[表4]
Value               Class      Type
-------------------------------------
[ラッパーオブジェクト]         ↓ohh!yes!yes!( ´,_ゝ`)
new String("foo")   String     string
new Number(1.2)     Number     number
new Boolean(true)   Boolean    boolean

もちろんString型やNumber型はJSではプリミティブ値のデータ型です。なので、new演算子を用いてコンストラクタで生成されたオブジェクトは「プリミティブ値」ということになりますね。










・・・Pardon?( ´,_ゝ`)


データ型とオブジェクトの種類は違う

とまあそういうことになれば、JSとしては言語的にも仕様的にもかなりいかがわしいものになってしまいます。

当たり前ですが、String型やNumber型というのはオブジェクトのデータ型ではなくプリミティブ値としてのString型やNumber型です。まさにドがつくほど当たり前のことなのです。

でもどこかでオブジェクトとしてのString型やNumber型といったように、無意識にクラス型のような概念と混じって解釈しているとやっかいです。

何度も言いますが、typeof演算子が返すString型やNumber型は「プリミティブ値としてのデータ型」です。クラスに属するオブジェクトの型を示しているわけではありません。

そして表で言うClassとはオブジェクトの内部プロパティにあたる[ [Class] ]というプロパティです。この内部プロパティは主に組み込みオブジェクトの種類を区別するためのプロパティであって、一般的な意味でいうClassとは違います。


だからデータ型と[ [Class] ]を比較するのはおかしい

こういった理由から表のようにTypeと[ [Class] ]と比較するのはそもそもおかしいのです。Typeとはデータ型のことで、[ [Class] ]とは上述した通りデータ型のうちObject型にあたるオブジェクトの種類を示す内部プロパティだからです。

一見すると同じような性質を持ったものに見えるものですが、このように考えると別ものだということが分かります。


typeof演算子の役割とはデータ型を返す演算子

見出しのような説明はどのリファレンスでもだいたい書かれていますが、少し深く考えないとその意味が読み取れないというのはどういうことなんでしょう。まあいいや。まとめ。

問題として挙げた表を見てみると、

[表2]
Value               Class      Type
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object

Typeと[ [Class] ]で比較すると一貫性がないように思えます。しかし、そもそもそれらは比較することのできないものです。

なぜなら、typeof演算子はデータ型がString型、Number型、Boolean型、Undefined型、Object型のうちどの型に当てはまるのかを返す演算子で、[ [Class] ]とはオブジェクトの内部にあるオブジェクトの種類を区別するためのプロパティだからです。

表の[ [Class] ]の項目はObject.prototype.toStringを汎用的に呼び出すことでオブジェクトから取得できます。つまり、次のようにそれぞれTypeと[ [Class] ]を取得したと思われます。

var foo = "bar";

// Type
typeof foo;                          // string

// [[Class]]
Object.prototype.toString.call(foo); // [object String]

そしてコンストラクタの場合。これが重要ですが、

var foo = new String("bar");

// Type
typeof foo;                          // object

// [[Class]]
Object.prototype.toString.call(foo); // [object String]

こうなります。typeof演算子が返しているのはデータ型としてのobjectです。

そしてObject.prototype.toStringが返しているのはデータ型がObject型のオブジェクトのうち、Stringオブジェクトだというオブジェクトの種類を意味する文字列です。([object String]って思いっきり書かれているのでそのままなのですが)

この通り2つの値が意味するものは異なります。


そういうわけでtypeof演算子とはオブジェクトの種類ではなくデータ型を返す役割を果たすという、ごく当たり前な演算子です。オブジェクトの種類を返すと思って利用しないよう注意したいですね。

ここで、一番最初の表をtypeof演算子向けにいくつか付け足して並べ替えます。実はあれは横に比較してみるのではなく、縦にみるべきです。あとtypeof演算子向けの表としていくつかつけたします。

[表4]
Value               Class      Type
-------------------------------------
[基本的なプリミティブ値]
"foo"               String     string
1.2                 Number     number
true                Boolean    boolean

[コンストラクタ]
new String("foo")   String     object
new Number(1.2)     Number     object
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
new Array(1, 2, 3)  Array      object
new RegExp("meow")  RegExp     object (function in Nitro/V8)
new Object()        Object     object
new Function("")    Function   function

[オブジェクトのリテラル]
[1,2,3]             Array      object
/abc/g              RegExp     object (function in Nitro/V8)
{}                  Object     object
function(){}        Function   function (つけたし)

[nullとundefined]
null                           object (つけたし)
undefined                      undefined (つけたし)

縦に見ればなんらかの一貫性があるということが確認できるはず。あと具体的にtypeof演算子がどのようなデータ型を返すのか対応表も載せておきます。

TypeResult
Undefined"undefined"
Null"object"
Boolean"boolean"
Number"number"
String"string"
Object (native and doesn't implement Call?)"object"
Object (native and implements Call?)"function"
Object (host)Implementation-dependent

以上のことからtypeof演算子はデータ型を調べる演算子で、もっといえば「データ型がプリミティブ値かどうかを調べる演算子」と言って良いでしょう。

そもそもオブジェクトはObject型と括っているわけですから、オブジェクトなんてアウト・オブ・眼中です。typeof演算子からすればデータ型なんて「プリミティブ値とその他の愉快なオブジェクトたち」なのです。偉そうですこいつ。

何はともあれオブジェクトの種類を知りたいときはObject.prototype.toStringを利用しましょう。


これらの使い分けが面倒ととらえるかどうかは人それぞれです。考えるのが面倒であればObject.prototype.toStringだけでも良いと思います。これらの言い分に関しては自由ですからね。

そんなわけで、評価対象のデータ型を返すという本来の役割から考えればtypeof演算子はぶっ壊れていないと思います。プリミティブ値への利用なら使えるでしょう。

ちなみに表を見ても分かる通り少しおかしい点がいくつかありますが、ここではそこが論点ではないので後述しています。


そして私はtypeof演算子が好きではないという結論が出たところで終わり( ´,_ゝ`)

今回は視点の発想をtypeof演算子からJSにおけるデータ型の概念をとらえてみようということで考えてみました。以上ですが、おかしなとこあればコメントください。

その他いろいろ気になったこととか補助的なこととか適当に考えてまとめておきます。分けるのめんどくさい。


型について

http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/8_Types.html

この定義を使いました。本やウェブサイトだと関数や配列もデータ型として扱っており、参照型に含ませている場合が多いのですが、今回はtypeof演算子の本質というわけで厳密な定義を使いました。

配列も関数もデータ型と認識した方が分かりやすいとは思いますが、今回ばかりはしょうがないです。

ちなみにこの記事はES3ベースです。一応ES5も見出し程度で確認したところデータ型に関しては大きな変更はなかったと思います。(こういう変化はある。ES5, Property Descriptor解説 - 枕を欹てて聴く

http://people.mozilla.org/~jorendorff/es5.html#sec-8


データ型とオブジェクト

これまでの文章を見て頭痛くなったJSに慣れていない人用に、データ型とオブジェクトの関係を図にしてみました。

f:id:sandai:20110308013145j:image

早い話が左側のデータ型がtypeof演算子が返す型の名前です(functionは定義の都合上含めていません)。そして汎用的なtoStringメソッドは、データがどのオブジェクトに属するかを返すわけですが、それがビルトインオブジェクトのそれぞれオブジェクト名です。(厳密にはそのオブジェクトの[ [Class] ]プロパティにある値)

文章だといろいろ芋臭くなりましたがこのようにまったく性質が違うということが分かると思います。

まあデータ型とオブジェクトの種類の関係性が不透明というか整理されていないから、ややこしい印象を与えてしまっているんじゃないかなと思います。

あと文字については精一杯でした。勘弁してください。


typeof演算子とObject.prototype.toStringの使い分け

プリミティブ値かどうかのときは素直にtypeof演算子。Object.prototype.toStringは対象のオブジェクトが不明なときとか使うと良いと思う。

var type = (function(toString, types){
  var l = types.length, _types = {};
  while((--l) >= 0){
    (l < 8) ? 
      (_types['[object ' + types[l] + ']'] = types[l].toLowerCase()) :
      (_types[types[l]] = types[l].toLowerCase());
  }
  return function(obj) {
    if(obj == null) { return obj + ''; }
    if(obj.nodeType) { return _types['Node']; }
    return _types[toString.call(obj)] || 'object';
  }
})(Object.prototype.toString,
  'Boolean,Number,String,Function,Array,Date,RegExp,Object,Node'.split(','));

type([]); // 'array'
type(new RegExp()); // 'regexp'
type(1); // 'number'
type(null); // 'null'
type(document); // 'node'

プリミティブ値以外はじくといったときにわざわざtoStringを使うことはないと思うけど。面倒だったら上の方法だけ使っても別に良いでしょう。


明示的にコンストラクタで生成するケースはあまりない

少なくとも文字列、数値、真偽値のコンストラクタはまず使わない。

// オブジェクトとして生成してもほとんど意味がないので使わない
new String("foo")
new Number(1.2)
new Boolean(true)

// Errorオブジェクトはほぼ触らない
new Error()

// new Array(1000)とかならあるかも
new Array(1, 2, 3)

// new Function("return window;")()みたいな黒魔術が
new Function("")

// 通常は/meow/のようにリテラル使うと思う
new RegExp("meow")

// これだけなら{ }を使う
new Object()

利用するにしても目的を持って利用します。なんとなくで使わないでしょう。

ちなみにプリミティブ値の場合オブジェクトとして生成するとどうなるのかというと、

console.log(new String('abcde')); // abcde { 0="a", 1="b", 2="c", 3="d", 4="e"}

みたいなオブジェクトになってます。でも、文字列として扱うときは文字列になってくれます。

console.log(new String('abcde') + 'fg'); // abcdefg

valueOfやtoStringが呼び出されて文字列として変換してくれてるんですね。これは不思議でたまらんかった。

valueOfとtoStringメソッドの水深43cmぐらいの深さの話 - 三等兵

valueOfとtoStringとToPrimitive - os0x.blog


このようにStringオブジェクトでも扱いやすいよう配慮してくれているんですが、何があるか分かったもんじゃないので意味なく使わない方が良いです。

そういうわけでjQueryとかで、

$.each(['abc', 'def', 'ghi', 'jel'], function() {
    console.log(this); // abc { 0="a", 1="b", 2="c"}...
});

thisを使うと要素の文字列がStringオブジェクトになるので注意。callでcallback呼んでるのでそりゃこうなりますね。あとnewを使用しないコンストラクタの場合、というか関数として実行した場合そのコンストラクタに依存します。

String(1234); // '1234'
Number('1234'); // 1234
Boolean(1234); // true

といったように型変換の形になります。速度重視だと演算子で済ますケースが多いと思いますが、使ってもいいと思います。でも、

Boolean('false'); // true

みたいなトラップが潜んでいるので気をつけてください。


データを全てオブジェクトと解釈することの障害

JSはデータが全てオブジェクトだという認識でもだいたい大丈夫。そのように配慮されている。正確にはJSがではなくECMAScriptがですが。

そしてJavaScript Gardenでの扱いを見る限りでもそう解釈し操作するように最適化しているように見受けられます。しかし、例えばそういった認識だと演算子を誤った方法で利用してしまうことがあります。

new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

// ↓誤った使い方
'foo' instanceof String; // false
'foo' instanceof Object; // false

プリミティブ値なんてしらねーよという意識だとこのような使い方をしてしまいます。プロパティやメソッドにアクセスしていないとただの文字列値だからfalseになるのは当たり前なのですが。

そういうわけでプリミティブ値に対してinstanceof演算子使うというのは結構恥ずかしいことです。そして私はこの恥ずかしい思いをしたことがあるわけで、誰しも通ることがある道なんだと気をつけてください。




べ、別にあんたのために忠告したわけじゃないんだから勘違いしないでよねっ///


typeof演算子は式を評価する演算子である

たとえば、

typeof(new String('foo'));

といったように関数みたい使えるのだけれど、そもそも演算子なので通常はいりません。そして関数みたいに使っているわけでもありません。

結局どういうことになっているのか他の演算子で例えるなら、

+(new String('100'));

こんなイメージだと思います。

でもtypeof演算子は式を評価するわけですから、

typeof (new String('100') + new Number(100) + 100); // 'string'

こういった形はあると思います。実践的なコードではありませんが。たとえば括弧の位置をかえると、

typeof (new String('100') + new Number(100)) + 100; // 'string100'

こんな感じになります。括弧使いたいです姉さん。演算子と散々言ってきて今更ですが、関数じゃないってことだけおさえておきたいですね。


nullとfunctionとNitro/V8でRegExpがfunctionな件

typeof null === 'object'のわけ

どっかの高尚な人が答えを出しているのかもしれませんが、なぜこのような仕様なのでしょう。typeof演算子はデータがReference Typeかどうかでobjectを判断しているようですが、nullの場合どうでもええからobjectにしとけって感じなんですね。

概念的な面での配慮かなーというのが今のところの結論なのですが。あ、いやいや、だったらnullじゃないか。そっちの方が筋が通っている。

ES5もnullはobjectなんですが互換かなこれは。今更変えられないよなあ。


typeof function(){} === 'function'のわけ

functionはObject型なんですがtypeof演算子を使うと特別にfunctionと返してくれます。しかしながら型は定義上Object型です。Function型はありません。でもfunctionと返します。

なんでこんな特別扱いなんでしょう。それじゃあArrayもarrayって返してくれてもいいんじゃないか!ケチッ!と思います。ここでES3のtypeof演算子が返す値の表をもってきます。

TypeResult
Undefined"undefined"
Null"object"
Boolean"boolean"
Number"number"
String"string"
Object (native and doesn't implement Call?)"object"
Object (native and implements Call?)"function"
Object (host)Implementation-dependent

Object (native and implements Call?)であればobjectのうちfunctionとして返すらしい。[ [Call] ]プロパティを保有しているオブジェクトがfunctionで、そうでなければobjectなんですね。

それだけで区別できるなら分けた方が良いとそういう判断だったのでしょう。というか関数だからそりゃ判定したいよね。こうしたことが正解かどうか知りませんが。

objectとfnctionの違いをリテラルで判断しているとこういう発想には辿りつけないですね。概念的な理解は厳しい。


v8のRegExpインスタンスがfunctionなわけ
typeof new RegExp(); // function (Nitro &v8)

変な感じがしますがこれはあながち間違ってはいません。

var r = new RegExp(/([あ-る])あ?([あ-る])う?.+さ([あ-る])す.+つ([あ-る])([あ-る])/);
var str = "あいうえおかきくこさしすてそたちつてる";

r(str); // ["あいうえおかきくこさしすてそたちつてる", "あ", "い", "し", "て", "る"]

*「え、なにこの暗号?」

_「いいから実行してみなよ」

*「えーなにもう。じゃあ実行ボタンをポチっと」

_「・・・」

*「なんかでてきたけどこの”あいうえおかき・・・”って?意味分かんないよもう」

_「それのうしろみてみなよ」

*「え?うん。んーと、"あ", "い", "し", "て", "る"・・・、あいしてる?」

_「うん、結婚しよう」

*「きゃ。ポッ」


ポッ、じゃねーよぼけええええ!


というわけでデフォルトでexecメソッドが呼び出されるようになっています。

型に関してはFirefoxも以前はfunctionと返していたけれど、今はobjectになっていますね。ちなみにRegExpオブジェクトには[ [Construct] ]という内部プロパティがないのでnewできません。

new /po/(); // TypeError: function is not a function (v8)
new /po/(); // TypeError: /po/ is not a constructor (firefox)

関数であって、関数ではない・・・か。v8は哲学的思想をお持ちのようだ。深い。

関数は関数じゃないもん!ポッ。とおっしゃっていますので関数は関数じゃない!と怒られないようにしたいですね。むしろ怒られたいですね。よくわかんないですね。ちなみにregexp.execと使った方が全然分かりやすいのでこっちをおすすめします。


あ、間違っても無許可で正規表現プロポーズなんてしようとしないでくださいよ!なんかどうでもいい文章を長々とかいて、「なにこれいみわかんない」とかつって今度は「これおしてみなよ」とかつって押したら文字がどんどんあらわれてきてそうなってこうなってああなんて「お父さんお母さん今までお世話になりました」的なロードは私に著作権があるので許可をとってください!著作権料レッドブルをいただくからねっ!w


Object.prototype.toStringの引数にプリミティブ値が渡せる理由

引数に渡されたオブジェクトの[ [Class] ]を返すのがObject.prototype.toStringの役目です。

というわけで通常はプリミティブ値だといけないように思えますが、[ [Class] ]の取得のためにToObjectという内部でオブジェクトに変換できる関数を使ってオブジェクトに変換し、[ [Class] ]を取得します。

このようにプリミティブ値を引数として渡すと内部でオブジェクトが生成されその[ [Class] ]を返すもんですから、Stringオブジェクトの[ [Class] ]とデータ型としてのString型が同じように見えてしまいます。

実際はオブジェクトとしてのStringと、プリミティブ値としてのStringという質の違うものなんですが。

変数に型がないんだからいっそのことRubyみたいになってたら良かったのにね。Javaっぽくしなきゃいけなかった理由でもあるのか。Rubyがあるとかないとか以前にその当時はそれが普通だったのでしょうか。歴史を知らないので分かりませんが。


AS3.0でtypeof new String("foo") === "string"なわけ

気になってAS3.0を少し調べたんですが、見出しのとおりになるそうです。これが通るならデータは全てオブジェクトなのでしょうね。"string"とはStringクラスのオブジェクトということになるのかな。

ES4に準拠しているようなんでデータ型にプリミティブ値はあるでしょうが、事実上はオブジェクトと同じなのでしょう。


JavaScriptとは何か

typeof演算子が生きるための言語です。JavaScriptがあるからtypeof演算子があるんじゃない。typeof演算子があるからJavaScriptが存在するのです。

お互いが活かされているだなんて啓発的なことを言ってるinstanceof演算子とは違うのです。

「instanceof演算子ェ、つながりなんて大切なんじゃねえんだよ。おれがいることが大切なんだ!」と、typeof演算子はおっしゃっています。半端ないほどブリリアントです。

そいうわけでtypeof演算子は「半端ないほどブリリアント」という真の結論に達したところで終わり。

2011-02-24

DOMに要素が挿入される度にイベントを起こすDOMNodeInsertedの扱い方

DOMに要素が挿入されたときを検出できるのがDOMNodeInsertedイベント。

たとえばajaxで特定の場所が表示されたらDOMに要素が挿入されたときや、ページング系のアドオンやユーザースクリプトでスクロールバーが下にきたら要素が挿入されたときなど、そういったときにイベントを発生させることができます。

要素が挿入されたかどうか検出するには自前でDOMを監視するコードを書く必要がありますが、このイベントを使えば簡単ですね。IE以外の主要なブラウザには実装されています。

しかしこれどうも確認してみたら非推奨になってるらしいんだけど。

http://www.w3.org/TR/DOM-Level-3-Events/#event-type-DOMNodeInserted

あれか。同期的だからかな。何もしらずに使うと面倒なことなりますからね。というかなりました。とはいえ、これに変わるものはないのでしばらくの間は利用せざるをえません。


でも普通に使うとやっぱり面倒なことになるんで、以前書いたアマゾンの検索ページとかランキングのページでレビューをポップアップ表示できるグリモンやGoogle Chromeの拡張no titleで、ajaxやページング系のアドオンやユーザースクリプトでのDOM挿入時に対応させた方法を書いておきます。

あとMutation系の情報少なくて困ったので残しておく用。


DOMNodeInsertedは同期的

http://www.w3.org/TR/DOM-Level-3-Events/#event-type-DOMNodeInsertedで記述されていますが同期的です。というわけで、要素が挿入されるごとにイベントが発火して鬱陶しいことになります。

なので普通に使うと非常に面倒というか、少なくともそのスクリプトを書いたときは面倒なことになりまして。

たとえば、

document.addEventListener('DOMNodeInserted', function() {
  var xpath = document.evaluate('.//div', document, null, 7, null);
  for(var i = 0, elem; elem = xpath.snapshotItem(i); i++) {
    // なんちゃらかんちゃら
  }
}, false);

みたいにやってたら要素が挿入されるごとに毎回呼び出されてもっさりします。困ったなあというわけで、非同期で処理します。


そういうわけで非同期にしよう

単純に発火のタイミングをずらして無駄なものははじくようにします。そういうわけでタイマー使いましょう。

var timer = 0;
document.addEventListener('DOMNodeInserted', function() {
  if(timer) return; // timerが初期化されていなければはじく
  timer = setTimeout(function() {
    var xpath = document.evaluate('.//div', document, null, 7, null);
    for(var i = 0, elem; elem = xpath.snapshotItem(i); i++) {
      // なんちゃらかんちゃら
    }
    timer = 0; // 初期化
  }, 30);
}, false);

簡単ですね。これだけでかなりコストがかからないようになりました。タイマーを使って、一定の間隔でイベントが発生するようにしています。無駄なものはタイマーの登録前にはじくので、それ以下の処理は実行されません。

このイベントは廃止予定みたいですが、あー、というかMutation系が廃止になるようです。warningばっかり。

でも現状は使ってもいいんじゃないかな。というか使うしかないですね。使い方を間違えないようにしたいところです。DOMへの挿入時にいろいろ処理したいときは便利。

その他のMutation系のイベント一覧はこちら。見やすい。

no title

DOMの全ての変化を検出するDOMSubtreeModifiedや削除を検出するDOMNodeRemovedなどありますが、たいていの場合は上記のように非同期にしておけば問題ないかと思います。

利用シーンはグリモンやChromeの拡張ぐらいに限定されるかもしれませんが、そういったスクリプトで使う場合は気をつけたいですね。


ちなみに

no titleがGoogle Chromeの拡張でAuto pagerだとうまく動作してなかったorz

たぶんAuto pager側でそれ用のAPI用意されてたはずなので、対応させておきます。