# はじめに

せっかくのお盆休み(とは言っても3連休だけですが)なので、知識としては知っているもののあやふやな部分があるところに関して少し振り返ってみようかと思いました。 まずは変数宣言について振り返ります。

# var,let,constについて

現在JavaScriptにおいて変数、定数を宣言する場合はlet,constが使用されます。 let,constがでる前はvarを使用していましたが、これからJavaScriptを書く場合にあえてvarを選択する理由はありません。

またletに関しても再宣言は不可能なものの再代入は可能であるため、一部の場面を除いてconstを利用する方が良いでしょう。

# MDNで確認

まずはそれぞれの定義に関してMDNで確認してみます。

var

var 文は変数を宣言し、任意でそれをある値に初期化します。

let

let 文はブロックスコープの局所変数を宣言します。任意で値を代入して初期化できます。

const

定数 (const) は、 let 文を使って定義する変数と同じ、ブロックスコープです。定数の値は、再代入による変更はできず、再宣言もできません。

# 各宣言方法について

var > let > constの順でより制限が強くなっていきます。 for文などイテレータが必要な場合はletを使用することもありますが、書き方次第ではそれも回避できるので使用頻度は低めです。

名称 再宣言 再代入 関数スコープ ブロックスコープ
var ×
let × ×
const × × ×

# 再宣言、再代入

# 再宣言

一度宣言した変数を再度同じ変数名で宣言することです。 それぞれ実際のコード例を元にして説明していきます。

# var

varの場合は、再宣言も再代入も可能です。同一名の変数に関して何度でも上書きが可能です。

// 再宣言
try {
  var cat = 'tama';
  var cat = 'saba';
  console.log(cat) // saba
} catch(err) {
  console.log(err);
}
// 再代入
try {
  let cat = 'tama';
  cat = 'mike';
  console.log(cat) // mike
} catch(err) {
  console.log(err);
}

# let

letの場合は、再宣言は不可ですが、再代入は可能です。上書き自体はできてしまうので、宣言した後に書き換えられてしまう危険性は残ります。


// 再宣言
try {
  let shiba = 'maru';
  let shiba = 'jiro';
  console.log(shiba)
} catch(err) {
  console.log(err); // Uncaught SyntaxError: Identifier 'shiba' has already been declared
}
// 再代入
try {
  let shiba = 'maru';
  shiba = 'taro';
  console.log(shiba) // taro
} catch(err) {
  console.log(err);
}

# const

再宣言も再代入も不可能です。一度宣言したら値への参照自体は変更することができません。 しかしobjectや配列を宣言した場合、その中身自体の変更可能です。(後述します)

// 再宣言
try {
  const Hamu = 'kuru'
  const Hamu = 'momo'
  console.log(Hamu) // Uncaught SyntaxError: Identifier 'Hamu' has already been declared
} catch(err) {
  console.log(err)
}
// 再代入
try {
  const Hamu = 'kuru';
  Hamu = 'tete';
  console.log(Hamu)
} catch(err) {
  console.log(err) // TypeError: Assignment to constant variable.
}

# スコープ

# グローバルスコープ

  • プログラム全体で参照可能
  • どのような場合にグローバルスコープになるか
    • 何もつけずに変数を宣言
    • 関数のトップレベルで変数を宣言
scope = 'global' // グローバルスコープの変数scope
function checkScope() {
  scope = 'local1' // 関数スコープにならずにグローバススコープ変数を上書き
  localScope = 'local2' // 関数内ではあるが、グローバルスコープとして宣言された変数localScope
  console.log('functionLocal', scope, localScope)
}
checkScope() // functionLocal, local1, local2
console.log('globalScoep', scope, localScope, window.scope, window.localScope) // globalScoep, local1, local2, local1, local2

# 関数スコープ

  • 変数が宣言された関数の中だけで参照可能。
  • どのような場合に関数スコープになるか
    • 関数内でvarで変数を宣言

var scope = 'global'
function checkScope() {
  var scope = 'local1' // checkScope関数の内部だけで有効な変数scope
  if (true) {
    var localScope = 'local2'  // checkScope関数の内部からアクセス可能な変数localScope
  }
  console.log('functionLocal', scope, localScope) // functionLocal, local1, local2
}
checkScope() // functionLocal, local1, local2
console.log('globalScoep', scope, window.scope, window.localScope) // globalScoep, global, global, undefined

# ブロックスコープ

  • ブロック({}に囲まれた領域)内でのみ参照可能
  • 同じ関数内であっても異なるブロックであった場合アクセス不可
  • どのような場合にブロックスコープになるか
    • ブロック内でlet, constで変数,定数を宣言
function checkScope() {
  var scope = 'local1' // checkScope関数の内部だけで有効な変数scope
  if (true) {
    let localScope = 'local2'  // checkScope関数の内部からアクセス可能な変数localScope
  }
  console.log('functionLocal', scope, localScope) // localScopeはスコープが異なるため参照不可
}
checkScope() // Uncaught ReferenceError: localScope is not defined

# constにおけるobject,arrayの扱い

object,arrayについては変数の参照自体の再宣言、再代入はできません。
ですがそれらの中身を変えることは可能です。自分ははじめここでかなりconstに関しては混乱をしました。 まずは一番わかりやすいただのstringとしての定数の宣言をしたいと思います、


// 再宣言
const Idol = 'shimamu-'
const Idol = 'mio' // Uncaught SyntaxError: Identifier 'Idol' has already been declared
// 再代入
const Idol = 'shimamu-'
Idol = 'mio' // Uncaught TypeError: Assignment to constant variable.

constの場合は定数(constant variable)として宣言されるので、再宣言も再代入もできません。 次にオブジェクトを宣言してみます。

