sit in a circle and chat happily

JavaScript 17日目

投稿日:2021-12-24
更新日:2022-03-24
js

第14章:スコープ

スコープとは

変数の参照範囲のことをいう。作成(宣言)した変数が使える範囲(有効範囲)はどこまでなのか把握しよう!

スコープは変数宣言の仕方でも変わってくる。変数宣言の種類も学習する。

スコープの種類

「グローバルスコープ」と「ローカルスコープ」の2種類。
ローカルスコープはさらに「関数スコープ」と「ブロックスコープ」に分かれる。

グローバルスコープ(グローバル変数)

トップレベルで変数宣言したものはどこからでも利用可能
 ※ローカルスコープに同名変数があるときはローカル変数優先

function test() {
  console.log('関数内:' + num);
  // 関数内から関数外の変数numを参照可能
}

console.log('1回目の出力');
var num = 100;
// 関数外で宣言した変数は
// 関数内からも参照できる
// ★「グローバルスコープ」を持つ変数
//    どこからでも参照可能なグローバル変数と呼ぶ(一部例外あり)
console.log('関数外:' + num);
test();
コンソール出力内容

グローバルスコープ応用

function recoveryMagic(playerID, magicID) {
  // playerID:0
  // magicID:1
  console.log(
    playerList[playerID].name + 'は'
    + magicList[magicID].name + 'を唱えた!'
    + magicList[magicID].point + '回復');
  return magicList[magicID].point;
}
// グローバル変数:基本的にどこからでも参照可能
// 今回はrecoveryMagic関数内から参照している
// たくさんの関数が定義されても
// 関数内からいつでも利用することができる

// ★どこからでも参照出来て便利
// ただし、便利な反面バグを誘発する可能性を秘めている
// 最近の開発ではグローバル変数はなるべく使わないようになっている
var playerList = [
  { name: '田中', life: 1000 },
  { name: '佐藤', life: 500 },
];
var magicList = [
  { name: 'ホイミ', point: 100 },
  { name: 'ベホイミ', point: 200 },
  { name: 'ベホマズン', point: 300 },
];

console.log('1回目の出力');
playerList[0].life += recoveryMagic(0, 1);
console.log(playerList[0].life);
コンソール出力内容

ローカルスコープ(ローカル変数)

関数スコープ

関数内で「var」「let」「const」宣言したものは関数内のみで利用可能。

function test() {
  var num = 100;
  // 関数内で宣言された変数は
  // 関数内でのみ使える
  // ★「ローカルスコープ」をもつローカル変数
  console.log('関数内:' + num);
}

console.log('1回目の出力');
test();
console.log('関数外:' + num);
// 関数外から関数内の変数は参照できないためエラーとなる
コンソール出力内容

「return」を使えば、関数内の変数を関数外に戻り値として返すことは可能。

【問題14-2】エラー箇所を特定して修正する

// ★修正前★
function recoveryMagic(playerID, magicID) {
  var magicList = [
    { name: 'ホイミ', point: 100 },
    { name: 'ベホイミ', point: 200 },
    { name: 'ベホマズン', point: 300 },
    { name: 'メラ', point: -20 },
    { name: 'メラミ', point: -100 },
    { name: 'メラゾーマ', point: -300 },
  ];
  console.log(playerList[playerID].name + 'は'
    + magicList[magicID].name + 'を唱えた!'
    + magicList[magicID].point + '回復');
  return magicList[magicID].point;
}
function attackMagic(playerID, enemyID, magicID) {
  console.log(playerList[playerID].name + 'は'
    + magicList[magicID].name + 'を唱えた!'
    + enemyList[enemyID].name + 'へ'
    + -magicList[magicID].point + 'のダメージ!');
  return magicList[magicID].point;
}

var playerList = [
  { name: '田中', life: 1000 },
  { name: '佐藤', life: 500 },
];
var enemyList = [
  { name: 'スライム', life: 10 },
  { name: 'スライムキッド', life: 100 },
];

