【STEP.05】
変数の使い方をマスターしよう!

変数とは

変数とは、ひとことでいうならば「データの入れ物」です。

スクリプトが、最終的な解答を導くための一連の「データのやりとり」を表すものであるとするならば、「やりとりされる途中経過のデータを一時的に保存する」のが、変数の役割です。

変数は「データの入れ物」

let命令で変数を宣言する

変数を利用するにあたっては、まずlet命令で変数を宣言しておく必要があります。

変数の宣言とは、変数の名前をJavaScriptに登録し、かつ、値を格納するための領域をメモリ上に確保することをいいます。

let命令の構文は次の通りです。

let命令

 let name [= value][, ...];  
 name    変数名
 value   初期値

たとえば、以下はmsgという名前の変数を宣言しています。

let msg;

変数が複数ある場合には、

let x;
let y;

のように、複数のlet命令を記述してもかまいませんし、

let x,y;

のように、複数の変数をカンマ区切りでまとめて宣言することも可能です。

また、宣言時に初期値を設定することもできます。

let msg = 'Hello、JavaScript!';
let x = 10;

「=」は「右辺の値を左辺の変数に格納しなさい」という意味の命令(演算子)です。

ここでは、変数msgに「Hello、JavaScript!」という文字列を、変数に「10」という整数値をそれぞれセットしているわけです。

値を変数に代入

初期値を設定しなかった場合、JavaScriptはデフォルトで、未定義値(undefined)という特別な値を変数に割り当てます

JavaScriptではもう1つ、変数を宣言するためのvarという命令を利用できます。

構文はletと同じですが、次のように挙動が異なります。

  • 同名の変数を許容(letでは許容しない)
  • ブロックスコープを認識できない(letでは認識できる)
  • 関数の外で変数を宣言した場合にグローバル変数を生成する(letでは生成しない)

以上に性質から、ES2015以降を利用できる環境では、できるだけlet命令を優先して利用することをお勧めします。

次は、このvar命令とlet命令の違いを詳しく見ていきます。

var命令とlet命令の違い

var命令とlet命令の違いは主に次の3つです。

  • 変数の重複を許可するかどうかの違い
  • ブロックスコープを認識するかどうかの違い
  • 関数の外で変数を宣言した場合にグローバル変数を生成するかどうかの違い

順番に解説していきます。

変数の重複を許可するかどうかの違い

let命令は、同名の変数を許可しません。

よって、以下のコードは「Identifier 'msg' has already been declared」(変数msgは宣言済みです。)のようなエラーが発生します。

let msg = 'Hello、JavaScript!';
let msg = 'こんにちは、JavaScript!';

一方、var命令は重複を許容するので、以下のコードは正しく動作します。

var msg = 'Hello、JavaScript!';
var msg = 'こんにちは、JavaScript!';

ブロックスコープを認識するかどうかの違い

変数の有効範囲のことをスコープといいます。

let命令で宣言された変数はブロックの中でのみ有効です。

このようなスコープのことをブロックスコープといいます。

具体例を見てみましょう。

let scope = 'Global';
		
function show () {
  let scope = 'Local';
  return scope;
}
console.log(show());
// 結果:Local
console.log(scope);
// 結果:Global

この例であれば、show関数で定義された変数scopelet scope = 'Local';)は関数ブロックのなかでのみ有効ですし、「let scope = 'Global';」の変数scopeはスクリプト全体で有効となります。

変数は、同名であってもスコープが異なれば別物である点にも注意が必要です。

よって、show関数経由でアクセスした変数scopeは「Local」、関数の外で宣言された変数scopeは「Global」を返します。

ブロックを構成するのは、関数ばかりではありません。

ifwhileなどの制御構文もブロックを構成します。

if (true) {
  let x = 10;
}
console.log(x);
// 結果:x is not defined

たとえば上のような例では、ifブロックの配下で宣言された変数xはそのブロックの中でだけ有効なので、「console.log(x);」は、「x is not defined」というエラーになります。

ブロックの外からは参照できないということです。

これに対してvar命令では、関数という単位でもってスコープが決まります。

そのため以下のコードのletvarに書き替えても同じ意味です。

let scope = 'Global';
		
