変数と宣言 - var、let、constとグローバルオブジェクト

《 初回公開:2022/08/21 , 最終更新:2022/11/17 》

JavaScriptの変数宣言にはvar、let、constとかあって。

letとconstは、ECMAScript2015(ES6)から採用された新しい宣言。

【 目次 】

var 文は関数スコープまたはグローバルスコープの変数を宣言し、任意でそれをある値に初期化します。

let 文はブロックスコープのローカル変数を宣言します。
...
スコープのルール
let で定義された変数は、自身が定義されたブロックと、そこに含まれるサブブロックがスコープになります。
この点において let のふるまいは var にとてもよく似ています。
大きな違いは、var で定義された変数のスコープはそれを含んでいる関数全体になるということです。

定数は、let キーワードを使って定義する変数と同様にブロックスコープを持ちます。
定数の値は、再代入による変更ができず、再宣言もできません。

グローバルな定数は var 変数とは異なり、window オブジェクトのプロパティにはなりません

定数は大文字または小文字で宣言することができますが、すべて大文字で宣言するのが慣例です。

以下のコードは各変数のスコープにおけるvar宣言とlet宣言のふるまいの違いを示している。

var x = "global";
function test() {
  var x = "関数内の変数";
  {
    var x = "ブロック内の変数";
  }
  console.log(x);  // ブロック内の変数
}

test();
console.log(x);  // global

上記のコードのvar宣言をlet宣言に置き換えると

let x = "global";
function test() {
  let x = "関数内の変数";
  {
    let x = "ブロック内の変数";
  }
  console.log(x);  // 関数内の変数
}

test();
console.log(x);  // global

以下のような変数のスコープがあって

グローバルスコープ
どこでも有効
関数スコープ
関数内でのみ有効(関数内のブロックでも有効)
ブロックスコープ
ブロック内のみ有効

ブロックというのは {...}の内部を指していて
定数(const)宣言もlet宣言と同じくブロックスコープ。

ブロック内で再宣言した変数はvarの場合は関数スコープであるのに対してletの場合はブロックスコープになっている。

グローバルエリア(グローバル実行コンテキスト)で宣言された変数(varもletもconstも)はグローバルスコープとなる。
関数内で変数を宣言すると関数スコープとなり、
ブロック内で変数を宣言するとブロックスコープとなる。

但し、var変数の場合は特別で関数内で宣言してもブロック内で宣言しても関数スコープとなり、同一スコープ内で何回でも再宣言が可能。
さらにvar変数は変数の巻き上げが起きる。

if文の波カッコ内{}もブロックスコープ。

const C = "const"
let l = "let"
var v = "var"

if (true) {
  const C = "const_block"
  let l = "let_block"
  var v = "var_block"
}

console.log(C); // const
console.log(l); // let
console.log(v); // var_block

宣言されているが値が代入されていない値はundefinedになる。
未宣言の値を使うとerrorが発生する。

let l;
var v;
console.log(l); // undefined
console.log(v); // undefined
console.log(x); // error Uncaught ReferenceError: x is not defined

varは(同一スコープの中で)再宣言可能だがletは再宣言するとerrorになる。

var x = 'x';
let y = 'y';

var x=10;
let y=20;   // Uncaught SyntaxError: Identifier 'y' has already been declared  

未宣言のグローバル変数とstrictモード

また、x = 42 のように、単純に値を変数に代入することもできます。この形は、未宣言のグローバル変数を生成します。
strict モードの JavaScript では警告が発生します。未宣言のグローバル変数は、よく予期しない動作を引き起こします。
したがって、宣言されていないグローバル変数を使用することはお勧めしません。

JavaScriptでは未宣言で変数を使う事ができて、未宣言の変数はグローバルな変数となる。

function func(){
  x="未宣言の変数x";
}

func();
console.log(x);

未宣言の変数とvar変数の使い分けは。

微妙な振る舞いの違いはあるが、未宣言の変数は宣言を省略したvar変数という事かな。

strictモード

strictモード ”use strict”宣言を指定する事で、未宣言の変数の使用を禁止したり,プログラムの誤りがみつけやすくなる。