console.log('1回目の出力');
playerList[0].life += recoveryMagic(0, 1);
console.log(playerList[0].life);
enemyList[1].life += attackMagic(1, 1, 3);
console.log(enemyList[1].life);

【解答】「var magicList」をグローバル変数に変える(関数外に出す)

【別解答】両方の関数に「var magicList」を記述する。(←非効率)

グローバルスコープとローカルスコープに同名変数

グローバルスコープとローカルスコープに同じ名前の変数が存在する場合、グローバル変数がローカルスコープから利用できなくなり、ローカルスコープではローカル変数のみ利用できる。

function test() {
  var num = 200;
  console.log('関数内:' + num);
  // 関数内と関数外に同名変数がある場合、関数内変数を参照する
  // グローバル変数を参照するのは
  // 対象のローカル変数がない場合のみ
}

console.log('1回目の出力');
var num = 100;
test();
console.log('関数外:' + num);
コンソール出力内容

グローバルスコープと関数スコープに同名変数が宣言された場合、関数内から変数外のグローバル変数を利用することはできなくなる。
関数内で宣言した同名のローカル変数のみ利用できる。

変数宣言の種類

宣言名称内容
変数名未宣言変数グローバル変数になる
var変数名変数宣言した場所でグローバル変数にもローカル変数にもなる
同じスコープ内で同名変数の再宣言ができる
Windowsオブジェクトに登録される変数
let変数名局所変数宣言した場所でグローバル変数にもローカル変数にもなる
同じスコープ内で同名変数の再宣言ができない
ブロックスコープをもつ変数
const変数名定数宣言した場所でグローバル変数にもローカル変数にもなる
同じスコープ内で同名変数の再宣言ができない
ブロックスコープをもつ変数
【読み取り専用】再代入による値の変更はできない
変数宣言の種類

ブロックスコープ:let

ブロック「{}中カッコ」内で「let」を使って宣言された変数、「const」を使って宣言された定数はローカルスコープをもつローカル変数になる。ブロック内のみで利用可能なため「ブロックスコープ」と呼ぶ。

console.log('1回目の出力');
if (true) {
  var num1 = 100;
  // if文のブロック内({}中カッコ内)で宣言
  console.log('ブロック内:' + num1);
}
console.log('ブロック外:' + num1);
// ブロック外でも出力される

console.log('2回目の出力');
if (true) {
  let num2 = 200;
  // if文のブロック内({}中カッコ内)で宣言
  // letは「ブロックスコープ」をもつ
  // ブロック内で宣言された変数は、ブロック外で使えない
  console.log('ブロック内:' + num2);
}
console.log('ブロック外:' + num2);
コンソール出力内容

letで宣言した変数num2は
ifブロック外では利用出来ないのでエラーが表示されて処理が止まる

【参考】いろんなパターンを確認

console.log('【追加】3回目の出力');
let num3 = 300;
console.log(num3);

let num3 = 500;
console.log(num3);
// letは同名変数を再宣言することができない
// varが同名変数の再宣言OK
// ★ただし、同名変数再宣言OKだと
// 既に存在している変数をうっかり再宣言して
// 別の用途で利用してしまう可能性があるので危険!
// letはそれをエラーを出すことで未然に防ぐことができる
// (letは2015年に新しく実装された)
コンソール出力内容
console.log('【追加】4回目の出力');
if (true) {
  let num4 = 400;
  console.log(num4);
// ブロック内で変数num4は消滅
}
let num4 = 600;
// 消滅済みのnum4が再宣言OK
console.log(num4);
コンソール出力内容
console.log('【追加】5回目の出力');
console.log(num5);
let num5 = 500;
console.log(num5);
// 巻き上げ(ホイスティング)はしているけど
// 宣言前に利用しようとするとエラーを出してくれる
コンソール出力内容

◆varからletに変数宣言を変える時の注意◆

