2022年11月13日

古い "var"

この記事は古いスクリプトを理解するためのものです

この記事の情報は、古いスクリプトを理解するのに役立ちます。

それは、新しいコードの書き方ではありません。

変数に関する最初の章で、変数を宣言する3つの方法について述べました。

  1. let
  2. const
  3. var

var宣言はletと似ています。ほとんどの場合、letvarに置き換えたり、その逆も同様に置き換えて、動作することを期待できます。

var message = "Hi";
alert(message); // Hi

しかし、内部的にはvarは非常に古い時代に由来する、非常に異質な存在です。一般的には現代のスクリプトでは使用されませんが、古いスクリプトにはまだ潜んでいます。

そのようなスクリプトに遭遇する予定がない場合は、この章をスキップしたり、後回しにしたりしても構いません。

一方で、古いスクリプトをvarからletに移行する場合、奇妙なエラーを避けるために違いを理解することが重要です。

「var」にはブロックスコープがない

varで宣言された変数は、関数スコープまたはグローバルスコープのいずれかになります。それらはブロックを通して可視です。

例えば

if (true) {
  var test = true; // use "var" instead of "let"
}

alert(test); // true, the variable lives after 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" is visible after loop, it's a global variable
alert(one); // 1, "one" is visible after loop, it's a global variable

コードブロックが関数内にある場合、varは関数レベルの変数になります。

function sayHi() {
  if (true) {
    var phrase = "Hello";
  }

  alert(phrase); // works
}

sayHi();
alert(phrase); // ReferenceError: phrase is not defined

見てわかるように、variffor、または他のコードブロックを貫通します。それは、JavaScriptのずっと昔には、ブロックにレキシカル環境がなく、varはその残存物であるためです。

「var」は再宣言を許容する

同じスコープ内でletを使って同じ変数を2回宣言すると、エラーになります。

let user;
let user; // SyntaxError: 'user' has already been declared

varでは、変数を何度でも再宣言できます。すでに宣言されている変数でvarを使用すると、それは無視されます。

var user = "Pete";

var user = "John"; // this "var" does nothing (already declared)
// ...it doesn't trigger an error

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は関数の上部に「ホイスト」(持ち上げ)されるからです。

したがって、上記の例では、if (false)ブランチは決して実行されませんが、それは問題ではありません。その中のvarは関数の最初に処理されるため、(*)の時点では変数は存在します。

宣言はホイストされますが、代入はホイストされません。

それが最もよく示されるのは、例です。

function sayHi() {
  alert(phrase);

  var phrase = "Hello";
}

sayHi();

var phrase = "Hello"という行には、2つのアクションがあります。

  1. 変数宣言var
  2. 変数代入=

宣言は関数実行の開始時に処理されます(「ホイスト」)、しかし代入はそれが現れる場所で常に機能します。したがって、コードは基本的にこのように機能します。

function sayHi() {
  var phrase; // declaration works at the start...

  alert(phrase); // undefined

  phrase = "Hello"; // ...assignment - when the execution reaches it.
}

sayHi();

すべてのvar宣言は関数の開始時に処理されるため、どこからでも参照できます。ただし、変数は代入されるまで未定義です。

上記の両方の例で、変数phraseが存在するため、alertはエラーなしで実行されます。ただし、その値はまだ割り当てられていないため、undefinedと表示されます。

IIFE

過去には、varしかなく、ブロックスコープの可視性がないため、プログラマーはそれをエミュレートする方法を考案しました。彼らが行ったのは、「即時呼び出し関数式」(IIFEと略されます)と呼ばれるものでした。

これは今日使用するものではありませんが、古いスクリプトで見つけることができます。

IIFEは次のようになります。

(function() {

  var message = "Hello";

  alert(message); // Hello

})();

ここで、関数式が作成され、即座に呼び出されます。したがって、コードはすぐに実行され、独自のプライベート変数を持っています。

関数式は括弧(function {...})で囲まれています。なぜなら、JavaScriptエンジンがメインコードで"function"を検出すると、それを関数宣言の開始として理解するためです。しかし、関数宣言には名前が必要なので、この種のコードはエラーになります。

// Tries to declare and immediately call a function
function() { // <-- SyntaxError: Function statements require a function name

  var message = "Hello";

  alert(message); // Hello

}();

「わかりました、名前を追加しましょう」と言っても、JavaScriptは関数宣言をすぐに呼び出すことを許可していないため、うまくいきません。

// syntax error because of parentheses below
function go() {

}(); // <-- can't call Function Declaration immediately

したがって、関数の周りの括弧は、JavaScriptに関数が別の式のコンテキストで作成されていることを示すためのトリックであり、したがって、それは関数式です。名前は必要なく、すぐに呼び出すことができます。

JavaScriptに、関数式を意味することを伝えるための括弧以外の他の方法も存在します。

// Ways to create IIFE

(function() {
  alert("Parentheses around the function");
})();

(function() {
  alert("Parentheses around the whole thing");
}());

!function() {
  alert("Bitwise NOT operator starts the expression");
}();

+function() {
  alert("Unary plus starts the expression");
}();

上記のすべての場合で、関数式を宣言してすぐに実行します。もう一度注意しておきましょう。今日では、このようなコードを書く理由はありません。

まとめ

varlet/constには2つの主な違いがあります。

  1. var変数にはブロックスコープがなく、その可視性は現在の関数、または関数外で宣言された場合はグローバルにスコープされます。
  2. var宣言は、関数の開始時(グローバルの場合はスクリプトの開始時)に処理されます。

グローバルオブジェクトに関連するもう1つの非常に小さな違いがあり、それは次の章で説明します。

これらの違いにより、varはほとんどの場合letよりも劣っています。ブロックスコープの変数は非常に素晴らしいものです。そのため、letはかなり前に標準に導入され、現在では変数を宣言するための主要な方法(constと合わせて)になっています。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善のための提案がある場合は、コメントする代わりに、GitHub issueを送信するか、プルリクエストを送信してください。
  • 記事で何か理解できないことがある場合は、詳しく説明してください。
  • コードを数語挿入するには、<code>タグを使用します。数行の場合は、<pre>タグで囲み、10行以上の場合は、サンドボックス(plnkrjsbincodepen…)を使用します。