変数が見える範囲をスコープと呼びます。let と const の変数は、作った { }(ブロック)の中だけで見えます。Java ・ C# と同じ考え方です。
const x = "外";
{
const x = "内";
console.log(x);
}
console.log(x);
if (true) {
let y = 1;
}
console.log(typeof y);
内
外
undefined
内側で同じ名前の変数を作ると、内側ではそちらが優先されます(外側の x は隠れるだけで、変わりません)。y はブロックの外からは見えないので、typeof y は "undefined" です。
関数の中に関数を書くと、スコープは入れ子になります。変数を探すときは、今いる場所から外側へ順に探します。外から内の変数は見えません。
const appName = "メモ帳";
function outer() {
const user = "user1";
function inner() {
const page = "一覧";
console.log(appName, user, page);
}
inner();
console.log(typeof page);
}
outer();
メモ帳 user1 一覧
undefined
この「関数は、書かれた場所の外側の変数が見える」という決まりは、09 章のクロージャの土台になります。
古い書き方の var は、スコープの決まりが違います。ブロックを無視して、関数全体(関数の外ならグローバル全体)が範囲になります。
for (var i = 0; i < 3; i++) {}
console.log(i);
for (let j = 0; j < 3; j++) {}
console.log(typeof j);
3
undefined
var i はループが終わっても残っています。let j はループの中だけです。
var の変数は、実行の前にスコープの先頭で作られ、undefined が入った状態になります。これを巻き上げ(hoisting)と呼びます。そのため、宣言より前で読んでもエラーにならず、undefined が返ります。let ・ const は、宣言より前で読むとエラーになります。
console.log(a);
var a = 1;
console.log(a);
console.log(b);
let b = 2;
undefined
1
Uncaught ReferenceError: Cannot access 'b' before initialization
宣言の前で読めてしまうのは、書き間違いに気づけない原因になります。新しいコードでは var を使わず、const と let だけを使います。古いコードや資料で var を見かけたら、このような違いがあることを思い出してください。
どの関数にも入っていない一番外側をグローバルと呼びます。グローバルには、globalThis という特別なオブジェクトがあり、ブラウザでは window、Node.js では global という名前でも呼べます。
let ・ const を付けずに代入すると、関数の中であっても、グローバルに変数ができてしまいます。エラーにならないので気づけません。
function setCount() {
count = 10; // let も const も付け忘れた
}
setCount();
console.log(count);
console.log(globalThis.count);
10
10
var fromVar = "var";
let fromLet = "let";
console.log(globalThis.fromVar);
console.log(globalThis.fromLet);
var
undefined
ブラウザでは、1 つのページに複数のスクリプトを読み込みます。グローバルの名前は全スクリプトで共有なので、別の人が同じ名前を使うと上書きされます。次の例は、2 つのスクリプトが続けて読み込まれた様子を 1 つにまとめたものです。
// a.js
var total = 100;
// b.js(別の人が書いた)
var total = "合計";
// a.js の関数があとで呼ばれる
console.log(total + 1);
合計1
エラーにはならず、計算結果だけがおかしくなります。グローバルに置くものは最小限にし、関数やモジュール(10 章)の中に閉じ込めます。
this は「この関数を呼んだ持ち主」を指す特別な変数です。Java ・ C# の this は「そのクラスのインスタンス」に決まっていますが、JavaScript の this は関数を書いた場所ではなく、呼んだときの書き方で決まります。
obj.method() の形で呼ぶと、this は点の左のオブジェクトになります。
const counter = {
count: 0,
up() {
this.count++;
return this.count;
},
};
console.log(counter.up());
console.log(counter.up());
1
2
同じ関数を変数に取り出してから呼ぶと、点の左が無くなります。すると this は counter ではなくなります。
const counter = {
count: 0,
up() {
this.count++;
return this.count;
},
};
const up = counter.up;
console.log(up());
console.log(counter.count);
NaN
0
点なしで呼んだので、this はグローバルオブジェクト(globalThis)になりました。globalThis.count は無い(undefined)ので、undefined + 1 で NaN です。counter.count は変わっていません。しかもエラーにならず、グローバルを汚しています。
call は this を指定して呼びます。bind は this を固定した新しい関数を作ります。
function introduce(greeting) {
return `${greeting}、${this.title}です`;
}
const shop = { title: "青果店" };
console.log(introduce.call(shop, "こんにちは"));
const bound = introduce.bind(shop);
console.log(bound("いらっしゃいませ"));
こんにちは、青果店です
いらっしゃいませ、青果店です
実務で this を失うのは、ほとんどがメソッドをコールバックとして渡したときです。渡した先では、点なしで呼ばれるからです。
const timer = {
label: "タイマー",
show() {
console.log(this.label);
},
};
setTimeout(timer.show, 0);
setTimeout(timer.show.bind(timer), 0);
setTimeout(() => timer.show(), 0);
undefined
タイマー
タイマー
1 つ目は this を失っています。2 つ目は bind で固定し、3 つ目はアロー関数の中で timer.show() と点付きで呼び直しています。
アロー関数は自分の this を持たず、書かれた場所の外側の this をそのまま使います。メソッドの中でコールバックを書くときは、アロー関数にしておくと this を失いません。
const timer = {
label: "タイマー",
start() {
setTimeout(function () {
console.log("function:", this.label);
}, 0);
setTimeout(() => {
console.log("アロー関数:", this.label);
}, 0);
},
};
timer.start();
function: undefined
アロー関数: タイマー
注意: 逆に、メソッドそのものをアロー関数で書くと、this がオブジェクトになりません。オブジェクトの外側の this を使ってしまうからです。メソッドは up() { … } の形で書き、その中のコールバックをアロー関数にします。
| 場面 | 書き方 |
|---|---|
| オブジェクトやクラスのメソッド | method() { … } |
| メソッドの中で渡すコールバック | アロー関数 () => { … } |
| メソッドを外へ渡す | obj.method.bind(obj) か () => obj.method() |
ファイルや関数の先頭に "use strict"; と書くと、strict モードになります。この章で見た事故のいくつかが、黙って進まずにエラーになります。
"use strict";
function setTotal() {
total = 10;
}
setTotal();
Uncaught ReferenceError: total is not defined
"use strict";
const counter = {
count: 0,
up() {
this.count++;
return this.count;
},
};
const up = counter.up;
console.log(up());
Uncaught TypeError: Cannot read properties of undefined (reading 'count')
宣言忘れはエラーになり、点なしで呼んだ関数の this は undefined になります。グローバルを黙って汚すより、その場で止まるほうが原因を見つけやすくなります。
ヒント: ES Modules(import ・ export を使うファイル。10 章 ・ 12 章)と、class の中は、自動で strict モードになります。モジュールで書いていれば、"use strict"; を書く必要はありません。
関数の外に書いた this の値は、どう実行されたかで変わります。
console.log(this === globalThis);
true
このページの「▶ 実行」のように、ふつうのスクリプトとして動かすと globalThis です。Node.js でファイルを実行すると、形式によって違います(形式の違いは 12 章)。
top-this.cjs
console.log(this);
console.log(this === module.exports);
{}
true
top-this.mjs
console.log(this);
undefined
一番外側の this に頼るコードは、環境が変わると壊れます。グローバルオブジェクトが欲しいときは globalThis と名前で書きます。
メモ: ブラウザのページに直接書いたスクリプトでは、globalThis は window です。この資料の「▶ 実行」は、ページを固まらせないために Web Worker という別の場所で動いているので、globalThis は self という名前のオブジェクトです。どちらも「グローバルオブジェクト」として同じように振る舞います。