エラーになった時の確認ポイント
・ブロックスコープを疑う:宣言位置確認
・同名宣言の再宣言を疑う

定数:const

(コンスタント→常に一定していること。定数)

変数宣言時に「var」や「let」ではなく「const」を使用すると定数宣言になる。
定数は再代入によって後から値を変更することはできない。判定用の値を入れておく場合などは定数を使用するとわかりやすいコードになる。
また同じスコープ上に定数と同名の変数を作成するとエラーが発生し処理が止まる。

console.log('1回目の出力');
const NUM = 100;
// constキーワードで変数を宣言すると
// 「定数」として扱われる
console.log(NUM);
NUM = 200;
// 定数は初期値を代入した後、
// 再代入による値の変更はできず、エラーになる
console.log(NUM);

// ◆定数名:暗黙のルール
// 定数名はすべて大文字で記述する(守らなくてもエラーにはならない)
// JSはこのルールを守ってない人が多いっぽい

// ◆定数の使いどころ
// 初期値を代入したら変更しない値に使用
// 不用意な再代入によるバグを防げる
コンソール出力内容

未宣言変数

変数宣言時に「var」「let」「const」を使用せず変数名のみ記述した場合は未宣言変数として扱われる。未宣言変数はどのスコープ上で記述してもすべてグローバルスコープとして扱われる。バグの原因になりやすい為絶対使用しない!!

function test() {
  num = 100;  // 未宣言変数
  // 未宣言変数は関数外で宣言したことと同じ
  // グローバル変数として扱われる
  console.log('関数内:' + num);
}

console.log('1回目の出力');
test();
console.log('関数外:' + num);
コンソール出力内容

【参考エラー】

function test() {
  num = 100;  // 未宣言変数
  // 未宣言変数は関数外で宣言したことと同じ
  // グローバル変数として扱われる
  console.log('関数内:' + num);
}

console.log('1回目の出力');
console.log('関数外:' + num); // 関数呼び出し前だと出てこない
test();

第15章:ブラウザオブジェクト

ブラウザオブジェクトとは

ChromeやSafari、IE、Firefoxなどのブラウザが独自に実装しているオブジェクト。
昔は挙動が違ったり、存在しないオブジェクトなどがあったりした。

インスタンスが既に用意されている:暗黙オブジェクト

ほとんどのブラウザオブジェクトはnew演算子とコンストラクタでインスタンスを生成することはない。自動的にインスタンスが生成されており(暗黙オブジェクト)、あらかじめ用意された変数(プロパティ)に代入される。

ブラウザオブジェクトは変数名(プロパティ名)を記述すればいつでも使用できる。

主要なブラウザオブジェクト

※DocumentオブジェクトとFormオブジェクトはjQueryから利用する。

Windowオブジェクト

Windowオブジェクトはブラウザオブジェクトの頂点(最上位)にいる。
画面全体に関するすべての値と処理をもっている。

Windowインスタンス用の変数名:window

Windowインスタンスは変数名(プロパティ名)「window」にあらかじめ用意されている。オリジナルオブジェクトの利用ではないので頭文字は小文字。
インスタンス利用時に頭文字大文字「Window」と記述するとエラーになる。

★すべてのブラウザオブジェクトの最上位にいるので
 「window.」は省略してOK

Windowインスタンスのプロパティ利用

window.プロパティ名
[window.]プロパティ名

Windowインスタンスのメソッド利用

window.メソッド名(引数)
[window.]メソッド名(引数)

Windowインスタンスの代表的なプロパティ

プロパティ名内容
window.name画面の名前(文字列)
window.scrollX【読み取り専用】
水平(横)方向のスクロール位置(浮動小数点数)
window.scrollY【読み取り専用】
垂直(縦)方向のスクロール位置(浮動小数点数)
window.innerWidth【読み取り専用】
垂直スクロールバーを含む内部(表示領域)の幅
window.innerHeight【読み取り専用】
水平スクロールバーを含む内部(表示領域)の高さ

