👏

初心者によるJavaScript講座2スコープについて「スコープの重要性とグローバルスコープ」

に公開

こんにちは。ブログ主です。今回はJavaScriptのスコープについてChatGPTと会話したり、jsprimerを読んだりしてわかったことがあったのでまとめます。

概要

JavaScriptでプログラミングする上で避けては通れない「スコープ」の概念について実例を交えながら軽く説明します。内容については目次を活用し、欲しい内容をさらってください。この記事では「そもそもスコープとは」や「スコープの重要性」や「グローバルスコープ」について説明します。以下常体。

そもそもスコープとは?

変数や関数にアクセスできる範囲(有効範囲)こと。scopeという英語の意味は「範囲」という意味からもわかる。簡単に言えばどこからその変数を使えるのか決めるルールことだ。

JavaScriptにおけるスコープを理解することの重要性

大きく分けてバグの予防とコードの見通しの向上があげられる。

スコープを理解していないと起きる問題

理解していない例1

var result = 10;

function calculate() {
  var result = 0;  // 実は別のスコープのresult
  // 実はこのresultは外のresultとは別物
  for (var i = 0; i < 5; i++) {
    result += i;
  }
  return result;
}

calculate();
console.log(result); // 10(予想外?)

理解していれば

  • 関数内のresult は外のresultを上書きしていないとわかる。
  • もし外の値を変更したいなら、グローバル変数の再利用を避ける設計にすべきと判断できる。

理解していない例2

var count = 0;

function increment() {
  count += 1;
}

function reset() {
  var count = 0;
  // increment() を使うと意図しない挙動に
}

increment();
reset();
increment();
console.log(count); // 2になると思ったら1

理解していれば

  • reset 関数内の var count = 0 はローカル変数の定義。
  • increment関数はグローバルのcountを変更している。
  • resetはグローバルの count に影響していないため、想定と異なる結果に。

理解していない例3

function createUser(name) {
  const id = generateId(); // 他の場所からアクセスできない
  return { id, name };
}

console.log(id); // ReferenceError: id is not defined\
  • 変数idは関数内でしか使えないので、安全・安心。
  • 外部から誤って変更されるリスクがない。
  • スコープを意識したコード設計は、保守性が高く、バグを防ぎやすい

グローバルスコープとは

グローバルスコープとは簡単に言えば、最も外側にあるスコープのこと。英語ではGlobal scopeと書き、scopeは前述の通り範囲Global世界的なという意味。

グローバルスコープの特徴

  • プログラム全体からアクセス可能であること
  • 大規模なプロジェクトや外部ライブラリを使用する場合は名前の衝突が起きやすいこと
  • スクリプトが終了するまで存続すること

グローバルスコープの種類

  • グローバル変数
  • ビルドインオブジェクト
    の二種類がある。

グローバル変数とは

グローバルスコープで定義した変数のこと。あらゆるスコープから参照できる変数となる。

グローバル変数の宣言方法

1,スクリプトの最上位レベルでの宣言

// グローバルスコープでの変数宣言
const globalConst = "これはグローバル定数です";
let globalVar = "これはグローバル変数です";
var anotherGlobalVar = "これも同様にグローバル変数です";

function someFunction() {
  console.log(globalConst); // グローバル変数にアクセス可能
  console.log(globalVar);   // グローバル変数にアクセス可能
}

2,意図しないグローバル変数(非推奨)

function createGlobalByAccident() {
  notDeclaredVar = "宣言キーワードを忘れるとグローバル変数になります"; // 暗黙的にグローバル変数として扱われる
}

createGlobalByAccident();
console.log(notDeclaredVar); // "宣言キーワードを忘れるとグローバル変数になります"

※上記の例は、strictモードを使用しない場合の挙動。strictモードでは暗黙的なグローバル変数の作成はエラーになる。

ビルドインオブジェクトとは

プログラム実行時に自動的に定義されるもの。ビルドインオブジェクトには、大きく分けて二種類のものがある。

  • ECMAScriptが定義するundefineのような変数やisNaNのような関数、ArrayRegExpなどのコンストラクタ関数
  • 実行環境(ブラウザ、Node.jsなど)が定義するオブジェクト

グローバル変数とビルドインオブジェクトのどちらが優先されるか

自ら定義したグローバル変数とビルドインオブジェクトでは、グローバル変数が優先して参照される。つまり、次のようにビルドインオブジェクトと同じ名前の変数を定義すると、定義した変数が参照される。

// "isNaN"という名前の変数を定義
const isNaN = 1;
// 自分で定義した変数がビルトインオブジェクトより優先される
console.log(isNaN); // => 1

ビルドインオブジェクトと同じ名前の変数を定義したことにより、ビルドインオブジェクトを参照できなくなる。 このように内側のスコープで外側のスコープと同じ名前の変数を定義することで、外側の変数が参照できなくなることを変数の隠蔽(shadowing)という。
この問題を回避するためには、むやみやたらにグローバル変数を定義しないことが良い方法としてある。なぜなら関数のスコープであれば影響範囲がそのスコープの中でとどまるからだ。
ビルドインオブジェクトと同じ名前を避けることは難しい。なぜならビルドインオブジェクトには実行環境が独自に定義したものが多く存在するからだ。
参考

https://jsprimer.net/basic/function-scope/

次の記事は更新しだいリンクをここに貼る予定。

Discussion