function show () {
  let scope = 'Local';
  return scope;
}
console.log(show());
// 結果:Local
console.log(scope);
// 結果:Global

しかし、同じく以下のコードのletvarに書き替えた場合には、結果が変化します。

// let命令で宣言した場合
if (true) {
  let x = 10;
}
console.log(x);
// 結果:x is not defined

// var命令で宣言した場合
if (true) {
  var x = 10;	
}
console.log(x);
// 結果:10

var命令では、関数でのみスコープが決まるので(=ifwhileブロックはスコープにならないので)、ブロック内で宣言された変数xは、そのままブロック外からも参照できるということです。

スコープはできるだけ限定するという観点からも、varよりもletを利用すべきです。

関数の外で変数を宣言した場合にグローバル変数を生成するかどうかの違い

関数の外でvar宣言された変数は、グローバル変数を生成します。

グローバル変数とは、スクリプト全体でアクセスできる変数です。

トップレベル(=関数の外)でlet宣言された変数とは決定的に異なる点があります。

グローバル変数は、グローバルオブジェクトのプロパティとなる

点です。

グローバルオブジェクトとは、グローバル変数/関数を管理するための便宜的なオブジェクトで、環境に応じて1つだけ生成されます。

ブラウザー環境であれば、Windowオブジェクトがそれです。

グローバル変数を宣言するとは、内部的にはグローバルオブジェクトのプロパティを宣言するのと同じ意味です。

具体的な例を見てみましょう。

var data1 = 'var variable';
let data2 = 'let variable';
	
console.log(data1);
// 結果:var variable
console.log(window.data1);
// 結果:var variable
console.log(data2);
// 結果:let variable
console.log(window.data2);
// 結果:undefined

var宣言された変数data1は、グローバルオブジェクト経由で参照できるのに対して、let宣言された変数data2はできません。

let宣言された変数は、あくまでもトップレベルの範囲で利用可能な局所変数(ローカル変数)なのです。

変数に名前を付けるときのルール(命名規則)

変数はもちろん、関数やメソッド、ラベル、クラスなどは、すべて互いを識別するために、何らかの名前を持っています。

JavaScriptでは、この名前を比較的自由に決めることができますが、それでも以下の4つのルール(命名規則)は最低限守らなければなりません。

  1. 1文字目はアルファベット、アンダースコア(_)、ドル記号($)のいずれか
  2. 2文字目以降は、1文字目で利用できる文字か、数字のいずれか
  3. 変数名に含まれる英字の大文字/小文字は区別される
  4. JavaScriptで意味を持つ予約語でないこと

(4)のJavaScriptで意味を持つ予約語とは、次に掲げるものをいいます。

【JavaScriptで意味を持つ予約語
はStrictモードの場合だけ)】
breakcasecatchconstcontinuedebugger
defaultdeletedoelseenumexport
extendsfalsefinallyforfunctionif
implementsimportininstanceofinterfacelet
newnullpackageprivateprotectedpublic
returnstaticsuperswitchthisthrow
truetrytypeofvarvoidwhile
withyield
MEMO
ー識別子ー
変数をはじめ、クラスや関数、ラベルなど、コードの中で扱うモノに付けられた名前のことを識別子といいます。

このページで扱う変数の命名規則は、正確には識別子の命名規則です。

また、以下についても、識別子として使用するのは避けるべきです。

  • 将来的に予約語として採用される可能性があるキーワード(表1-1参照)
  • JavaScriptですでに定義されているオブジェクトやそのメンバー名(String、evalなど)

これらは使用してもエラーにはなりませんが、もともと定義されていた機能が使えなくなるからです。

【将来的に予約語として採用される可能性があるキーワード】(表1-1)
abstractbooleanbytechardoublefinal
floatgotointlongnativeshort
synchronizedthrowstransientvolatile

以上のルールから、str、_tmp、$、F1、formedのような名前はOKですが、以下のような名前はエラーとなります。

  • if・・・・・予約語
  • _@_・・・・・許可されない記号
  • 1F・・・・・数字で始まる名前