strictモードでは未宣言の変数はエラーになる。

  'use strict';

  u = 'undefined_v';    // Uncaught ReferenceError: u is not defined

グローバルオブジェクト

  • Global object (グローバルオブジェクト) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN

    JavaScript では、グローバルオブジェクトが常に定義されています。
    ウェブブラウザー上でスクリプトがグローバル変数を生成する時、グローバルオブジェクトのメンバーとして作成されます。 (Node.js ではこの限りではありません。)

    ウェブブラウザーでは、明示的にバックグランドタスクとして起動されるコードを除き、 Window がグローバルオブジェクトになります。
    ウェブにおける JavaScript コードのほとんどはこのケースに該当します。
    Worker 内で実行されるコードでは WorkerGlobalScope オブジェクトがグローバルオブジェクトになります。
    Node.js で実行されるスクリプトの場合、 global と呼ばれるオブジェクトがグローバルオブジェクトになります。

未宣言の変数やグローバル実行コンテキスト (すべての関数の外側) で宣言されたvar変数はグローバルスコープの変数でこれはグローバルオブジェクトのプロパティとなる。

ウェブブラウザーで動作するいわゆるクライアントサイドJavaScriptでは基本的にはWindow がグローバルオブジェクト。

ウェブブラウザーで動作するいわゆるクライアントサイドJavaScriptのコード

var x = 'var_x';
y = '未宣言の変数y';
console.log(window.x); // var_x
console.log(window.y); // 未宣言の変数

Node.jsではglobalと呼ばれるオブジェクトがグローバルオブジェクト。

