[EffectiveJavaScript輪読会2]名前付き関数式のスコープは可搬性がないので注意しよう

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「ローカルスコープを作るには即時関数式(IIFE)を使おう」をお届けしました。

[EffectiveJavaScript輪読会2]ローカルスコープを作るには即時関数式(IIFE)を使おう
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「変数の巻き上げ(ホイステ...

今回は、「名前付き関数式のスコープは可搬性がないので注意しよう」 をお届けします。

テキスト第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以降では、関数式による名前付き関数は、ほぼ使わないかもしれません。

次回は、「ブロックローカルな関数宣言のスコープも可搬性がないので注意しよう」 をお届けします。

参考資料

再帰関数を理解するための最もシンプルな例

このシリーズの目次

タイトルとURLをコピーしました