命名規則ではありませんが、可読性の観点からは、以下の点にも留意しておくことをお勧めします。

  1. 値の意味を名前から類推できる
    〇:price、isbn
    △:a、b
  2. 長すぎない、短すぎない
    〇:password
    △:pw、password_sha1_for_this_app
  3. 紛らわしい名前を混在しない
    △;user/usr、name/namae、keyword/keywd
  4. 一文字目のアンダースコアは特別な意味を類推されるので使わない
    △:_name
  5. ローマ字表記は避ける
    〇:name、count
    △:namae、kaisu
  6. 記法は統一する
    △:EmailAddress、emailAddress、email_address

(2)の「短すぎない」は、むやみに単語を省略しない、という意味です。

passwordをpasswdとすることは(3)の原因にもなりますし、pwともなればそもそもの理解が困難になります。

ただし、temporary(temp、tmp)、exception(e、ex)、message(msg)のように、一般的に通用する略語は、その限りではありません。

(6)の記法には、次に掲げる表のようなものがあります。

【識別子の主な記法】
記法概要表記例
camelCase記法単語の区切りは大文字で表記(ただし、先頭のみ小文字)myApp
Pascal記法単語の区切りは大文字で表記(先頭は大文字)MyApp
アンダースコア記法すべての単語を小文字で表記、区切り目は「_」my_app

一般的には以下のように使い分けます。

  • 変数/関数(メソッド) camelCase記法
  • 定数 アンダースコア記法
  • クラス(コンストラクター関数) Pascal記法

もっとも、(1)~(6)のポイントは文脈によっても異なる場合があります。

たとえば、for命令で利用するカウンター変数のように、便宜的な変数は「i」「j」のようにできるだけ短い名前をつけるのが一般的です。

また、呼び出しの利便性を考えて、頻繁に呼び出す関数の名前として、「$」のような名前を付けるケースもあります。

そのような例外もあることを頭の隅に置きつつ、ここで述べたポイントはポイントで、1つの基準として意識しておくと良いでしょう。

文字列の中に変数を埋め込む方法

JavaScriptで文字列の中に変数を埋め込むには、「+」演算子で連結する方法と、テンプレート文字列(Template Strings)を利用する方法の2種類があります。

「+」演算子で連結する方法

「+」演算子は、 オペランドのいずれかが文字列である場合、これを連結します。

この性質を利用して、文字列の中に変数を埋め込みます。

具体的な例を見てみましょう。

let name ='横浜流星';
console.log('こんにちは、' + name + 'さん!');
// 結果:こんにちは、横浜流星さん!

テンプレート文字列を使用する方法

テンプレート文字列(Template Strings)を使えば、「${...}」の形式で文字列に変数を埋め込むことができます。

テンプレート文字列の中では、シングルクォート/ダブルクォートの代わりに、「`」(バッククォート)で文字列をくくります。

ちなみに、「`」(バッククォート)の出し方は、Windows・Macともに「shift+@」です。

具体的な例を見てみましょう。

let name ='横浜流星';
console.log(`こんにちは、${name}さん!`);
// 結果:こんにちは、横浜流星さん!

この例であれば、「${...}」で変数nameを埋め込んでいます。

「+」演算子で連結する方法と比べると、コードがぐんとシンプルになることが確認できます。

なお、テンプレート文字列の中で、ただの文字列として「${...}」を表したい場合には、以下のように「\${...}」、または「$\{...}」のようにしてください。

console.log(`Hello、\${hoge}JavaScript!`);
console.log(`Hello、$\{hoge}JavaScript!`);
// 結果:Hello、${hoge}JavaScript!

変数の巻き上げ(hoisting)とは

let宣言された変数は、厳密には「宣言されたブロック全体」で有効です。

「全体」とは、以下のような挙動を意味します。

let scope = 'Global';
	
function show() {
  console.log(scope); 
  // 結果:ReferenceError: Cannot access 'scope' before initialization
  let scope = 'Local';
  return scope;
}
show();

この例では、関数内で変数scopelet scope = 'Local';)が宣言される前に、「console.log(scope); 」でこれを参照しています。

このような状況では、関数外ですでに宣言されている同名の変数scopelet scope = 'Global';)が参照されるように見えます。

しかし、結果を見てもわかるように、結果はエラー(ReferenceError: Cannot access 'scope' before initialization)となります。

