[EffectiveJavaScript輪読会2]ブロックローカルな関数宣言のスコープも可搬性がないので注意しよう

GAS

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

前回のおさらい

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

[EffectiveJavaScript輪読会2]名前付き関数式のスコープは可搬性がないので注意しよう
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「ローカルスコープを作るに...

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

テキスト第2章「変数のスコープ」の項目15に対応しています。

今日のアジェンダ

  • ブロックスコープ内の関数定義
  • ECMAScriptの見解

ブロックスコープ内の関数定義

「ブロックローカルな関数宣言のスコープは可搬性がない」というのは、if文などのブロックスコープ内に書かれた関数宣言などは、どこに移動してもいいわけではない、という意味です。

このようなコードを実行してみましょう。関数fは実行されませんので、TypeErrorが発生します。

これは、ソースコードが解釈されたときには結合されず、if文の条件式の結果によって、結合が決まるということです。

function myFunction2_15_01() {

  if (false) {
    function f() { return 'local'; }
  }

  console.log(f()); //TypeError: f is not a function

}

興味深いことは、strictモードでは、ブロックスコープ内の関数定義を認めていないことです。

function myFunction2_15_02() {
 'use strict'

  if (true) {
    function f() { return 'local'; }
  }

  console.log(f()); //TypeError: f is not a function

}

項目1でお伝えしたリストには入っていなかったので、追加しておきましょう。

strictモードで効く主な厳格

  1. 暗黙的なグローバル変数の禁止
  2. 代入不可なプロパティへの代入の禁止
  3. 削除できないプロパティの削除の禁止
  4. 関数の引数名の重複の禁止
  5. 幾つかの識別子は予約語にするため使用禁止(staticとか)
  6. 8進数表記の禁止
  7. eval 変数、arguments 変数の宣言禁止
  8. with 禁止
  9. ブロックスコープ内の関数定義 ← New

ECMAScriptの見解

テキストの写経です。このコードは、関数testに引数で真偽値を渡すと、配列に要素を追加したり、しなかったりします。

trueを渡したときは、2回pushしているのがわかります。

問題は、その要素はどこから引っ張ってくるのか、という点です。

JavaScriptでは、一番近くのスコープで名前結合されますので、「’内側の関数スコープ’」が2回呼び出されています。

function myFunction2_15_03() {

  function f() { return '一番外側の関数スコープです'; }

  function test(x) {

    function f() { return '内側の関数スコープ'; }

    const result = [];
    if (x) {
      result.push(f());
    }

    result.push(f());
    return result;
  }

  console.log(test(true)); //	[ '内側の関数スコープ', '内側の関数スコープ' ]
  console.log(test(false)); //[ '内側の関数スコープ' ]

}

そして、この項目のテーマ 「ブロックスコープ内に書かれた関数宣言などは、どこに移動してもいいわけではない」 を検証します。

まず、内側の関数スコープをコメントアウトします。

必然と、外側の関数スコープを呼び出します。

function myFunction2_15_04() {

  function f() { return '一番外側の関数スコープです'; }

  function test(x) {

    //   function f() { return '内側の関数スコープ'; }
    
    const result = [];
    if (x) {
      result.push(f());
    }

    result.push(f());
    return result;
  }

  console.log(test(true)); //	[ '一番外側の関数スコープです', '一番外側の関数スコープです' ]
  console.log(test(false)); //[ '一番外側の関数スコープです' ]

}

内側の関数fを、if文の中に移動してみましょう。

trueは今まで通りですが、falseのときにTypeErrorが発生してしまいます。

falseのときは、[ ‘一番外側の関数スコープです’ ]になることを期待しますが、外側の関数スコープを呼びだしません。

function myFunction2_15_05() {

  function f() { return '一番外側の関数スコープです'; }

  function test(x) {

    const result = [];
    if (x) {
      function f() { return '内側の関数スコープ'; }
      result.push(f());
    }

    result.push(f());
    return result;
  }

  console.log(test(true)); //	[ '内側の関数スコープ', '内側の関数スコープ' ]
  console.log(test(false)); //TypeError: f is not a function

}

これはなぜなのかと言うと、ECMAScript(JavaScript公式)も見解を示していないようです。(そんなこともあるのか。。。)

公式の見解としては、

  • 関数定義は、最も外側のスコープに定義しなさい
  • ブロックスコープの中(とくに条件的な構文内)に定義したりしたら、エラーを出すこともあるからな
  • use strictモードだったら、一発でエラー出すからな

ということらしいです。激おこですね。(言い方次第)

まとめ

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

第2回目の輪読会は、ここまでです。

次回輪読会をお楽しみに。

このシリーズの目次

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