この記事の情報は古いスクリプトを理解するのに役立ちます。
新しいコードを記述する方法ではありません。
変数 の最初の章では、変数を宣言する3つの方法について述べました:
let
const
var
var
宣言は let
と同じような振る舞いをします。ほとんどの場合で let
を var
あるいは後の逆に置き換えることができ、期待通りに動作するでしょう:
var message = "Hi";
alert(message); // Hi
ですが、内部的には var
はずっと昔から使われてきた、全く異なるものです。一般的には最近のスクリプトでは利用されていませんが、古いスクリプトには依然として潜んでいます。
このようなスクリプトに出会う予定がないのであれば、この章はスキップするか後回しにしてください。
一方で、古いスクリプトを var
から let
に移行する際には奇妙なエラーを避けるために違いを理解することが重要です。
“var” はブロックスコープを持ちません
var
で宣言された変数は、関数スコープかグローバルスコープのいずれかです。ブロックを通って見えます。
例:
if (true) {
var test = true; // "let" の代わりに "var" を使う
}
alert(test); // true, if の後も変数は生きています
var
はコードブロックを無視するので、グローバルの test
を取得します。
var test
の代わりに let test
を利用すると、変数は if
の中でのみ見えます:
if (true) {
let test = true; // use "let"
}
alert(test); // ReferenceError: test is not defined
ループでも同様です: var
はブロック、またはループのローカルにはなれません:
for (var i = 0; i < 10; i++) {
var one = 1;
// ...
}
alert(i); // 10, "i" ループ後も見え、それはグローバル変数です
alert(one); // 1, "one" はループ後も見え、それはグローバル変数です
コードブロックが関数の内側にある場合、var
は関数レベルの変数になります:
function sayHi() {
if (true) {
var phrase = "Hello";
}
alert(phrase); // works
}
sayHi();
alert(phrase); // Error: phrase は未定義
上の通り、var
は if
, for
もしくは他のコードブロックを貫通します。それは、JavaScriptは長い間ブロックがレキシカル環境を持っていなかったためです。var
はそれを想起させます。
“var” は再宣言を許容します
同じスコープ内で2回 let
で同じ変数を宣言すると、エラーになります:
let user;
let user; // SyntaxError: 'user' has already been declared
var
では何度でも変数の再宣言が可能です。var
で既に宣言した変数を使用すると、var
を無視します。
var user = "Pete";
var user = "John"; // "var" は何もしません (宣言済み)
// ...エラーは発生しません
alert(user); // John
“var” は、その使用の下で宣言することができます
var
宣言は、関数の開始時(またはグローバルのスクリプト開始時)に処理されます。
言い換えると、var
変数は関数の最初で定義され、定義される場所は関係ありません(定義がネストされた関数ではないと言う仮定で)。
なので、以下のコードをみてください:
function sayHi() {
phrase = "Hello";
alert(phrase);
var phrase;
}
sayHi();
…これは技術的には以下同じです(var phrase
を上に移動させています):
function sayHi() {
var phrase;
phrase = "Hello";
alert(phrase);
}
sayHi();
…もしくはこれです(コードブロックが無視されることを忘れないでください):
function sayHi() {
phrase = "Hello"; // (*)
if (false) {
var phrase;
}
alert(phrase);
}
sayHi();
また、すべての var
が関数の先頭に “持ち上げられ” ているので、人々はそのような振る舞いを “巻き上げ(hoisting/raising)” とも呼びます。
したがって、上の例では if (false)
の分岐は決して実行されませんが、それは関係ありません。その内側の var
は関数の最初に処理されるので、(*)
の時点で変数は存在します。
宣言は巻き上げられますが、代入はされません。
次のコードはその例です:
function sayHi() {
alert(phrase);
var phrase = "Hello";
}
sayHi();
行 var phrase = "Hello"
はその中で2つのアクションを持っています:
- 変数宣言
var
- 変数代入
=
.
宣言は関数実行の開始時に処理されます(“巻き上げ”)が、代入は常にそれが出現した場所で行われます。従って、コードは本質的にはこのように動作します:
function sayHi() {
var phrase; // 宣言は最初にされます...
alert(phrase); // undefined
phrase = "Hello"; // ...代入 - 実行がここに来た時にされます.
}
sayHi();
すべての var
宣言が関数開始時に処理されるため、どこからでもそれらを参照することができます。しかし、変数は代入されるまで undefined です。
上の両方の例では、 alert
はエラーなく動作します。なぜなら変数 phrase
が存在するからです。しかし、その値は代入されていないので、 undefied
を表示します。
IIFE(即時実行関数)
昔は var
しかなく、ブロックレベルの可視性がないため、プログラマはそれをエミュレートする方法を考案しました。彼らが行ったのは “即時実行関数(immediately-invoked function expressions)” (IIFEと略します) と呼ばれます。
これは最近ではつかうべきものではありませんが、古いスクリプトでは見つけることができます。
IIFE はこのようなものです。:
(function() {
let message = "Hello";
alert(message); // Hello
})();
ここで、関数式は作成された後、すぐに呼ばれます。従って、コードはすぐに実行され自身のプライベート変数を持ちます。
関数式は括弧 (function {...})
で囲まれています。なぜなら、JavaScriptはメインコードフローの中で "function"
を見つけると、関数宣言の開始と理解します。しかし、関数宣言は名前が必須なので、エラーになります。:
// 宣言を行い、すぐに関数を実行しようとします
function() { // <-- SyntaxError: Function statements require a function name
let message = "Hello";
alert(message); // Hello
}();
では関数宣言にして名前をつけよう" と思うかもしれませんが、これは動作しません。JavaScriptでは関数宣言をすぐに呼ぶことができません。:
// 下の括弧による構文エラー
function go() {
}(); // <-- 関数宣言は即時呼び出しできません
従って、関数が別の式のコンテキストで作られており、関数式であることを JavaScript に示すには括弧が必要になります。名前なしで、すぐに呼び出せる必要があります。
JavaScriptには、関数式を意味する他の方法があります:
// IIFE の作成方法
(function() {
alert("関数を括弧で囲みます");
})();
(function() {
alert("全体を括弧で囲みます");
}());
!function() {
alert("NOT 演算子は式を開始します");
}();
+function() {
alert("単項プラスは式を開始します");
}();
上のすべてのケースで、私たちは関数式を宣言した後すぐに実行することができます。
サマリ
let/const
と比較して、var
には大きな違いが2つあります:
var
変数はブロックスコープを持っておらず、可視性は現在の関数にスコープされ、関数外で宣言されている場合には、グローバルになります。var
変数宣言は関数開始時に処理されます(グローバルの場合はスクリプト開始時)
グローバルオブジェクトに関連する小さな違いがもう少しあります。それは次のチャプターで説明します。
これらの違いにより、ほとんどの場合 var
は let
よりも不利になります。ブロックレベルの変数はとして役に立ちます。なので、let
ずいぶん前に標準として導入され、今では const
と並んで変数を宣言するための主要な手段となっています。
コメント
<code>
タグを使ってください。複数行の場合は<pre>
を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。