どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のシリーズでは、第1章に相当する考察をお届けしました。
引き続き、第2章をお送りします。
目次と日程
第2章は「変数のスコープ」です。JavaScriptだけでなく、すべてのプログラミング言語において、避けて通れない概念ですね。
第1章 JavaScriptに慣れ親しむ
第2章 変数のスコープ
第3章 関数の扱い
第4章 オブジェクトとプロトタイプ
第5章 配列とディクショナリ
第6章 ライブラリとAPI設計
第7章 並行処理
LT大会は2021年9月8日です。がんばりましょう。
今日はさっそく1回目で、「グローバルオブジェクトを使うのは、最小限にとどめる」 をお届けします。
テキスト第2章「変数のスコープ」の項目8に対応しています。
今日のアジェンダ
- スコープのおさらい
- globalThis
スコープのおさらい
まず、スコープのおさらいです。
中級のみなさんは、変数宣言によりどのようにスコープが生成されるのか、理解していると思います。
ローカル領域で宣言された変数のスコープ
宣言 | 関数スコープ(function)生成 | ブロックスコープ({})生成 |
---|---|---|
無し | × | × |
var | 〇 | × |
let | 〇 | 〇 |
const | 〇 | 〇 |
関数の仮引数 | 〇 | ― |
グローバル領域で宣言された変数は、グローバル変数(グローバルオブジェクト、もしくはスクリプトオブジェクト)になるため、スコープを生成しません。
よく、「グローバル領域に宣言したグローバル変数は、どこからでも呼び出せるが、書き換えもされる」という解説を見かけます。
あまり見かけないのが、「関数宣言内で変数宣言を付けない変数への代入が、グローバル変数になり、思わぬ結果をもたらす」という例です。
デバッガを見ながら、グローバル変数とローカル変数の違いを理解しましょう。
function myFunction2_8_01() {
flag = true; //varをつけたり外したりして実行しましょう
const total = getFlag();
if (flag) console.log('flagが立ってます');
if (!flag) console.log('flagは降りてます');
}
function getFlag() {
flag = false; //varをつけたり外したりして実行しましょう
}
テキストP32は、とくに変数iとnがグローバル変数になることで破綻することを表しています。
score()関数のfor文のiの前にletを付けたり外したりして、結果を確認してみましょう。
function myFunction2_08_2() {
const players = [
{
name: 'Tom',
levels: [
{ score: 100 },
{ score: 200 },
]
},
{
name: 'Bob',
levels: [
{ score: 300 },
{ score: 400 },
]
},
];
const avrageScore = averageScore(players);
console.log(avrageScore); //300 + 700 / 2 = 500
}
function averageScore(players) {
sum = 0;
for (i = 0, n = players.length; i < n; i++) {
sum += score(players[i]);
}
return sum / n;
}
function score(player) {
sum = 0;
for (i = 0, n = player.levels.length; i < n; i++) { //iの前にletを付けたり外したりする
sum += player.levels[i].score;
}
return sum;
}
globalThis
thisについてもおさらいしておきましょう。
thisはもともと、書かれた場所で、オブジェクト自身を表すキーワードでした。
グローバル領域や関数内でthisを書くと、thisはグローバルオブジェクトになります。
ということは、thisを使って、グローバルオブジェクトに変数をもたせることもできます。
わたしはECMAScript2018から採用された、globalThis でコーディングしていきたいと思います。
実行ログを確認したら、関数内のコードを、グローバルエリアに移動して実行したりしてみましょう。
function myFunction2_8_03(num) {
x = 1
console.log(globalThis.x); //1
var y = 2;
console.log(globalThis.y); //undefined
const z = 3;
console.log(globalThis.z); //undefined
globalThis.l = 4;
console.log(l); //4
}
スコープ表にグローバル変数の挙動を加えたものがこちらです。
宣言 | 関数スコープ(function)生成 | ブロックスコープ({})生成 | グローバル変数の生成 |
---|---|---|---|
無し | × | × | 〇 |
var | 〇 | × | 〇(グローバルエリアで記述) |
let | 〇 | 〇 | △(グローバルエリアで記述) |
const | 〇 | 〇 | △(グローバルエリアで記述) |
関数の仮引数 | 〇 | ― | × |
まとめ
以上で、「グローバルオブジェクトを使うのは、最小限にとどめる」をお届けしました。
実務を意識するなら、getterやsetterとグローバル変数の使いどころになってきますが、これらはクラスの項目で考察したいと思います。
次回は、「ローカル宣言は、必ず宣言しよう」 をお届けします。
このシリーズの目次
[EffectiveJavaScript輪読会2]グローバルオブジェクトを使うのは、最小限にとどめる
[EffectiveJavaScript輪読会2]ローカル宣言は、必ず宣言しよう
[EffectiveJavaScript輪読会2]クロージャと仲良くしよう
[EffectiveJavaScript輪読会2]変数の巻き上げ(ホイスティング)を理解する
[EffectiveJavaScript輪読会2]ローカルスコープを作るには即時関数式(IIFE)を使おう