Tips

変数のletとvarの違いを知ると、スコープのことも理解できる件【JavaScript】

  • このエントリーをはてなブックマークに追加

「スコープは習ったけど本質的に理解できていない」
「自作した関数や繰り返し処理がなぜかエラーになることがある」
「変数でletとvarの違いが分からないけど、とりあえず使って何とか動作している」

本日はスコープについて取り上げていきます。

プログラミングを学習し始めて序盤の方に登場する「スコープ」について理解して進めた駆け出しエンジニアの方はどれくらいいるでしょうか?

ぶっちゃけ最初の段階ではそこまで真剣に理解しなくても大丈夫なんですが、実務に入ってエラーに遭遇した時に原因がスコープであることがあります。

今回はスコープの基本を初学者でも理解しやすいJavaScriptを使って解説していきます。

また動画もあるので必要に応じて活用してください。

https://youtu.be/LyBkKn68_uc

JavaScriptにおけるスコープの基本

スコープというのは簡単にいうと「変数や定数の書き方」のことです。

まずスコープには大きく分けて2種類あることから知っておきましょう。

最初のスコープは「グローバルスコープ」と呼ばれるもので、以下のように書くことを指します。

const num = 10;
console.log("ただのコンソール:", num);
if (true) {
  console.log("ifのコンソール:", num);
}

定数numに10を代入しておきコンソールに表示するだけの簡単なコードです。

コンソールログを使ってnumを2箇所に渡って呼び出しているのが分かります。

グローバルスコープとは上記のコードのように、関数や条件式の外で設定されるものだと覚えてもらえれば大丈夫です。

もう一つのスコープは「ブロックスコープ」と呼ばれるもので、以下のように書くことを指します。

if (true) {
  const num = 10;
  console.log("ifのコンソール:", num);
}
console.log("ただのコンソール:", num);

コードの仕様については先ほどの例とほとんど変わりません。

こちらも定数numに10を代入してコンソールログで表示するだけですが、最終行に書いたコンソールログのみエラーになっているのが分かります。

これがブロックスコープの特徴で、言葉の通り限られたエリアでしか変数や定数を使い回せないことになります。

もう少し具体的に言うと、関数や条件式の中で設定した変数、定数は外のエリアに出ると使い回すことが出来なくなるんです。

イマイチ分かりづらいと感じている初学者の方は、「{ }で囲われた変数や定数は{ }の外で使えない」と言う覚え方でも良いでしょう。

ちなみに{ }が入れ子になっている場合はどうなるでしょうか?

function test() {
  if (true) {
    const num = 100;
  }
  console.log(num);
}
test();

functionの中で完結していますが、結局はif文の中で作った定数numをif文の外で使おうとしたのでブロックスコープのエラーになりました。

こちらはイメージしやすいと思います、一方で以下のような場合も見てみましょう。

function test() {
  const num = 100;
  if (true) {
    console.log(num);
  }
}
test();

今度はfunctionの中ですがif文の外で定数numを設定しました。

その状態でif文の中で定数numを使用すると問題なく使用できています。

こちらの方がイメージしにくいかもしれませんが、function側から見た時にif文は「{ }の中」ということになるためです。

ブロックスコープは「{ }の外では使えない」と言いましたが、上記のようなケースだと問題なく使用できます。

似たようなコードでもスコープという意味では別物です、この違いを持ってスコープの動作がさらに理解できるかと思います。

今回はJavaScriptを例題にしていますが、他の言語でも大体はグローバルスコープとブロックスコープの2種類の分け方になっています。

ここまで読んだ方の中で「グローバルスコープとブロックスコープのどちらが良いの?」と思われた方がいるかもしれません。

結論、どちらも使います。

とはいえイメージしてもらうとするならば、ブロックスコープは限られたエリアなので「他の場所で同じ変数、定数を使い回さないとき」に使うことが好ましいでしょう。

例えば特定の関数でしか使わない変数はブロックスコープで設定して、「ユーザー名」「本日の日付」「APIのURL」など、同じファイルの中で何回も使うような変数はグローバルスコープで設定する感じです。

この辺りは経験しながらコツを掴むことが多いので、初学者の方は2種類の違いを頭に叩き込むことに専念してもらえればと思います。

モジュールについて

スコープとは少しニュアンスが違いますが同じような考え方をするモジュールというものがありますので一緒に紹介しておきます。

モジュールは複数のJSファイル同士でデータをバケツリレーのように引き継ぐことを言います。

例えば以下のようなHTMLファイルとJSファイルがあったとします。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./style.css" />
    <script src="./script.js" defer></script>
  </head>
  <body></body>
</html>
console.log("テスト");

index.htmlにscript.jsを読み込んでいてscript.jsの中はコンソールログを出力する内容になっています。

この状態から追加で別のJSファイルをmain.jsと言う名前で作ったとします。

const num = 100;
const value = 200;

main.jsにある定数をコンソールログで表示するのにscript.js側で以下のように書いてもエラーになります。

理由としてはindex.htmlはscript.jsを読み込んでいるだけでmain.jsは読み込んでいないので、main.jsの中で宣言されたものをscript.jsに呼ぼうとしても見当たらないからです。

スコープとは厳密には違いますが考え方は似ていますね。

console.log("テスト");
// ここから追加
console.log(num); // script.jsでは宣言されていない
console.log(value); // script.jsでは宣言されていない