Windowオブジェクトのダイアログメソッド

window.alert([message])
window.confirm([message])
window.prompt([message[,value]])
console.log(window.alert('警告ダイアログ'));
// ◆alartメソッド
// 「OK」ボタンをもつダイアログ画面を表示
// 第1引数(任意):ダイアログ画面に表示する文字
//     省略時は空文字
// 戻り値:undefined

console.log(window.confirm('確認ダイアログ'));
// ◆confirmメソッド
// 「OK」と「キャンンセル」ボタンをもつダイアログ画面を表示
// 第1引数(任意):ダイアログ画面に表示する文字
//     省略時は空文字
// 戻り値:「OK」押下時「true」
//        「キャンセル」押下時「false」を返す

console.log(window.prompt('プロンプト'));
// ◆promptメソッド
// 入力欄をもつダイアログ画面を表示
// 第1引数(任意):ダイアログ画面に表示する文字
//     省略時は空文字
// 戻り値:「OK」押下時は入力した文字列
//        「キャンセル」押下時はnullを返す

// ◆Windowインスタンスを利用する時は
// 「window.」を省略できる
コンソール出力内容

補足:変数名「name」は要注意

グローバル変数名としてよく使う「name」は要注意。
変数内に文字列を代入している限り問題は起きないが、配列などを代入するとおかしな挙動になる。

Windowインスタンスはnameプロパティ(画面の名前を管理)を既にもっている。このnameプロパティは文字列型のみ対応しており、文字列型以外のデータ型が代入されると強制的に文字列に変換される。

var name = '名前';
console.log(name);  // ← VSCodeだとnameに打消し線が入る
// 変数nameはWindowインスタンスに
// 予め用意されているプロパティ(画面名管理用)
// データ型が文字列型で固定されている

// 再宣言することになるのでVSCodeでは警告を出してくる
コンソール出力内容
プロパティ名内容
window.name画面の名前(文字列)
console.log('1回目の出力');
var nameList = ['ゆず', 'びわ'];
console.log(nameList);
console.log(nameList.length);
for (var i = 0; i < nameList.length; i++) {
  console.log(nameList[i]);
}
コンソール出力内容
console.log('2回目の出力');
var name = ['ゆず', 'びわ'];
// window.name(画面名管理用プロパティ)
// データ型は文字列固定
// 配列をtoStringメソッドで文字列化して代入
console.log(name);  // ゆず,びわ
console.log(name.length);  // 5
// 配列のlengthではなく、文字列のlengthを参照しているため
// 文字数の「5」が出力される
for (var i = 0; i < name.length; i++) {
  console.log(name[i]);
}
// 1文字ずつ出力されてしまう
コンソール出力内容

WindowオブジェクトのsetTimeoutメソッド

ブラウザ
// 関数定義:呼び出されないと実行しない
function test() {
  console.log('処理しました1');
}

// グローバル変数:どこからでもアクセス可能
var timerID1;
// イベントと紐づけて呼び出す関数
// 「setTimeout1を開始」ボタンクリック時に呼び出す
// 引数なし:()内に受け取り用変数がない
// 戻り値なし:returnの記述がない
function start1() {
  timerID1 = window.setTimeout(test, 3000);
  // ◆右辺の動き
  // WindowインスタンスのsetTimeoutメソッド呼び出し
  // ■setTimeoutメソッド
  // 一定時間経過後に処理をする
  // 第1引数:処理の内容
  //   処理名の後に()をつけない
  //   ()をつけると待機せずに即呼び出して実行される
  //   戻り値が第1引数に指定される
  // 第2引数:遅延させるミリ秒で指定
  // 戻り値:停止させるためのID番号
  console.log('timerID1:' + timerID1);
}
// 「setTimeout1を停止」ボタンクリック時に呼び出す
function stop1() {
  console.log('stop1実行');
  window.clearTimeout(timerID1);
  // <clearTimeout>遅延処理を途中で停止させる
  // 第1引数:停止用ID番号
  // 戻り値無:なし
}

