どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「ローカルスコープを作るには即時関数式(IIFE)を使おう」をお届けしました。
今回は、「名前付き関数式のスコープは可搬性がないので注意しよう」 をお届けします。
テキスト第2章「変数のスコープ」の項目14に対応しています。
今日のアジェンダ
- 関数名の結合
- 関数式による結合
- 再帰関数
関数名の結合
名前束縛(結合)についておさらいです。今回は、関数についてです。
JavaScriptはレキシカルスコープを採用しています。
ソースコードを実行する前に、読み込まれた時点で(エディタを保存したときに解釈されることです)、名前束縛(結合)を行います。
このようなコードを書いて保存したとき、関数bindingは関数スコープを生成します(厳密にはcallオブジェクトを生成します)。
そして、同じスクリプトファイル内で、識別子bindingがないか、探しにいきます。
そして、識別子bindingを見つけたら、お互いで呼ぶ呼ばれる関係になれるように、関数名の対応付け をします。
これが、関数名の結合です。名前の「結合」が「代入」ではないことが理解できると思います。
function myFunction2_14_01() {
binding();
function binding() {
console.log('結合先を探します');
}
binding();
}
関数には、関数定義と関数式(関数リテラル)の2つの書き方がありました。
関数定義による関数名の結合は、上記で説明しましたが、関数式による関数名の結合は少し違います。
関数式は変数に代入されますので、変数名による結合 が行われます。
関数式には、名前あり関数式と、無名関数の2種類がありますが、実務では、最後のアロー関数方式で書くことが多いです。
function myFunction2_14_02() {
//関数宣言
function double(x) { return x * 2; }
//関数式
const f = function double(x) { return x * 2; };
//関数リテラル
const func = function (x) { return x * 2; };
//関数リテラル(アロー関数)
const getDouble = x => { return x * 2; };
double();
f();
func();
getDouble();
}
関数式による結合
これまで、関数式による記述は、後で呼び出すことがほとんどないから、無名関数(アロー関数)にしてしまいましょう 、としていました。
しかし、名前あり関数式と無名関数には、結合に違いがあります。
名前あり関数式は、その関数名が、関数内のローカル変数に結合されます。
無名関数のばあいは、thisキーワードを使って、Functionオブジェクトにアクセスできますので、このような書き方もできます。
function myFunction2_14_03() {
const f = function double(x) {
return double.name;
};
const getDouble = function (x) {
return this.name;
};
console.log(f); //[Function: double]
console.log(getDouble); //[Function: getDouble]
}
再帰関数
これは、再帰関数に応用できます。
下記は、引数を渡すと、引数から1ずつ減らして、加算していく関数です。
関数の中に関数(自分自身)の呼び出し が書かれています。
再帰関数のポイントは、必ずループが終わる処理を書かなければなりません。
function myFunction2_14_04() {
const sum = function getSum(n) {
if (n <= 0) { return n; }
return n + getSum(n - 1);
}
const n = 10;
const total = sum(n);
console.log(`1から${n}まで加算すると${total}です`); //1から10まで加算すると55です
}
再帰関数のために、名前付き関数を使う必要はありません。
外側のスコープを呼び出してもいいからです。
function myFunction2_14_05() {
const sum = n => {
if (n <= 0) { return n; }
return n + sum(n - 1);
}
const n = 10;
const total = sum(n);
console.log(`1から${n}まで加算すると${total}です`); //1から10まで加算すると55です
}
テキストの後半は、ほぼ古いJavaScriptの仕様についての解説のようでしたので、読み飛ばしました。
デバッグのさいに、メモリのスタックをトレースする必要に遭遇したさいは、再度考察してみたいと思います。
まとめ
以上で、「名前付き関数式のスコープは可搬性がないので注意しよう」をお届けしました。
ES6以降では、関数式による名前付き関数は、ほぼ使わないかもしれません。
次回は、「ブロックローカルな関数宣言のスコープも可搬性がないので注意しよう」 をお届けします。
参考資料
このシリーズの目次
- [EffectiveJavaScript輪読会2]グローバルオブジェクトを使うのは、最小限にとどめる
- [EffectiveJavaScript輪読会2]ローカル宣言は、必ず宣言しよう
- [EffectiveJavaScript輪読会2]クロージャと仲良くしよう
- [EffectiveJavaScript輪読会2]変数の巻き上げ(ホイスティング)を理解する
- [EffectiveJavaScript輪読会2]ローカルスコープを作るには即時関数式(IIFE)を使おう
- [EffectiveJavaScript輪読会2]名前付き関数式のスコープは可搬性がないので注意しよう
- [EffectiveJavaScript輪読会2]ブロックローカルな関数宣言のスコープも可搬性がないので注意しよう