JavaScript Hyakusho
手を動かしたり、JavaScript Ninja読んだりして、
百姓なりにJavaScriptがうすぼんやりみえてきたので、
忘れないように大事そうなとこメモ書き。
参考: JavaScript Ninja / Effective JavaScript / サイ本
というかこんなん読んでないでみんなも読みましょう。
##JavaScriptと真摯に向き合うために大事そうなこと
jsの関数はファーストクラスオブジェクト
jsで関数はこれでもかってほど幅広い役割を持たされてる上に、節操がないほど柔軟な設計になってます。
関数の果たす役割がみえてくればjsの外観もほぼみえてるんじゃないかって錯覚できるレベル。
純粋に処理を記述するためのプロシージャ/関数という役目に飽きたらず、
インスタンスを生成するコンストラクタであり、ローカルスコープを定義し、
半歩進んでクロージャを生み出したりと、
その魅力で無計画に関数で関数をネストしてしまうJavaScript 農民を生み出す多芸っぷりです。
そんなjsの根幹をなす関数の能力を、柔軟に、節操無く振りかざすために、
jsの関数はファーストクラスオブジェクトという扱い受けています。
ファーストクラスオブジェクトの定義はwikiでも参考にするといいと思います。
うまく説明できる気はしない。
要は、他のオブジェクト(変数とか配列とか)と同じように変数に入れたり、配列に入れたり、
あまつさえ戻り値にしたり関数の引数として渡すことだってできるよっ!てことかと。
と言いつつwikiみて、
"それ自体が独自に存在できる(名前とは独立している)。"とか大事そうなこと書いてますね。
あぁ、確かに!
// 関数の節操のないファーストオブジェクトっぷり
var test = function(){}; // 変数へ代入
var testObj = {test:function(){}}; // オブジェクトのメンバに代入
var testArr = [function(){}]; // 配列のメンバに代入
test // functionオブジェクトが格納されてる
testObj["test"] // functionオブジェクトが格納されてる
testArr[0] // functionオブジェクトが格納されてる
// 関数の戻り値にしたり
function test(){
return function(){}; // testを実行するとここで定義した無名関数がかえってくる手はず
}
test(); // functionオブジェクトが実行結果として返ってくる
// 関数の引数に渡したり
function test(fn){
return fn; // 引数に渡したものが返るだけの関数
}
test(function(){}) // ゆきずりに定義して渡したfunctionオブジェクトが返ってくる
// 入れたものと出てきたものを比較
var fn = function(){}
fn === test(fn) // 演算子で関数オブジェクト同士、比較もできる
JavaScripはクラスのないオブジェクト指向言語
jsはオブジェクト指向言語ですがクラスが存在しない。
Q.「え、じゃぁ、クラスどうやって定義すんの。」
A.「できません。クラスないから」
一般的なクラスベースは非実体(クラス)から実体(インスタンス)化しますが、
jsは実体(関数オブジェクト)から実体(オブジェクト)を生成します。
これがプロトタイプベース。
オブジェクト生成に使われる/使われた関数は、コンストラクタと呼ばれる。
jsのすべての関数はnew宣言すればコンストラクタになれるものの、
コンストラクタ用に書かないとあんまり意味ないよ。
スコープ範囲は関数宣言で切られる
スコープは関数で定義され、関数の内側から外側を参照することはできるが、
外側から内側を参照することはできない。
オブジェクトメンバは外部から参照可能なので、
カプセル化が必要ならその範囲で関数定義するなり、即時実行関数でスコープを切る必要がある。
あと、ローカルスコープに存在しない変数をvar付けずに宣言すると、
無慈悲にグローバルスコープに入っちゃうから注意。粗末なものはパンツの中に。
関数の呼び出しパターン
それぞれのパターンでレシーバ(thisが参照するオブジェクト)が異なる。
1.関数として呼び出し
function();
レシーバはグローバルオブジェクト。
ブラウザであればwindow、strictだったらundefined等など実行環境に依存する。
2.メソッド呼び出し
obj.function();
レシーバは呼び出したオブジェクトになる。
上の場合はobjになる。
1.の関数呼び出しも暗黙的にグローバルオブジェクトが参照されて
window.function(); // 仮にwindowをグローバルオブジェクトとする
で基本は一緒。
3.コンストラクタ呼び出し(オブジェクト生成)
ins = new function();
レシーバは新たに生成された名も無きオブジェクトになる。最終的にinsに収まる。
4.その他(apply,call,bind)関数から呼び出す
任意のレシーバを指定して実行できる。
継承とか名前空間の探索
前述のとおり、jsオブジェクトは関数オブジェクトからnewされる。
newされたオブジェクトは自分が誰に作られたかを覚えていて、その誰かをプロトタイプと呼ぶ。
jsオブジェクトでレシーバを指定せずプロパティやメソッドを参照すると、
下記の流れで探索を行う。
1.自分がもっているか?(ローカルスコープに存在するか)
2.プロトタイプのprototypeプロパティに存在しているか?
3.プロトタイプのプロトタイプのprototypeプロパティに存在しているか?
4.プロトタイプのプロトタイプのプロトタイプの(以下、再帰探索
5.最終的にグローバルスコープに存在するか?
6.ないじゃん!リファレンスエラー返す
2,3,4のように暗黙的にプロトタイプを探索する作用をプロトタイプチェーンと呼ぶよ。
一般的なクラスベースではコンパイル時にクラスが静的に定義されるので、
動的なクラスメソッドの変更はできない。
jsではコンストラクタのporototypeプロパティをいじれば、子オブジェクトメンバの一括変更ができる。
例えば、jsの関数はFunctionコンストラクタをプロトタイプに持つので、
Functionのprototypeをいじくれば全ての関数にメソッド追加したりできる。
でもやっちゃダメ。
関数の名前
関数には名前をつけることができます。
名前は関数を定義したスコープに格納されてあとから関数オブジェクトを参照できます。
function test(){ // testという名前の関数が定義される。
// 処理
}
test.name // "test"
// 関数名は現在のスコープに束縛されて関数名で定義した関数オブジェクトを参照できる
// 関数オブジェクトのnameプロパティにtestが入ってる
名前はオプションなので付けなくてもよかったり。
あ、これファーストクラスオブジェクト特性のあれや。
(function(){ // nameオプションないとsyntax errorでちゃうからカッコで括っとく
// 処理
}).name // ""
// 無名関数オブジェクトができてnameプロパティには""が入ってる
// 無名関数は名前で参照できないのでそのままnameプロパティ参照してる
でも、名無しでヤサ無しは困るので無名関数は変数のヤサに詰めとく。
var test = function(){ // 無名関数定義して変数testへ格納
// 処理
}
test.name // ""
// nameプロパティは""だけどtestで参照可能
再帰処理とかで関数内から自分自身参照したいとき。
var fnTest = function test(){
test // nameプロパティつけてれば参照できる
arguments.callee // 無名関数でもcalleeでも参照できる
fnTest // いちおう参照可能であるものの、fnTestが上書きされると参照できなくなる
}
// 変数に格納すると関数名では参照できないっぽい?
test() // ReferenceErrorでちゃう
即時関数式で関数を使い捨てる
関数は(関数定義)()と書くと定義してその場で実行される。
関数を使いまわさず結果だけ欲しい場合や、ローカルスコープの作成に使われる。
var fnc = function(){ return "hello" };
var str = (function(){ return "hello"})();
fnc // functionオブジェクトが格納されている
str // hello 関数の定義と評価が行われ、戻り値の文字列"hello"が格納されている
var str = fnc() // fncの戻り値がstrに格納され、即時関数式での定義と同等
スコープとかクロージャとか書いてないけど取りあえずこのくらいで。