どうも。つじけ(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モードで効く主な厳格
- 暗黙的なグローバル変数の禁止
- 代入不可なプロパティへの代入の禁止
- 削除できないプロパティの削除の禁止
- 関数の引数名の重複の禁止
- 幾つかの識別子は予約語にするため使用禁止(staticとか)
- 8進数表記の禁止
- eval 変数、arguments 変数の宣言禁止
- with 禁止
- ブロックスコープ内の関数定義 ← 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回目の輪読会は、ここまでです。
次回輪読会をお楽しみに。
このシリーズの目次
- [EffectiveJavaScript輪読会2]グローバルオブジェクトを使うのは、最小限にとどめる
- [EffectiveJavaScript輪読会2]ローカル宣言は、必ず宣言しよう
- [EffectiveJavaScript輪読会2]クロージャと仲良くしよう
- [EffectiveJavaScript輪読会2]変数の巻き上げ(ホイスティング)を理解する
- [EffectiveJavaScript輪読会2]ローカルスコープを作るには即時関数式(IIFE)を使おう
- [EffectiveJavaScript輪読会2]名前付き関数式のスコープは可搬性がないので注意しよう
- [EffectiveJavaScript輪読会2]ブロックローカルな関数宣言のスコープも可搬性がないので注意しよう