上記のコードをエラーなしで実行するためにモジュールというものがあります。

まずHTMLファイルであるindex.htmlに専用の属性を追加します。

JSファイルを読み込むためのscriptタグの属性に「type=”module”」というものです。

これを追加することでモジュールを取り入れることができます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./style.css" />
   <!-- scriptタグにtype="module"を追加 -->
    <script src="./script.js" defer type="module"></script>
  </head>
  <body></body>
</html>

続いてエラーのもとになっていたmain.jsについて引き継ぎしたいデータの先頭に「export」を付けます。

今回は違いをわかりやすくするために2つあるうちの片方だけに「export」を付けてみましょう。

// 定数numを引き継ぐようにする
export const num = 100;
const value = 200;

最後にscript.jsではimport文というものを書くことでmain.jsからexportしたデータを受け取ることができます。

console.log("テスト");
// ここを追加
import { num } from "./main.js";
console.log(num);
console.log(value);

fromの後にはmain.jsの名前ではなくパスを書くので注意してください。

main.jsで宣言していたデータのうちexportしてscript.jsでimportした「num = 100」だけがコンソールに表示できました。

valueについては引き続きエラーになったままですね。

このモジュールという考え方は主にReactなどモダンJSでは必須の知識になるので初学者の方も覚えておくようにしましょう。

またモジュールについては別の記事でも解説しているので詳しく知りたい方はどうぞ。

JavaScriptにおけるletとvarの違い

そもそもJavaScriptでは変数宣言にletとvarの2種類あることに何の違いがあるのかわからない方もいるかと思います。

経緯としては最初はvarのみだったのですが、これから紹介するような課題があるのでletが後から追加されて大半の人がletを使うようになったのです。

letとvarの違いは大きく3点です。

書き順におけるletとvarの違い

例えば初心者ならではのミスとして書く順番が違うため正常に実行されないときがあります。

以下のようなコードは正常に動作します。

var num1 = 100;
console.log(num1);

let num2 = 200;
console.log(num2);

例えば変数の宣言とコンソールログへの出力を間違って、以下のように逆順に書いてしまったとします。

console.log(num1);
var num1 = 100;

console.log(num2);
let num2 = 200;

varの方はundefinedになって、letの方はエラーになりましたね。

両方とも意図した動作になっていない点は同じですが結果に出力されるものが違います。

varの方はundefinedなので後続の処理が実行できることがあるわけですが、letの方はエラーなので後続にあるものは実行されません。

今回のようなミスは基本的には修正が必要なのでエラーにして止まってくれた方が安全と考えるのがプログラマーの考え方です。

「両方とも同じ」とは言わず、「letの方が安全性が高い」という解釈に切り替えることをお勧めします。

再宣言したときのletとvarの違い

続いては再宣言における違いです。

こちらも初心者ならではのミスとして、すでに宣言した変数と同じ名前を別の変数宣言にも使ってしまうパターンです。

var num3 = 300;
var num3 = 302;
console.log(num3);

let num4 = 400;
let num4 = 402;
console.log(num4);

varについてはエラーにならずletの場合にのみエラーになります。

例えばvarについて再宣言と再代入を両方やってみましょう。

var num3 = 300;
console.log(num3);

// これは再代入
num3 = 301;
console.log(num3);

// これは再宣言
var num3 = 302;
console.log(num3);

varについては再宣言も再代入と同じ動作になっているわけですね。

初心者の方は「これは便利」と思われるかもしれませんが、再宣言と再代入は別のものであるはずなので実務ではやってはいけないものです。

ブロックスコープでのletとvarの違い

条件分岐のなかで変数を書くときのletとvarの違いがあり、こちらは前段でお話したスコープが関係しています。

例えばletを使って以下のようなコードを書いたとします。

if (true) {
  let num = 10;
  console.log("ifのコンソール:", num);
}
console.log("ただのコンソール:", num);

先ほどと同じようなコードですね。

スコープの種類としてはif文の中で変数を設定しているので「ブロックスコープ」になります。

そのため最終行にあるような、if文の外で変数numを使おうとするとエラーになります。

それでは上記のコードをvarに変えてみます。

if (true) {
  var num = 10;
  console.log("ifのコンソール:", num);
}
console.log("ただのコンソール:", num);

するとなんとブロックスコープで変数numを設定しているはずなんですが、if文の外で書いたコンソールログも実行できてしまっています。

これがletとvarの違いです。

letは2種類のスコープの違いを使い分けるのに対して、varで書いた変数は「ブロックスコープとして動作する」という特徴があります。

つまりvarで書いた変数はどこで設定しようが、色んな場所で自由に使い回せるということですね。

一見するとvarの方が自由が効いて便利そうですが、実務においてはletを使うようにしましょう。

varは自由であるからこそ、「コードの間違いに気付きにくい」「予期せぬエラーを誘導する」ような危険性があるとも言い換えることが出来ます。

サンプルコードをググっていると意外とvarで書かれた記事はそれなりに見かけると思います。

遊びや初歩の勉強の間だけは良いとしても実務ではletのみで書くようにしましょう。

こんな感じでスコープはいきなり覚えようとしても中々難しいものですが、小さくてもコードを書いている内にスコープの大事さを実感するので初学者の方も焦らず覚えてください。

また今回参考にした本はこちらになります。

良ければどうぞ。

  • このエントリーをはてなブックマークに追加