これは、JavaScriptが

変数を、ブロックの先頭まで巻き上げた(hoist)

ために生じた結果です。

つまり、巻き上げの結果、「console.log(scope); 」の変数はブロックの先頭で有効となっています。

しかし、初期化はあくまでも元々の宣言位置で行われるため、ブロックの先頭から宣言までの間は、変数は利用できないのです。

このような領域をTemporal dead zoneといいます。

これは直感的には理解しにくい挙動なので、Temporal dead zoneをなくすようなコードを書くべきです。

具体的には、変数は関数(ブロック)の先頭で宣言すべきです(なお、この作法は「変数はできるだけ利用する場所の近くで宣言する」という他の言語のそれに反するので注意が必要です)。

const命令で定数を宣言する

変数とは「データの入れ物」であると説明しました。

入れ物ですから、スクリプトの途中で中身を入れ替えても構いません。

一方、入れ物と中身がワンセットで、途中で中身を変更できない入れ物のことを定数といいます。

定数とは、コードの中で現れる意味ある値に、あらかじめ名前を付けておく仕組みともいえます。

定数の意味を理解するには、定数を使わない例を見てみるのが一番です。

let price = 1000;
console.log(price * 1.10) 
// 結果:1100

これは、ある商品の(税抜き)価格priceに対して、消費税10%を加味した価格を求める例です。

しかし、このようなコードには、以下のような問題があります。

ただの数値は意味を表さない(マジックナンバー)

まず、1.10は、だれにとっても理解できる数値ではありません。

この例であれば、比較的類推しやすいかもしれませんが、より複雑な式の中で1.10がサービス率を表すのか、値上げ率を表すのか、はたまた、全く予想もつかない何かを表すのかを、誤解なく伝えるのは困難です。

一般的には、ただの数値(リテラル)は、自分以外の人間にとっては意味を持たない、謎の値だと考えるべきです。

そして、そのような値のことをマジックナンバーといいます。

同じ値がコードに散在する

将来的には、消費税は、15%、20%…と変更されるかもしれません。

その時、コードの至る所に1.10というリテラルが散在していたら、どうでしょう。

それらのリテラルをあますことなく検索&修正するといいう作業が必要となります。

これは面倒というだけでなく、修正漏れなどバグの原因ともなります。

そこで、1.10という値を次のように定数で書き換えます。

const TAX = 1.10;
let price = 1000;
console.log(price * TAX) 
// 結果:1100

定数を宣言するには、varlet命令の代わりに、const命令を利用するだけです。

ここでは、定数TAXに対して1.10という数値リテラルを割り当てています。

const命令の構文は次の通りです。

const命令

const name = value
name   変数名
value  値

定数の命名規則は、変数のそれにほぼ準じますが、定数であることを識別しやすいよう、すべて大文字で、単語をアンダースコアで区切るのが一般的です。

たとえば、CONSUMPTION_TAXUSER_NAMEなどです。

上の定数を利用した例からも、定数を利用することで値の意味が明確になり、コードの可読性が増したことが見て取れるでしょう。

また、あとからTAXの値を変更したくなった場合でも、定数の値だけを修正すればよいので、修正漏れを未然に防ぐことができます。

var、let、constいずれを優先的に使うべきか?

上述の通りvar命令は機能的な制限があるので、モダンなブラウザー環境では利用する理由はありませんし、利用すべきではありません。

let const命令いずれを優先して利用すべきかですが、結論から言うとconst命令を優先して利用すべきです。

なぜなら、コードを書いていくうえで、再代入しなければならない状況はあまりありません。

演算/加工の結果、値の意味が変わってしまったら、それは別な変数(名前)に格納すべきだからです(同じ変数に書き戻しても構いませんが、コードの意味は不明瞭になります)。

再代入されない変数をlet宣言することは、「(変化しないにも関わらず)どこかで値が変わるかもしれない」可能性を意識しながらコードを読まなければならないという意味で、可読性を落とします。

そのため、値が変化することを想定していなければ、まずはconst宣言を優先して利用すべきなのです。

ちなみに、値が変化する前提となる状況とは、forループでのカウンター変数/仮変数です。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です