JavaScriptの関数ってなんでしょうね…。
(この記事の実行環境はNode.jsです)
#関数を作る方法
関数を作る方法は主に3つある。
##1. function文
function
というキーワードで関数を作る方法その1。文であることに注意。
function hoge() {
console.log('call hoge');
}
hoge();
fuga(); //fugaは既に作られているので、呼び出せる。
function fuga() {
console.log('call fuga');
}
function 名前(引数1, 2...) {本体}
という形式になる。
特徴は例にも書いたとおり、function文で作る関数は本文の 実行に先駆けて作られる ので、その行よりあとにfunction文があってもその関数を呼び出すことができる。
最初にメインロジックを書いてサブロジックをあとから付き足していく時とかは便利かも。
(仕様として説明すると結構面倒なので割愛するけれど、このfunction文での関数が作られるのはそのスコープが実行されるときの前になる。つまり、関数の中でfunction文を使って関数を作った場合、その関数が呼び出された時になる)
##2. function式
function
というキーワードで関数を作る方法その2。文ではなく式。
var
hoge = function () { //いわゆる無名関数とか匿名関数とか。
console.log('call hoge');
};
hoge();
try {
fuga(); //この時点ではfugaはundefinedなので例外を吐く。
} catch (e) { console.log(e); }
var
fuga = function fuga() { //関数に名前をつけることができる。また、ここで付けた名前はFunction#nameにしか影響しない。
console.log('call fuga');
};
(function () {
//スコープを作るために使うものもfunction式。
})();
//関数に渡すことも出来る。
'hoge fuga'.forEach(function (name) {
this[name]();
}, this);
function (引数1, 2...) {本体}
もしくはfunction 名前(引数1, 2...) {本体}
という形式になる。
特徴は式なので、変数にそのままぶち込んだり関数に渡したりその場で呼び出したりとやりたい放題できる。JavaScriptが関数型言語といわれる所以でもある。
また、function式にもfunction文同様名前を付けられるけど、これは外側のスコープからこの名前で呼び出せるようになるわけではなく、単にその関数のnameプロパティが設定されるだけ(関数の内側からは参照できる)。じゃあ何の役に立つのかというと、スタックトレースが親切になる。コールバックにfunction式を多用する場合、スタックトレースが<anonymous>
で埋められて絶望的な感じになるのを回避できる。
余談だけど、function文とfunction式を構文上どうやって識別しているのかというと、
- 文の先頭に
function
がある場合はfunction文 - そうでなければfunction式
という風にしている。で、文はそれ単体で完結しているので後に呼び出しの括弧をつけることが出来ない。
要するに、ツイートボタンなんかのスクリプトが!function
から始まっているのは、function文と認識させて閉じたスコープの中で実行するため、ということになる。
##3.Function組み込み関数
JavaScriptの闇。eval
以外で文字列からJavaScriptを実行できる方法の一つ。
var
hoge = new Function('console.log("call hoge");'), //コンストラクタとして呼び出す。
fuga = Function('console.log("call fuga");'); //関数として呼び出してもいい。
hoge();
fuga();
var
fib = Function('i', 'return i <= 1 ? 1 : fib(i - 1) + fib(i - 2);'), //引数を取る場合はこうする。
pow = Function('n, m', 'return m === 0 ? 1 : m < 0 ? pow(n, m + 1) / n : pow(n, m - 1) * n;'), //引数が複数ある場合はこうする。
map = Function('f', 'xs', 'return xs.length === 0 ? [] : [f(xs[0])].concat(map(f, xs.slice(1)));'); //こうでもいい。
//関数に渡せるけど…
console.log(map(Function('x', 'return pow(fib(x), x);'), [1, 2, 3]));
なんて言うか、不恰好です…。シンタックスハイライトもされませんし。
しかも、実行時に文字列から関数を作りだすので、速度がネックになります。百害あって一利なしという感じです。
ならどこで使われるのかというと、文字列のJSONを読んだり(eval
よりは安全?)後述する方法でグローバルオブジェクトを取得するのに使ったりします。
ちなみに、このFunction
で作った関数はfunction式やfunction文のようにスコープを束縛せず(ちょっと違う気がする)グローバル直下の環境で実行します。
なので、例え'use strict';
されていてもthis
がundefined
にならずグローバルオブジェクトを取得できます。(なんか表現がおかしい)
(function () {
'use strict';
console.log(typeof this); //=> 'undefined'
console.log(typeof Function('return this;')()); //=> 'object'
})();
##番外編, ArrowFunction
みんな大好きArrowFunction。今のところ使えるのはFirefox 25 nightlyだけのはず。
これで作られるのは厳密にはFunctionでは無いのですけど…。
var
hoge = () => console.log('call arrow hoge'),
fuga = () => console.log('call arrow fuga');
hoge();
fuga();
まともに説明すると本題から外れすぎてしまうので省略します。興味のある方は調べてみてください。
(自分はstrawman:block_lambda_revival [ES Wiki]の方が好きなのですけどそれもまた別のお話)
##で、結局どれを使えばいいの?
Function組み込み関数は論外だとして、function文とfunction式。
function文の先駆けて作られるっていう性質はかなりいいと思うんだけど、知らない人からしたらややこしいだけでもあるし、やっぱり一番素直なfunction式を使えばいいと思う。なんか'use strict 2';
でfunction文を廃止してfunction式に統一しよ―ぜ、と主張してる人も居た事だし。
あ、でも関数に名前は出来る限り付けたほうがいいです。なぜって、デバッグがかなり楽になりますから。
##最後に
あとになってからもっと書きたいことが出てきそうだから、大幅に書き換えることがあるかもしれない。
それではっ!