{"title": "{"title": "ウェブブラウザーで動作するいわゆるクライアントサイドJavaScriptのコード"}
のコード"}
var x = 'var_x';
y = '未宣言の変数y';
console.log(global.x); // var_x
console.log(global.y); // 未宣言の変数

WebページのJavaScriptではwindowオブジェクト,Node.jsではglobalオブジェクトによってグローバルオブジェクトにアクセス可能であるがGAS(Gooogle Apps Script)ではこれらは使えない。

this変数

this変数はその実行コンテキストによって指す値が異なる。

グローバル実行コンテキストでのthis

グローバル実行コンテキスト (すべての関数の外側) では、strict モードであるかどうかにかかわらず、this はグローバルオブジェクトを参照します。

関数コンテキストでのthis

関数内での this の値は、関数の呼び出し方によって異なります。

下記のコードは strict モードではないため、また呼び出し時に this の値が設定されないため、this は既定でグローバルオブジェクトとなり、これはブラウザーでは window です。

オブジェクトのメソッド内のthis

関数がオブジェクトのメソッドとして呼び出されるとき、その this にはメソッドが呼び出されたオブジェクトが設定されます。

globalThis

globalThis はグローバルプロパティで、グローバルオブジェクトと同等であるグローバルな this が格納されています。

globalThisを使えばWebページやNode.jsそしてGASでもグローバルオブジェクトにアクセス可能になる。

以下のコードはthis変数とグローバルオブジェクトのふるまいを示している。

var x = 'global_x';
function f(){
  let x="local_x";
  console.log(this.x);  // obj.x
  console.log(globalThis.x);    // obj.x
}
f();

obj={
    x: "obj.x",
    f: function(){
      console.log(this.x);  // obj.x
      console.log(globalThis.x);    // global_x
    }
  };
obj.f();

変数・関数の巻き上げ - ホイスティング

変数の巻き上げと言って宣言される前の変数を参照できてその時その変数の値はundefinedとなる。

console.log(v); // undefined

var v="v";

その変数のスコープ内で宣言されたvar変数は、 その変数のスコープの先頭で宣言されたものとみなされる。
つまり、上記の例は以下のコードと等価。

var v;
console.log(v); // undefined

var v="v";

しかし、変数の巻き上げが起こるのはvar変数だけのようで、let変数や未宣言の変数ではエラーとなる。

console.log(l); // Uncaught ReferenceError: Cannot access 'l' before initialization
console.log(x); // Uncaught ReferenceError: x is not defined

let l="l";
x="x";

関数の巻き上げというのもあって

複数の変数をまとめて宣言

C言語のように、複数の変数をカンマでくぎってまとめて宣言する事ができる。

const c1="c1",c2="c2";
let l1="l1",l2="l2",l3;
var v1="v1",v2="v2",v3;

console.log(c1,c2);
console.log(l1,l2,l3);
console.log(v1,v2,v3);
c1 c2
v1 v2 undefined
l1 l2 undefined

いろいろなケースでの変数の振る舞い

変数の振る舞いがいろいろな場合でどのように動作するのか検証してみた。

グローバル実行コンテキストで宣言した変数や未宣言の変数はグローバル変数になる。
つまり、関数内やオブジェクトのメソッド内でアクセス可能。

const C = "const"
let l ="let"
var v ="var"
u = 'undefined_v';

function f() {
    console.log(C); // const
    console.log(l); // let
    console.log(v); // var
    console.log(u); // undefined_v
}
f();

obj={
  x: "obj.x",
  f: function(){
    console.log(C); // const
    console.log(l); // let
    console.log(v); // var
    console.log(u); // undefined_v
  }
};
obj.f();

constを除いて関数内やメソッド内で内容を変更すれは、その値はグローバルに反映される。

const C = "const"
let l = "let"
var v = "var"
u = 'undefined_v';

function f() {
  //C = C + "_change";  // Uncaught TypeError: Assignment to constant variable.
  l = l + "_change";
  v = v + "_change";
  u = u + "_change";
}
f();

console.log(l); // let_change
console.log(v); // var_change
console.log(u); // undefined_v_change

obj = {
  x: "obj.x",
  f: function () {
    //C = C + "_obj";       // Uncaught TypeError: Assignment to constant variable.

    l = l + "_obj";
    v = v + "_obj";
    u = u + "_obj";
  }
};
obj.f();

console.log(l); // let_change_obj
console.log(v); // var_change_obj
console.log(u); // undefined_v_change_obj

関数内ではグローバル変数と同名の変数を定義するとそれはローカル変数となり、
constとletの場合は宣言の前で変数を使うとエラーとなるが、
varの場合は変数の巻き上げが起こってエラーとはならず値はundefinedとなる。
関数内での変数の値はローカルの値となり、グローバルな変数の値は隠される。
関数の実行後もグローバル変数の値は変わらない。

const C = "const"
let l = "let"
var v = "var"

function f() {
  //console.log(C); // Uncaught ReferenceError: Cannot access 'c' before initialization
  //console.log(l); // Uncaught ReferenceError: Cannot access 'l' before initialization
  console.log(v);   // undefined
  const C ="local_const"
  let l = "local_let"
  var v = "local_var"
}
f();

console.log(C); // const
console.log(l); // let
console.log(v); // var

関数内ではグローバルなvar変数や未宣言の変数はグローバルオブジェクトのプロパティとなりthisやglobalThis変数を使ってアクセスできる。
しかし、constやlet変数はグローバルオブジェクトのプロパティとはならず参照できない。

        const C = "const";
        let l = "let"
        var v = "var"
        u = 'undefined_u';

        function f() {
            const C = "local_const";
            let l = "local_let"
            var v = "local_var"

            console.log(this.C);          // undefined
            console.log(this.l);          // undefined
            console.log(this.v);          // var
            console.log(this.u);          // undefined_u
            console.log(globalThis.C);    // undefined
            console.log(globalThis.l);    // undefined
            console.log(globalThis.v);    // var
            console.log(globalThis.u);    // undefined_u
        }
        f();

オブジェクトのメソッド内ではthisはオブジェクトを指していて、グローバルオブジェクトにはglobalThisやwindow等の変数でアクセスすることになる。

var v = "var"
u = 'undefined_v';

obj={
  x: "obj.x",
  f: function(){
    console.log(this.x);        // obj.x
    console.log(globalThis.v);  // var
    console.log(globalThis.u);  // undefined_v
  }
};
obj.f();

指針(私信)

バグをつくらないために。

未宣言のグローバル変数は使わない。(”use strict”宣言を指定する。)
var変数はlet変数にできるだけ置き換える。
グローバルオブジェクトはglobalThis変数を使う。(windowやthisではなく)

ページのトップへ戻る