3秒待った時

コンソール出力内容

3秒待たずにstop1をクリックした時

コンソール出力内容

なぜ関数名の後に「()丸カッコ」を記述しないのか

今まで「変数」と「関数」は別物のように扱ってきたが、実は関数も変数の中に入っている。名前のない無名関数が変数に代入されることで変数名を使って呼び出すことができる。この変数への無名関数代入処理は巻き上げ実行時に自動的に行われる。

遅延時間が保証されない理由

setTimeoutメソッドの第2引数で指定した遅延時間は保証されない。
setTimeoutメソッドは遅延時間経過後に処理の流れに割り込み登録するため、割り込み前の処理に時間がかかっているとその分遅れてしまう。

第2引数の遅延時間は処理を登録するまでの時間で処理が実行される時間ではない。

var timerID1;
  ・
  ・
  ・
function stop1() {
  console.log('stop1実行');
  window.clearTimeout(timerID1);
  // <clearTimeout>遅延処理を途中で停止させる
  // 第1引数:停止用ID番号
  // 戻り値無:なし
}

setTimeoutメソッド呼び出し後の戻り値タイマーIDがグローバル変数timerID1に代入されている。このタイマーIDをclearTimeoutメソッドの引数に指定する。このタイマーIDを使って停止させるsetTimeoutメソッドを判断する。

setTimeoutメソッド呼び出しから経過時間到達までの間にclearTimeoutメソッドが呼び出されると対象のsetTimeoutメソッドを停止する。

window.clearTimeout(timerID);

遅延時間に紐づける関数に無名関数を使う

setTimeoutメソッドの第1引数で指定する関数は関数名がなくてもsetTimeoutメソッドと紐づいていることがわかれば呼び出すことができる。

別の場所に関数を定義して第1引数に関数名を登録するのではなく、第1引数部分にじかに関数を定義して登録することができる。その場合は名前なしで紐づけることができる。
この名前なしの関数を「無名関数」と呼ぶ。

第1引数に「関数名で登録」しても「無名関数を定義」しても同じように処理される。関数名をつけると知らぬ間に同名関数を重複定義してしまうことがあるので、特に理由がなければ無名関数で定義する。

var timerID2;
// 「setTimeout2を開始」ボタンクリック時に呼び出す
function start2() {
  timerID2 = window.setTimeout(function () {
    console.log('処理しました2');
  }, 3000);
  // 第1引数に直接呼び出す処理をfunctionキーワードを使って直に定義
  // 名前がない関数(無名関数)として定義
  // ◆無名関数の使いどころ
  //  ・遅延位置で呼び出しが確定
  //  ・イベントによる呼び出し(詳細はjQueryで確認)
  console.log('timerID2:' + timerID2);
}
// 「setTimeout2を停止」ボタンクリック時に呼び出す
function stop2() {
  console.log('stop2実行');
  window.clearTimeout(timerID2);
}


// ◆setTimeoutメソッドの基本形◆
setTimeout(function () {}, 遅延時間);
//   ↓ 改行してわかりやすくすると・・・ ↓
setTimeout(function () {
遅延後の処理
}, 遅延時間);

【実際に無名関数で書いてみる】
  ブラウザを開いて4秒後に、下記の広告(文字)が表示されるようにする。

ブラウザ出力内容
<div id="ad2" style="display: none;">
  無名関数で4秒後に表示する広告<br>
  遅延を停止させることはない
</div>
// ◆PPG作成時の手順
// 1.何をしたいのかを明確にする
//  無名関数で4秒後に広告(ad2)を表示
//  遅延を停止させることはない
// 2.「値」「条件」「処理」に分ける

setTimeout(function () {
  document.getElementById('ad2').style.display = 'block';
}, 4000);
カテゴリー