// 再宣言
const Idol = {nickname: 'shimamu-'}
const Idol = {nickname: 'mio'} // Uncaught SyntaxError: Identifier 'Idol' has already been declared
// 再代入
const Idol = {nickname: 'shimamu-'}
Idol =  {nickname: 'mio'} // Uncaught TypeError: Assignment to constant variable.

当然同じように再代入も再宣言もできません。 ではオブジェクトそのものではなく、オブジェクトの中身を変更した場合はどうでしょうか

// オブジェクトの要素を変更
const Idol = {nickname: 'shimamu-'}
Idol.nickname = 'mio'
console.log(Idol) // {nickname: 'mio'}

オブジェクトはそのものの再宣言、再代入は許容されませんがオブジェクトの要素に関しては変更、追加、削除が可能です。

// オブジェクトの要素の追加
const Idol = {nickname: 'shimamu-'}
Idol.type = 'cute'
Idol.talent = 'smile'
console.log(Idol) // {nickname: "shimamu-", type: "cute", }
// オブジェクトの要素の削除
const Idol = { nickname: 'shimamu-', type: 'cute',  talent:'smile' }
delete Idol.talent
console.log(Idol) //{nickname: "shimamu-", type: "cute"}

また配列においても同様に変更、削除、追加ができます。

// 変更
const Newgene = ['shimamu-']
const idolAdd = ['mio', 'shiburin']
Newgene.push(...idolAdd)
console.log(Newgene) // ["shimamu-", "mio", "shiburin"]
// 削除
const Newgene = ['shimamu-', 'mio', 'shiburin']
Newgene.shift()
console.log(Newgene) // ["mio", "shiburin"]
// 追加
const Newgene = ['mio', 'shiburin']
Newgene.unshift('shimamu-')
console.log(Newgene) // ["shimamu-", "mio", "shiburin"]

ここでこれまで説明してきた内容を振り返りつつ、とあるアニメのとあるグループの状態遷移を再現してみたいと思います。

// objectの宣言
const Uduki = { nickname: 'shimamu-', type: 'cute', status:'doMyBest'},
      Mio = { nickname: 'chanmio', type: 'passion', status: 'fun'},
      Rin = { nickname: 'shiburin', type: 'cool', status: 'seek'}
// 配列の宣言
const UdukiMioRin = [Uduki, Mio, Rin]
//初期値の宣言
const Newgene = []
// 配列への追加、削除
const changeGroup = (groupName, method, member) => {
  if (method === 'add') {
    Array.isArray(member) ? groupName.push(...member) : groupName.push(member) // 配列への追加
  } else if (method === 'left') {
    const NickName = member.nickname
    const Order = groupName.findIndex(n => n.nickname === NickName)
    groupName.splice(Order, 1) // 配列からの削除
  }
}
// objectの変更
const changeGroupValue = (groupName, type, nickname, key, value) => {
  if (type === 'change') {
    groupName.filter(n => n.nickname === nickname)[0][key] = value
  } else if (type === 'delete') {
    delete groupName.filter(n => n.nickname === nickname)[0][key]
  }
}
// 1話
changeGroup(Newgene, 'add', UdukiMioRin)
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "chanmio", type: "passion", status: "fun"}
2: {nickname: "shiburin", type: "cool", status: "seek"}
*/
// 6話
changeGroup(Newgene, 'left', Mio)
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "shiburin", type: "cool", status: "seek"}
 */
// 7話
changeGroup(Newgene, 'add', Mio)
changeGroupValue(Newgene, 'change', 'chanmio', 'status', 'motivated')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "shiburin", type: "cool", status: "seek"}
2: {nickname: "chanmio", type: "passion", status: "motivated"}
*/
// 21話
changeGroupValue(Newgene, 'change', 'chanmio', 'talent', 'acting')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "doMyBest"}
1: {nickname: "shiburin", type: "cool", status: "seek"}
2: {nickname: "chanmio", type: "passion", status: "motivated", talent: "acting"}
 */
// 22話
changeGroupValue(Newgene, 'change', 'shiburin', 'talent', 'song')
changeGroupValue(Newgene, 'change', 'shiburin', 'status', 'fun')
changeGroupValue(Newgene, 'delete', 'shimamu-', 'talent')
changeGroupValue(Newgene, 'change', 'shimamu-', 'status', 'dark')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "dark"}
1: {nickname: "shiburin", type: "cool", status: "fun", talent: "song"}
2: {nickname: "chanmio", type: "passion", status: "motivated", talent: "acting"}
*/
// 25話
changeGroupValue(Newgene, 'change', 'shimamu-', 'talent', 'smile')
changeGroupValue(Newgene, 'change', 'shimamu-', 'status', 'joy')
console.log(Newgene)
/**
0: {nickname: "shimamu-", type: "cute", status: "joy", talent: "smile"}
1: {nickname: "shiburin", type: "cool", status: "fun", talent: "song"}
2: {nickname: "chanmio", type: "passion", status: "motivated", talent: "acting"}
*/

# なぜconstを使うのか

# スコープを狭める

letも同じですが、スコープを狭くしておくことで変数の影響範囲がわかりやすくなり、開発がしやすくなります。

# 状態の把握が容易

letの場合は一度宣言しても値の再代入は可能です。そのため宣言後にも状態が変わります。 constの場合は上書きされることがないことが保証されているので現在の状態の推測が容易です。

# まとめ

最後まで読んでくださってありがとうございます。

  • グローバルスコープの汚染は避ける
  • これからJavaScriptを書く場合varは使用しない
  • 再代入が必要な場面以外はconstを使用する

ちなみに自分の好きなものを変数にすると記事を書くことが捗ることに気づきました。皆さんも良いお盆休みを