Rails new時の環境構築時エラー(Psych::BadAlias: Unknown alias: default)

発生している問題

  • rails *6.0.3.6* new プロジェクト名コマンドを実行後、rails aborted! Psych::BadAlias: Unknown alias: defaultのエラーが出現しました。

      $ rails _6.0.3.6_ new プロジェクト名
      ~省略~
      rails aborted!
      Psych::BadAlias: Unknown alias: default
      /Users/name/workspace/practice_app/プロジェクト名/bin/rails:9:in `<top (required)>'
      /Users/name/workspace/practice_app/プロジェクト名/bin/spring:15:in `require'
      /Users/name/workspace/practice_app/プロジェクト名/bin/spring:15:in `<top (required)>'
      ./bin/rails:3:in `load'
      ./bin/rails:3:in `<main>'
      Tasks: TOP => app:template
      (See full trace by running task with --trace)
    
  • lsコマンドで、フォルダが作成できているのかを確認したところRails newしたフォルダは問題なく作成出来ていました。

      $ ls
      プロジェクト名のフォルダ
    
  • その後、試しにRailsサーバーが立ち上がるかコマンドを実行してみました。すると、gem 'psych (~> 3.1)'が見つかりませんというエラーが出現しました。確認すると、gemfileには、psychというgemはありませんでした。

      $ rails s
      Could not find gem 'psych (~> 3.1)' in locally installed gems.
    
      The source contains the following gems matching 'psych':
        * psych-4.0.3
      Run `bundle install` to install missing gems.
    
  • 調べてみますと、Rubyには標準でpsychというYAML解釈用のGemがあります。
    しかし、4系列では解釈法が変わった(Psych.loadが内部でPsych.safe_loadを使用するように変更した)ため、今まで読み込むことができていたYAMLファイルが今後読み込みエラーになる可能性があるということです。3系列でないとdatabase.ymlの解釈に失敗することが考えられます。
    つまり、'psych (~> 3.1)'を使いましょうというエラーだったんですね。

    Ruby の YAML.load が非互換になる(かもしれない) - Secret Garden(Instrumental)

    エラーUnknown alias: defaultが出て、railsサーバを起動できない

解決法

  1. gemfileにgem 'psych', '~> 3.1’を追加後、bundle install

     # gemfile
     gem 'psych', '~> 3.1'
    

    実行後、

     $ bundle install
    
     ~省略~
     Installing psych 3.3.2 with native extensions
     Bundle complete! 18 Gemfile dependencies, 76 gems now installed.
     Use `bundle info [gemname]` to see where a bundled gem is installed.
    

    gem 'psych', '~> 3.1’を無事インストールできました。

  2. Railsサーバーを立ち上げると、上手く立ち上がりました!

     $ rails s
     => Booting Puma
     => Rails 6.0.4.7 application starting in development 
     => Run `rails server --help` for more startup options
     Puma starting in single mode...
     * Version 4.3.11 (ruby 3.1.1-p18), codename: Mysterious Traveller
     * Min threads: 5, max threads: 5
     * Environment: development
     * Listening on tcp://127.0.0.1:3000
     * Listening on tcp://[::1]:3000
    

参考

Ruby の YAML.load が非互換になる(かもしれない) - Secret Garden(Instrumental)

エラーUnknown alias: defaultが出て、railsサーバを起動できない

週刊Railsウォッチ(20210518後編)RubyのGCを深掘りする、Psych gemのbreaking change、11月のRubyConf 2021ほか|TechRacho by BPS株式会社

補足情報(フレームワークのバージョン・OS)

2022/03/14(月) JSprimer(演算子④-代入演算子-)

代入演算子(=)

複合代入演算子の例⏬

Image from Gyazo

MDN-代入演算子

// 加算代入の例
let num = 1;
num += 10; // num = num + 10; と同じ
console.log(num); // => 11
  • 初めにnum + 10のプラス演算が行われます。
  • 次に変数numにnum + 10の結果値が代入されます。

[ES2015] 分割代入(Destructuring assignment)

  • 分割代入は、配列またはオブジェクトの中身を複数の変数へ同時に代入できる構文です。
  • 短縮記法のひとつでES2015から導入されました。
  • JSでよく使われる2種類のデータ構造にObjectArrayがあります。さまざまなデータをまとめて扱えることは便利なのですが、時と場合によりオブジェクト・配列全体の値が必要でない場合があります。
    例えば、関数の引数としてオブジェクト・配列内の個々の値が使いたい場合などです。そのような場合に分割代入を活用すると、オブジェクト・配列内の使いたいデータだけを取り出すことができて便利です。
  • まとめると、配列または、オブジェクトのある特定の情報だけを参照して、変数に格納したい場合に用いられます。

配列を分割代入した例

const array = [1, 2];
// aには`array`の0番目の値、bには1番目の値が代入される。以下の代入式をまとめてできるのが分割代入です。
// const a = array[0];
// const b = array[1];
const [a, b] = array;
console.log(a); // => 1
console.log(b); // => 2
  • 要素が2つある配列arrayを定義します。
  • 次にarray配列内の値をそれぞれ変数a,bに分割代入しています。この時、変数aにはarray0番目の値が、変数bには1番目の値が代入されます。
  • 代入される要素の順番は配列のインデックス順です。

オブジェクトを分割代入した例

const obj = {
    "summer": "夏",
    "spring": "春", 
    "autumn": "秋"
};
// プロパティ名`summer`の値を、変数`summer`として定義する
const { spring } = obj;  // const summer = obj.summer;と同じ。キー名と同じ変数にキーと紐づく値を代入できる。
console.log(spring); // => 春

// プロパティ名`spring`の値を、変数`spring`として定義する
const { summer } = obj;
console.log(summer); // => 夏

// オブジェクトに未定義のプロパティ名を変数名として記載すると、対応する値がないのでundefinedが代入される
// const { winter = "冬"} = obj;でデフォルト値を設定できる。
const { winter } = obj;
console.log(winter);  // => undefined

// オブジェクトに定義済みのプロパティ名であったとしても変数名を{}オブジェクトリテラルで囲まないと、分割代入は出来ない。オブジェクト全体が代入されてしまうので注意。
const autumn = obj;
console.log(autumn);  // => { summer: '夏', spring: '春', autumn: '秋' }
  • {}オブジェクトリテラルを使って変数名を宣言することで、分割して変数に値が代入されます。
  • {}オブジェクトリテラルをを使わないと、オブジェクト全体が代入されてしまうので、注意が必要です。
  • オブジェクトの分割代入では、プロパティ名に対応する値が代入されます。そのため、変数名にはプロパティ名と一致する名前を指定するようにしましょう。
  • オブジェクトに存在しないプロパティ名を変数名として指定するとundefinedが代入されます。さらに=デフォルト値デフォルトの値を設定できます。
  • オブジェクトの分割代入を活用するメリットは2つあります。
    • 1つ目は、オブジェクトのプロパティ値をより直感的に扱えるという点です。summerでアクセス出来ることで、「あ、夏のデータにアクセスしているのね。」ということが一目で分かります。
    • 2つ目は、変数に代入できるので、記述が少なくなるという点です。obj .summerよりもsummerでアクセス出来る方が簡単です。

参考

JSPrimer

MDN-代入演算子

JAVASCRIPT.INFO-分割代入

【JS】分割代入の方法まとめ

朝会ブログ

2022/03/13(日) JSprimer(演算子③-ビット演算子-)

前回前々回の記事の続きです。

ビット演算子

ビット演算子では、オペランドである数値を符号付き32ビット整数(0と1からなる32個のビットの集合)として扱います。 符号付き32ビット整数では、先頭の最上位ビット(一番左のビット)は符号を表し、0の場合はの値、1の場合はの値であることを示しています。 符号付き32ビット整数ではの数値は、2の補数形式という形式で表現されます。 2の補数とは、それぞれのビットを反転して1ビットを足した値となります。

  • オペランド32ビット整数に変換され、連続したビット(0と1)によって表現されます。(ビット演算子で計算されるオペランドは、符号付き32ビット整数に変換)
  • Aというオペランドの各ビットは、Bというオペランドの各ビットと対応して計算されます。つまり、Aというオペランドの1つ目のビットはBというオペランドの1つ目のビットといったようにペアごとに計算が行われるということです。
  • 演算結果は、10進数の数値に変換して返します。

符号付き32ビット整数への変換は、以下の2つの例を参考に解説していきます。

例①
10進数での1という数値は、符号付き32ビット整数のビット(32桁の2進数の数値)で表すと以下のようになります。

  • 10進数での1という数値は0000_0000_0000_0000_0000_0000_0000_0001 のような32ビットの集合になります。
  • 先頭の最上位ビット(一番左のビット)は符号を表しているため、正の数を表す0が付いています。

例②
10進数での-1という数値は、符号付き32ビット整数のビット(32桁の2進数の数値)で表すと以下のようになります。
マイナスの数値をビットで表すときは2の補数形式を使います。 1. 表したいマイナスの数値に対応する正の数値の各ビット(0か1の数値)を反転させる 2. 1ビットを足す

  • 10進数の1は、符号付き32ビット整数では0000_0000_0000_0000_0000_0000_0000_0001となる
  • 0000_0000_0000_0000_0000_0000_0000_0001 の各ビットを反転(0を1に、1を0に反転)すると 1111_1111_1111_1111_1111_1111_1111_1110 となる
  • これに1ビットを足すと 1111_1111_1111_1111_1111_1111_1111_1111 となる
  • 最終的に-1符号付き32ビット整数1111_1111_1111_1111_1111_1111_1111_1111となります。

そもそもビット演算がわからなかったので、深掘りしてみました🔍

ビット演算とは?

ビット演算とは、主にコンピュータ上で行われる演算の一つで、対象データをビット列(2進数の0と1の羅列)とみなして、ビットの移動やビット単位の論理演算などを行うもの。
e-Words-ビット演算 【bitwise operation】

  • コンピューターの世界では、0か1の数値を複数組み合わせて、さまざまなデータを扱っています。これは、0か1の数値しかコンピューターが判別でき無いからです。
  • この0か1が入る箱のことをビットと呼びます。例えば4ビットというと、0か1が入る箱が4つあるということです。また、8ビット集まると、その箱の塊のことをバイト(byte)と呼ぶようになります。8ビットは、1バイトです。
  • 格納できる数値が2つしか無いからこそたくさん箱を組み合わせることで複雑なデータを扱うことが出来ます。
  • 0か1の2つの数値を扱うことから2進数を使って表すとも言います。私たち人間が普段扱うことが多いのは、0〜9までの10つの数値を扱う10進数です。
  • 0か1の数値の塊を計算する演算子のことをビット演算子と言います。ビット演算の計算は、0か1の数値しかないので、1であれば0に、0であれば1にスイッチする計算方法になります。電気のONとOFFを切り替えることをイメージすると分かりやすいです。
    「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典-ビット演算

❶ビット論理積(&)

ビット論理積演算子(&)はビットごとのAND演算した結果を返します。 AND演算では、オペランドの各ビットがどちらも1の場合は1となり、それ以外の場合は0となります。

  • AND演算では、aとbが両方とも1の時のみ、1を返します。それ以外は、0を返します。

Image from Gyazo

console.log(15     & 9);      // => 9
// 同じ位の各ビット同士をAND演算する(上位の`0`は省略)
// 1111
// 1001
// ----
// 1001
console.log(0b1111 & 0b1001); // => 0b1001
  • 上記のコードでは、10進数の15と9をAND演算しています。
  • 15は、符号付き32ビット整数では0000_0000_0000_0000_0000_0000_0000_1111となります。
  • 9は、符号付き32ビット整数では0000_0000_0000_0000_0000_0000_0000_1001となります。
  • これらをAND演算した結果は0000_0000_0000_0000_0000_0000_0000_1001となり、10進数の値である9を返します。

❷ビット論理和(|)

ビット論理和演算子(|)はビットごとのOR演算した結果を返します。 OR演算では、オペランドの各ビットがどちらか片方でも1の場合は1となり、両方とも0の場合は0となります。

  • OR演算では、aまたはbのどちらかが1であれば1を返し、aまたはbのどちらも0である場合にのみ0を返します。 Image from Gyazo

    ❸ビット排他的論理和(^)

    ビット排他的論理和演算子(^)はビットごとのXOR演算した結果を返します。 XOR演算では、オペランドのビットが異なるなら1、両方とも同じなら0となります。

  • XOR演算は、aとbの2つのビットを比較して同じ(0同士か1同士)だったら0、違ったら1を返します。 Image from Gyazo

❹ビット否定(~)

各ビットに対して、NOT演算を実行します。 NOT aは、aを反転します。

  • NOT演算は、aという1つのビットを比較してaが0の時に1を、0の時に1のように値を反転させて返します。 Image from Gyazo

ビット否定演算子(~)の性質を利用したindexOfメソッド

文字列(Stringオブジェクト)が持つindexOfメソッドは、マッチする文字列を見つけて、そのインデックス(位置)を返すメソッドです。 このindexOfメソッドは、検索対象が見つからない場合には-1を返します。

const str = "森森本森森";
// 見つかった場合はインデックスを返す
// 文字列str内に本と一致する文字列が見つかったので、その発見位置(インデックス番号)を返す
console.log(str.indexOf("本")); // => 2
// 見つからない場合は-1を返す
console.log(str.indexOf("火")); // => -1

ES2015では、文字列(Stringオブジェクト)にincludesメソッドが実装されました。 includesメソッドは指定した文字列が含まれているかを真偽値で返します。

const str = "森森木森森";
if (str.includes("木")) { // インデックスではなく、真偽値で結果を返す
    console.log("木を見つけました");
}

❺左シフト演算子(<<)

左シフト演算子は、数値であるnumをbitの数だけ左へシフトします。 左にあふれたビットは破棄され、0のビットを右から詰めます。

num << bit;

❻右シフト演算子(>>)

右シフト演算子は、数値であるnumをbitの数だけ右へシフトします。 右にあふれたビットは破棄され、左端のビットのコピーを左から詰めます。 左端のビットのコピーを使うため、常に符号は維持されます。

num >> bit;

❼ゼロ埋め右シフト演算子(>>>)

ゼロ埋め右シフト演算子は、数値であるnumをbitの数だけ右へシフトするのは右シフト演算子(>>)と同じです。 異なる点としては右にあふれたビットは破棄され、0のビットを左から詰めます。 左端のビットは0となるため、常に正の値となります。

num >>> bit;

まとめ

Image from Gyazo

参考

JSPrimer-ビット演算子

朝会ブログ

js STUDIO-ビット演算子

「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典-ビット演算

e-Words-ビット演算 【bitwise operation】

2022/03/12(土) JSprimer(演算子②~比較演算子~)

前回の記事の続きです。

比較演算子

❶厳密等価演算子(===)

厳密等価演算子は、左右の2つのオペランドを比較します。 同じ型で同じ値である場合に、trueを返します。

// 同じ型で同じ値である場合にtrueを返す
console.log(1 === 1); // => true
console.log(1 === "1"); // => false
  • 比較するオペランドがどちらもオブジェクトの場合、 オブジェクトの参照が同じであれば、trueを返します。
  • 下記の例では、空のオブジェクトリテラル({})同士を比較しています。
  • オブジェクトリテラルは、その都度、新しいオブジェクトを作成します。
  • つまり、オブジェクトリテラルは、異なる参照のオブジェクトを毎回作成するということです。
  • そのため、異なるオブジェクトを参照する変数を===で比較するとfalseを返します。
// {} は新しいオブジェクトを作成している
const objA = {};
const objB = {};
// 生成されたオブジェクトは異なる参照となる
console.log(objA === objB); // => false
// 同じ参照を比較している場合
console.log(objA === objA); // => true


// objAとBどちらもobject型
console.log(typeof objA);  // => object
console.log(typeof objB);  // => object

❷厳密不等価演算子(!==)

厳密不等価演算子は、左右の2つのオペランドを比較します。 異なる型または異なる値である場合に、trueを返します。

// 異なる型か異なる値である場合に、trueを返す
console.log(1 !== 1); // => false
console.log(1 !== "1"); // => true

❸等価演算子(==)

  • 等価演算子==)は、2つのオペランドを比較します。
  • 同じデータ型のオペランドを比較する場合は、厳密等価演算子===)と同じ結果になります。
  • しかし、等価演算子==)はオペランド同士が異なる型の値であった場合に、 同じ型となるように暗黙的な型変換が行われてから比較します。
  • この暗黙的な型変換は、バグを引き起こすことがあるため、注意が必要です。
  • 等価演算子(==)は、nullundefinedを比較する時にだけ使うようにしましょう。
  • その他の比較の際は厳密等価演算子を使うことを推奨します。
// 同じ型同じ値の場合はtrueを返す
console.log(1 == 1); // => true
console.log("str" == "str"); // => true

// 同じ型で異なる値の場合はfalseを返す
console.log("JavaScript" == "ECMAScript"); // => false

// オブジェクトは参照が一致しているならtrueを返す
// {} は新しいオブジェクトを作成している
const objA = {};
const objB = {};
console.log(objA == objB); // => false(異なる参照)
console.log(objA == objA); // => true(参照が一致)

比較するオペランドの型が異なり、暗黙的な型変換が行われる場合⏬

// 文字列を数値に変換してから比較
console.log(1 == "1"); // => true
// "01"を数値にすると`1`となる
console.log(1 == "01"); // => true
// 真偽値を数値に変換してから比較
console.log(0 == false); // => true
// nullの比較はfalseを返す
console.log(0 == null); // => false
// nullとundefinedの比較は常にtrueを返す
console.log(null == undefined); // => true

比較したいオペランドが null または undefined であることを判定したい場合に唯一等価演算子(==)が使われる場合⏬

  • そもそもundefinednullは大きなくくりで「値がない」ことを表現します。
  • そのため、nullまたはundifinedのどちらかが入っていれば値がないことを判定できます。
  • しかし、厳密にはundefinednullで型が異なるなどの違いがあるため、厳密等価演算子===)を使って比較すると、それぞれと比較する必要が出てきてしまいます。
  • 結果的に2回比較する必要が出てきてしまうため、等価演算子(==)を使って1回の比較で済むようにします。
const value = undefined; /* または null */
// === では2つの値と比較しないといけない
if (value === null || value === undefined) {
    console.log("valueがnullまたはundefinedである場合の処理");
}

// nullとundefinedの比較は常にtrueを返すことを利用して
console.log(null == undefined); // => true
// == では null と比較するだけでよい
if (value == null) {
    console.log("valueがnullまたはundefinedである場合の処理");
}

💡nullとundefinedの違い

undefinedとnullは大きなくくりで「値がない」ことを意味する点は共通しています。意味的な違いがあるとしたら、undefinedは「値が代入されていないため、値がない」、nullは「代入すべき値が存在しないため、値がない」という微妙な違いです。 サバイバルTypeScript - undefinedとnullの違い

  • 意図的に値がないことを示すために変数などに代入する値が null
  • プログラマーが明示的に使わなくても自然発生的に値が代入されていないことを意味するのが undefined
  • typeOf演算子を使うと、nullobject型undefinedundefined型と判定されます。
console.log(typeof undefined);
//=> "undefined"
console.log(typeof null);
//=> "object"
  • undefined変数であり、リテラルではないので、undefinedという変数を宣言できます。
  • nullリテラルであるため、nullという変数を宣言できません。

❹不等価演算子(!=)

  • 不等価演算子!=)は、2つのオペランドを比較し、等しくないならtrueを返します。
  • 不等価演算子も、等価演算子==)と同様に異なる型のオペランドを比較する際に、暗黙的な型変換をしてから比較します。
  • 暗黙的な型変換は意図しない挙動を生む可能性があるため、不等価演算子!=)を利用するべきではありません。
  • 代わりに暗黙的な型変換をしない厳密不等価演算子!==)を利用します。
// 比較するオペランドが同じ型で同じ値の場合はfalseを返す
console.log(1 != 1); // => false
console.log("str" != "str"); // => false

// 比較するオペランドが同じ型で異なる値の場合はtrueを返す
console.log("JavaScript" != "ECMAScript"); // => true
console.log(true != true);// => false

// オブジェクトは参照が一致していないならtrueを返す
const objA = {};
const objB = {};
console.log(objA != objB); // => true(オブジェクトの参照が不一致)
console.log(objA != objA); // => false(オブジェクトの参照が一致)

比較するオペランドの型が異なり、暗黙的な型変換が行われる場合⏬

// 文字列が数値に数値に変換されて、値が1で一致するためfalse
console.log(1 != "1"); // => false
// 真偽値を数値に変換してから比較。今回は、一致するのでfalse
console.log(0 != false); // => false
// nullの比較は一致しないのでtrueを返す
console.log(0 != null); // => true
// nullとundefinedの比較は常に一致するのでfalseを返す
console.log(null != undefined); // => false

参照

JSPrimer

朝会ブログ

サバイバルTypeScript - undefinedとnullの違い

2022/03/8~ 3/11JSprimer(演算子①)

演算子

// +演算子で値同士を足し算する加算演算を行う
// この場合、1と2の2つの値がオペランド、演算する対象です。
1 + 2;

二項演算子

// 二項演算子とオペランドの関係
左オペランド 演算子 右オペランド

プラス演算子(+)

  • 2つの数値を加算する演算子です。
console.log(1 + 1); // => 2
// 数値型の10を明示的に浮動小数点型に変換せずとも計算できてしまう。
// それは、数値型の値が内部的に浮動小数点数として表現されているからである。
console.log(10 + 0.5); // => 10.0 + 0.5 = 10.5

文字列結合演算子(+)

  • 数値の加算に利用したプラス演算子(+)は、文字列同士の結合に利用できます。
  • 文字列結合演算子(+)は、2つの文字列を結合した文字列を返します。
  • 文字列と数値の加算が行われるときは、数値が暗黙的に文字列に変換されます。
const value = "文字列" + "結合";
console.log(value); // => "文字列結合"

// 文字列と数値をプラス演算子で計算すると、数値の暗黙的な変換が行われ文字列に変換されます。
// 文字列に変換された数値と文字列を結合したものを返します。
console.log("文字列" +  1);  // => 文字列1

[ES2016] べき乗演算子(**)

2つの数値のべき乗(2の4乗などの計算)を求める演算子です。 左オペランドを右オペランドでべき乗した値を返します。

// べき乗演算子(ES2016)で2の4乗を計算
console.log(2 ** 4); // => 16
  • べき乗演算子と同じ動作をするMath.powメソッドがあります。
console.log(Math.pow(2, 4)); // => 16
  • べき乗演算子はES2016で後から追加された演算子であるため、関数演算子の両方が存在しています。

単項演算子(算術)

let num = 1;

//どちらもnum += 1;と同じ意味
num++; // 1つのオペランドを用いて評価をしているので、単行演算子
// または
++num; // 1つのオペランドを用いて評価をしているので、単行演算子

❶単項プラス演算子(+)

  • 単項演算子+は数値や数値以外のオペランド数値に変換する演算子です。
  • 数値に変換できない文字列などはNaNという特殊な値へと変換されます。
// 数値の1を数値へ変換する
console.log(+1); // => 1

// 数字(文字列)を数値へ変換
console.log(+"1"); // => 1

// 数値ではない文字列はNaNという値に変換される
console.log(+"文字列"); // => NaN
  • 以上のことから、単項プラス演算子は、数値への変換を行うことが可能です。
  • しかし、単項プラス演算子は文字列から数値への変換に使うべきではありません。
  • なぜなら、数値への変換は、Numberコンストラクタ関数やparseInt関数を使う明示的な変換方法が存在するためです。

NaNとは?

NaNは"Not-a-Number"の略称で、数値ではないがNumber型の値を表現しています。 NaNはどの値とも(NaN自身に対しても)一致しない特性があり、Number.isNaNメソッドを使うことでNaNの判定を行えます。

  • NaNは、数値ではないNumber型の値を表現するものです。
  • NaNは自分自身も含むどの値とも一致しない特性があり、厳密等価演算子を使ってもfalseしか返りません。
  • NaNかどうかの判定を行う場合は、厳密等価演算子ではなくNumber.isNaNメソッドを使いましょう。
// 自分自身とも一致しない(厳密等価演算子では一致しない)
console.log(NaN === NaN); // => false
// Number型である
console.log(typeof NaN); // => "number"
// Number.isNaNでNaNかどうかを判定
console.log(Number.isNaN(NaN)); // => true

❷単項マイナス演算子(-)

  • 単項マイナス演算子はマイナスの数値を表現するために使用されます。
  • 例えば、-1 と書くとマイナス1を意味します。
  • 単項マイナス演算子マイナスの数値を反転できます。 そのため、"マイナスのマイナスの数値"はプラスの数値となります。
  • 文字列などを数値へ変換することができます。ただし、NaNという特殊な値に変換される場合もあり、文字列から数値への変換のために使われる事は推奨されません。
  • 数値へ変換できない文字列などをオペランドに指定した場合は、NaNという特殊な値に変換されます。
// 単項マイナス演算子を使って、マイナスな数値を表現
console.log(-1); // => -1

// "マイナスのマイナスの数値"をプラスの数値に符号を反転させる
console.log(-(-1)); // => 1

// 文字列の1を数値へ変換
console.log(-"1"); // => -1

// 数値へ変換できない文字列などのオペランドを変換しようとすると、NaNという特殊な値になる
console.log(-"文字列"); // => NaN

❸インクリメント演算子(++)

let num = 1;
num++;
console.log(num); // => 2
// 次のようにした場合と結果は同じ
// num = num + 1;

後置インクリメント演算子num++)の評価の順番 1. 評価する前の値を返す 2. 値に対して+1の評価をする - そのため、num++の返り値は、+1と評価する前の値になります。値が返された後に+1が行われます。

// num++で、出力される値は、+1する前の値
let num = 1;
console.log(num++);  // => 1
console.log(num);  // => 2

前置インクリメント演算子(++num)の評価の順番 1. 値に対して+1の評価する 2. +1した評価結果を返す - ++numが返す値は+1した後の値です。

let x = 1;
console.log(++x); // => 2
console.log(x);   // => 2

❹デクリメント演算子(--)

// 後置デクリメント演算子(値が返されてから、-1の評価が行われる)
let x = 1;
console.log(x--); // => 1
console.log(x);   // => 0

// 前置デクリメント演算子(-1の評価がされてから値が返る)
let y = 1;
console.log(--y); // => 0
console.log(y);   // => 0

参照

JSPrimer

2022/03/4(金)~03/7(月) JSprimer(データ型とリテラル)

はじめに

プログラミング言語には、動的型付け言語と静的型付け言語という2種類のタイプがあります。

動的言語と静的言語の違い

  • 両者の違いは、あらかじめデータ型を決めるか(宣言する)どうかという部分です。

動的型付け言語

言語変数などのデータ型の宣言がいらない以下のようなプログラミング言語のことです。

例) Python Ruby JavaScript PHP など

静的型付け言語

変数などのデータ型の宣言があらかじめ必要なプログラミング言語のことです。以下のような言語が当てはまります。

例) C/C++ C# Java Go Scala Swift など

データ型

JavaScriptは動的型付け言語に分類される言語であるため、静的型付け言語のような変数の型はありません。 しかし、文字列、数値、真偽値といった値の型は存在します。 これらの値の型のことをデータ型と呼びます。 データ型を大きく分けると、プリミティブ型とオブジェクトの2つに分類されます。

  • JSは、動的型付け言語であるため変数に型をつけることはありませんが、変数に代入される値には型が存在します。
  • JSのデータ型は、プリミティブ型オブジェクトの2つがあります。

プリミティブ型

  • プリミティブ型(基本型)は、最初から用意されている基本的な値の型のことです。
  • プリミティブ型の値は、一度作成したらその値自体を変更できないというイミュータブルimmutable)の特性を持ちます。
  • イミュータブルは、オブジェクトが作成された後は状態を変更できないオブジェクトです。
  • 値を変更できないというのは、メソッドや関数を使って値を直接操作、変更したりすることができないという意味です。
  • ただし、プリミティブ値を代入することで新しい値に置き換えることは可能です。その場合も値の状態が変わった訳ではありません。変数自体が他の参照に代わるだけです。
  • プリミティブ型基本型
    • 真偽値(Boolean): trueまたはfalseのデータ型
    • 数値(Number): 42 や 3.14159 などの数値のデータ型
    • 巨大な整数(BigInt): ES2020から追加された9007199254740992nなどの任意精度の整数のデータ型
    • 文字列(String): "JavaScript" などの文字列のデータ型
    • undefined: 値が未定義であることを意味するデータ型
    • null: 値が存在しないことを意味するデータ型
    • シンボル(Symbol): ES2015から追加された一意で不変な値のデータ型

Primitive (プリミティブ)

イミュータブルな値の例⏬

// 文字列値を持つ新しいオブジェクトが作成されます。文字列はイミュータブル(不変)な値です。
var immutableString = "Hello";

console.log(immutableString);  //=> Hello

// 今、既存の値に "World" を追加しています。(ここで重要なのは、文字列HelloにWorldを追加した新しい文字列を作成していることです。)
immutableString = immutableString + "World";

// 確かに、既存の値にworldを追加した値が出力されるが、これは既存の値を変更したものではなく、新しい値を作成したものを参照しています。
console.log(immutableString); //=> HelloWorld
  • ここで重要なのは、"Hello"という既存の文字列を変更しているわけではないことです。
  • 既存の文字列を参照する変数immutableStringと" World"を連結して新たな文字列「"Hello World"」を生成しimmutableStringに再代入しています。
  • この時、新たに作成された文字列は新しいメモリー空間を参照することになります。ここからも既存の値自体が変更されたわけではないことが分かります。
  • 既存の値が参照されていたメモリーは使わなくなるため、ガベージコレクションに追加されます。
  • まとめると、イミュータブルな値は変更できません。上記の例は、一見値の変更が行われているように見えます。しかし、実際は変更されているのではなく、変更が加えられた新しいオブジェクトが作成されているだけなのです。(元の値は、変わっていません。)

  • 元の値を変更したい時使われるのは、既存の値ではなくそれをコピーして参照した値になります。こうすることで、元の値に影響が及ぶことはありません。

ミュータブルな型とイミュータブルな型の相違を知ろう

JavaScriptでイミュータブルなプログラミングをする

オブジェクト

  • オブジェクトは複数のプリミティブ型の値またはオブジェクトからなる値の集合のことです。
  • オブジェクトは、一度作成した後もその値自体を変更できるためミュータブルmutable)の特性を持ちます。
  • オブジェクトは、値そのものではなく値への参照を経由して操作されるため、参照型のデータとも言います。
  • プリミティブ型以外のデータをオブジェクトであると覚えていれば問題ありません。

Mutable (ミュータブル)

  • オブジェクト(複合型)
    • プリミティブ型以外のデータ
    • オブジェクト、配列、関数、正規表現、Dateなど

  • typeof演算子を使うことで、値のデータ型を調べることができます。
  • typeof演算子は、基本的にプリミティブ型またはオブジェクトかを判別するものです。
  • typeof nullが"object"となるのは、歴史的経緯のある仕様のバグです。本来は、nullと出力されるべきです。

typeof演算子

console.log(typeof true);// => "boolean"
console.log(typeof 42); // => "number"
console.log(typeof 9007199254740992n); // => "bigint"
console.log(typeof "JavaScript"); // => "string"
console.log(typeof Symbol("シンボル"));// => "symbol"
console.log(typeof undefined); // => "undefined"
console.log(typeof null); // => "object"
console.log(typeof ["配列"]); // => "object"
console.log(typeof { "key": "value" }); // => "object"
console.log(typeof function() {}); // => "function"

リテラル

  • リテラルとはプログラム上で数値や文字列など、データ型の値を直接記述できるように定義された構文のことです。
  • リテラル表現がない場合は、その値を作る関数(コンストラクタ関数など)に引数を渡して作成する形になります。これでは大変冗長なため、よく使われる値にはリテラルが用意されています。

  • リテラル表現を持つ4つのプリミティブ型

    • 真偽値
    • 数値
    • 文字列
    • null
  • リテラル表現が用意されているオブジェクト

リテラル表現を持つプリミティブ型①:真偽値(Boolean)

真偽値にはtruefalseリテラルがあり、trueとfalseの値を返します。

true; // => true
false; // => false

リテラル表現を持つプリミティブ型②:数値(Number)

数値には42のような整数リテラルと3.14159のような浮動小数点数リテラルがあります。

❶整数リテラル

整数リテラルには次の4種類があります。 Image from Gyazo

  • 10進数:0から9の数字の数字の組み合わせ
console.log(1); // => 1
console.log(10); // => 10
console.log(255); // => 255
  • 2進数:0bからはじまり、数値の0と1で表現される2進数リテラルは、ビットを表現するのによく利用されています。 bは2進数を表すbinaryを意味しています。
console.log(0b1111); // => 15
console.log(0b10000000000); // => 1024
  • 8進数: 0o(または0O)から始まり、0から7までの数字の組み合わせで表現される。ファイルのパーミッションを表現するのによく利用されます。
console.log(0o644);  // => 420
console.log(0o777);  // => 511
  • 16進数: 0xからはじまり、0から9までの数字とaからfまたはAからFのアルファベットの組み合わせで表現されます。文字のコードポイントやRGB値の表現などに利用されています。
console.log(0xFF); // => 255
// 小文字で書いても意味は同じ
console.log(0xff); // => 255
console.log(0x30A2); // => 12450

浮動小数リテラル

  • .ドット)を含んだ数値
// 0からはじまる浮動小数点数は、0を省略できる
.123; // => 0.123
  • eまたはEを含んだ数値
// eは指数(exponent)を意味する記号です。今回は、2の8乗なので、e8と記述します。
2e8; // => 200000000

上記を含む数値を浮動小数リテラルとしてみなします。

リテラル表現を持つプリミティブ型③: [ES2020] BigIntとは?

bigint型は、数値型よりも大きな整数を扱えるプリミティブ型です。 bigint型のリテラルは整数値の末尾にnをつけて書きます。 サバイバルTypeScript

  • 数値リテラルが正確に扱える数値の最大値は253-1(2の53乗から1引いた値)です。
  • 数値リテラルで253-1(9007199254740991)よりも大きな値を表現したり計算すると間違った結果となる場合があります。
  • その問題を解決するためにES2020でBigIntという新しい整数型のデータ型とリテラルが追加されました。
  • BigIntでは253-1(9007199254740991)よりも大きな整数を正しく表現できます。
// 数値の後ろにnをつけます。(数値は、整数のみ)
console.log(1n); // => 1n
// 2^53-1より大きな値も扱える
console.log(9007199254740992n); // => 9007199254740992n

// BigIntは、整数を扱うデータ型であるため、小数点を含めた場合は構文エラー
1.2n; // => SyntaxError

[ES2021] Numeric Separatorsとは?

ES2021から、数値リテラル内の区切り文字としてを追加できるNumeric Separatorsがサポートされています。 Numeric Separatorsは、数値リテラル内では区切り文字としてが追加できます。

1_000_000_000_000;

リテラル表現を持つプリミティブ型④: 文字列(String)

文字列リテラル共通のルールとして、同じ記号で囲んだ内容を文字列として扱います。 文字列リテラルとして次の3種類のリテラルがありますが、その評価結果はすべて同じ"文字列"になります。

  • ""または、''で囲んだ範囲が文字列リテラルになります
  • "(ダブルクォート)と'(シングルクォート)、バッククォートの3種類どちらで記述してもまったく同じ意味として評価されます。
console.log("文字列"); // => "文字列"
console.log('文字列'); // => "文字列"
console.log(`文字列`); // => "文字列"

ダブルクォートとシングルクォート

  • "(ダブルクォート)と'(シングルクォート)はまったく同じ意味となります。
  • もちろん、評価結果も同じです。
  • シングルクォートとダブルクォートの文字列リテラル改行を入れるには、エスケープシーケンスを使わないと構文エラーになってしまいます。
 // 文字列リテラルは同じ記号で囲む必要があるため、次のように文字列の中に同じ記号が出現した場合は、 \'のように\(バックスラッシュ)を使ってエスケープしなければなりません。
'8 o\'clock'; // => "8 o'clock"

// 文字列内部に出現しない別のクォート記号を使うことで、エスケープをせずに書くこともできます。
"8 o'clock"; // => "8 o'clock"


// ダブルクォートとシングルクォートどちらも、改行をそのままでは入力できないため、構文エラー(SyntaxError)となります。
"複数行の
文字列を
入れたい"; // => SyntaxError: "" string literal contains an unescaped line break

// 改行の代わりに改行記号のエスケープシーケンス(\n)を使うことで複数行の文字列を書くことができます。
"複数行の\n文字列を\n入れたい";

[ES2015] テンプレートリテラル

テンプレートリテラルは、`(バッククォート)で囲んだ範囲を文字列とするリテラルです。

  • テンプレートリテラルでは、複数行の文字列を改行記号のエスケープシーケンス(\n)を使わずにそのまま書くことができます。
// 改行を含む文字列をテンプレートリテラルで囲むだけで文字列として認識される
`複数行の
文字列を
入れたい`; // => "複数行の\n文字列を\n入れたい"
  • テンプレートリテラル内で${変数名}と書いた場合に、その変数の値を埋め込むことができます。
const str = "文字列";
console.log(`これは${str}です`); // => "これは文字列です"

リテラル表現を持つプリミティブ型⑤:nullリテラル

nullリテラルはnull値を返すリテラルです。 nullは「値がない」ということを表現する値です。

// null値を持つfooという変数を定義=>fooを値がない変数として定義し、参照できる
const foo = null;
console.log(foo); // => null

true、false、nullなどはグローバル変数ではなくリテラルであるため、同じ名前の変数を定義することはできません。 リテラルは変数名として利用できない予約語のようなものであるため、再定義しようとすると構文エラー(SyntaxError)となります。

  • true、false、nullは、リテラルとして定義されているため、変数名として利用できない予約語に設定されています。

リテラル表現が用意されているオブジェクト①: オブジェクトリテラル

オブジェクトはあらゆるものの基礎となります。 そのオブジェクトを作成する方法として、オブジェクトリテラルがあります。 オブジェクトリテラルは{}(中カッコ)を書くことで、新しいオブジェクトを作成できます。

オブジェクトリテラルはオブジェクトの作成と同時に中身を定義できます。 オブジェクトのキーと値を:で区切ったものを {} の中に書くことで作成と初期化が同時に行えます。 キー名には、文字列またはSymbolを指定し、値にはプリミティブ型の値からオブジェクトまで何でも入れることができます。

  • オブジェクトリテラル{}を使って、オブジェクトを作成することができます。
  • オブジェクト自体は、キーと値の組み合わせで記述していきます。
const obj = {}; // 中身が空のオブジェクトを作成


// オブジェクトの作成と初期化を同時に行う
// key というキー名と value という値を持つオブジェクトを作成
const obj = {
    "key": "value"
};
  • オブジェクトが持つキーのことをプロパティ名と呼びます。
  • プロパティを参照するには、.ドット)でつないで参照する方法と、 []ブラケット)で参照する方法があります。
const obj = {
    "key": "value"
};
// ドット記法でkeyプロパティの値を参照
console.log(obj.key); // => "value"

// ブラケット記法でkeyプロパティの値を参照
console.log(obj["key"]); // => "value"

リテラル表現が用意されているオブジェクト②: 配列リテラル

配列リテラルは[と]で値をカンマ区切りで囲み、その値を持つArrayオブジェクトを作成します。 配列(Arrayオブジェクト)とは、複数の値に順序をつけて格納できるオブジェクトの一種です。

配列は0からはじまるインデックス(添字)に、対応した値を保持しています。 作成した配列の要素を取得するには、配列に対してarray[index]という構文で指定したインデックスの値を参照できます。

  • 配列リテラル[]を使うと、配列オブジェクトを作成できます。

  • 配列は0からはじまるインデックス(添字)に、対応した値を保持しているため、値の取得の際は値に対応しているインデックス番号を指定します。

const emptyArray = []; // 空の配列を作成
const array = [1, 2, 3]; // 値を持った配列を作成

// 0番目の要素を参照(インデックス番号で指定)
console.log(array[0]); // => 1
// 1番目の要素を参照(インデックス番号で指定)
console.log(array[1]); // => 2

リテラル表現が用意されているオブジェクト③: 正規表現リテラル

JavaScript正規表現リテラルで書くことができます。 正規表現リテラルは/(スラッシュ)と/(スラッシュ)で正規表現のパターン文字列を囲みます。

  • /(スラッシュ)と/(スラッシュ)で正規表現のパターン文字列を囲むと正規表現リテラルとして表現できます。
  • 実際に囲まれている部分は、正規表現のパターン文字列として認識されます。
// 数字にマッチする特殊文字である\dを使い、1文字以上の数字にマッチする正規表現をリテラルで表現しています。

const numberRegExp = /\d+/; // 1文字以上の数字にマッチする正規表現(バックスラッシュで囲んでいる正規表現リテラル)
// `numberRegExp`の正規表現が文字列"123"にマッチするかをテストする
console.log(numberRegExp.test("123")); // => true

プリミティブ型とオブジェクト

  • 真偽値(Boolean)、数値(Number)、文字列(String)などのプリミティブ型はそれぞれリテラル以外にオブジェクトとして表現する方法もあります。
  • それは、ラッパーオブジェクトを使う方法です。
  • 本来オブジェクト型でないプリミティブ型の値(undefined と null を除く)をそれぞれ対応したラッパーオブジェクトで包んであげることで、あたかもオブジェクトのようにプロパティを参照したりメソッドを実行したりすることができるようになります。
  • ラッパーオブジェクトは、new演算子と対応するコンストラクタ関数を利用して作成します。
  • しかし、プリミティブ型の値は、必要に応じて自動的に対応するラッパーオブジェクトに変換されます。
  • そのため、明示的にラッパーオブジェクトを作成する処理を記述する必要はありません。
  • ラッパーオブジェクトのメソッドやプロバティを使用する際は、プリミティブ型のデータに対して直接メソッドを呼び出したりプロパティを参照するようにしましょう。

new演算子を使って、ラッパーオブジェクトを作成する場合(非推奨)

// 文字列をラップしたStringラッパーオブジェクト
const str = new String("文字列");
// ラッパーオブジェクトは"object"型のデータ
console.log(typeof str); // => "object"
// Stringオブジェクトの`length`プロパティは文字列の長さを返す
console.log(str.length); // => 3

ラッパーオブジェクトに自動的に変換される仕組みを利用して、直接プリミティブ型の値にラッパーオブジェクトのメソッドやプロパティを使用する場合(推奨)

// プリミティブ型の文字列データ
const str = "文字列";
// プリミティブ型の文字列は"string"型のデータ
console.log(typeof str); // => "string"
// プリミティブ型の文字列も`length`プロパティを参照できる
console.log(str.length); // => 3

ラッパーオブジェクトとプリミティブ型

参照

JSPrimer

ミュータブルな型とイミュータブルな型の相違を知ろう

JavaScriptでイミュータブルなプログラミングをする

動的言語と静的言語の違い

Mutable (ミュータブル)

typeof演算子

サバイバルTypeScript

ラッパーオブジェクトとプリミティブ型

2022/03/3(木) JSprimer(変数と宣言)

変数と宣言

プログラミング言語には、文字列や数値などのデータに名前をつけることで、繰り返し利用できるようにする変数という機能があります。

  • データを繰り返し利用できるようにする仕組みに変数があります。
  • JSでは、変数を宣言するキーワードとしてconst、let、varの3つがあります。
  • varは、昔からあった変数宣言のキーワードですが、意図しない挙動をしやすいという問題がありました。
  • それらの問題を改善するために作られたのが、ECMAScript 2015で導入されたconstletです。
  • varは、問題点もありますが、後方互換のために仕様を変更することはありません。その代わりに、constとletが追加されました。

[ES2015] const

  • const再代入できない変数を宣言するキーワードです。(全てが定数になる訳ではありません。)
  • そのため、constキーワードで宣言した変数に対して、後から値を代入しようとするとTypeErrorが発生します。
  • 変数に対して値を再代入する必要がない場合は、constキーワードで変数宣言するようにしましょう。
  • また、変数の宣言時に必ず初期値を入れるようにしましょう。
// const 変数名 = 初期値;(変数宣言)
const bookTitle = "JavaScript Primer";

// カンマを使って、複数の変数を同時に定義(複数の変数を宣言)
const bookTitle = "JavaScript Primer",
      bookCategory = "プログラミング";

// constで定義した変数に値を再代入しようとするとTypeErrorが発生!
const bookTitle = "JavaScript Primer";
bookTitle = "新しいタイトル"; // => TypeError: invalid assignment to const 'bookTitle'

[ES2015] let

  • constと同じくECMAScript 2015で導入されたletですが、値の再代入が可能な変数を宣言できます。
  • 変数宣言時に初期値を指定しない場合、デフォルト値としてundefined(未定義)という値で初期化されます。(constと異なり、変数宣言時に初期化が必須ではありません。)
// 変数宣言時に初期値を指定しない場合は、デフォルト値としてundefinedが代入されます。
// `bookTitle`は自動的に`undefined`という値になる
let bookTitle;

// constと異なり、値の再代入が何度でも可能です。
bookTitle = "JavaScript Primer";
bookTitle = "React";
bookTitle = "Ruby超入門";

var

  • varは、constとletの登場前からある変数宣言キーワードです。
  • varキーワードでは、値の再代入が可能な変数を宣言できます。
  • 変数宣言時に、初期値の代入が必須ではありません。
// varを使った場合でもletと同様に初期値がない変数を宣言できます。
// 変数宣言時に初期値を設定しない場合、let同様undefinedが代入されます。
var bookTitle;
console.log(bookTitle);  //=> undefined

// let同様、変数に対して値の再代入が可能です。
bookTitle = "JavaScript Primer";
bookTitle = "新しいタイトル";
console.log(bookTitle);  //=> 新しいタイトル

varの問題点①〜変数の再定義が可能〜

  • varキーワードには同じ名前の変数を再定義できてしまう問題があります。
  • letconstでは、同じ名前の変数を再定義しようとすると、SyntaxErrorが発生するため同じ変数名で2重に定義することが出来ません。
// "x"という変数名で変数を定義する
let x;
// 同じ変数名の変数"x"を定義するとSyntaxErrorとなる
let x; // => SyntaxError: redeclaration of let x

// yという変数を定義する
const y = 1;

// 定義済みの変数yを再定義する 
const y = 2; //=> => SyntaxError: Identifier 'y' has already been declared
  • しかし、varを使って同じ変数名で定義してもエラーとならずに、値を上書きしてしまいます。
  • これは、意図せずに値を書き換えてしまう挙動につながる可能性があるため、良いことではありません。
// "x"という変数を定義する
var x = 1;
// 同じ変数名の変数"x"を定義できる
var x = 2;
// 変数xは2となる
  • 現在、varは、[ES2015] で導入されたconstletに書き換えが可能になっています。varを使った変数宣言は、使用しないようにしましょう。

varの問題点②〜varによる変数の巻き上げの問題〜

  • varを使うことによる変数の巻き上げという問題があります。
  • 簡単に言うと、varで宣言された変数が宣言前に参照でき、その値がundefinedとなる特殊な動きをすると言うものです。具体例を以下で見ていきましょう。
// var宣言より前に参照してもエラーにならない
console.log(x); // => undefined(定義のみ最初に実行されるため、代入する値"varのx"は出力されません。)
var x = "varのx";
  • 通常の流れとしては、変数を参照する時、変数をまず宣言してから値を代入します。その後代入された値を変数を使って参照することになると思います。
  • そのため、宣言をする前に値の参照はできません。
  • しかし、varを使うと、記述の位置に関わらず、変数の宣言部分だけが関数やグローバルスコープの先頭に移動します。
  • 結果、値未代入の変数定義だけが先に行われてしまうため、undefinedが参照できるという仕組みです。
  • 上記のコードは実際の実行時には、次のように解釈されて実行されています。
// 解釈されたコード
// スコープの先頭に宣言部分が巻き上げられる
var x;
console.log(x); // => undefined
// 変数への代入はそのままの位置に残る
x = "varのx";
console.log(x); // => "varのx"

変数の宣言部分がもっとも近い関数またはグローバルスコープの先頭に移動しているように見える動作のことを変数の巻き上げ(hoisting)と呼びます。

varはlet、constとは異なった動作をしています。 varは巻き上げによりブロックスコープを無視して、宣言部分を自動的に関数スコープの先頭に移動するという予測しにくい問題を持っています。

変数名に使える名前のルール

  1. 半角のアルファベット、_(アンダースコア)、$(ダラー)、数字を組み合わせた名前にする
  2. 変数名は数字から開始できない
  3. 予約語と被る名前は利用できない
  • 変数名が数字から始まっていたり、数字のみで構成されている場合はSyntaxError: Invalid or unexpected tokenエラーになります。
var 1st; // NG: 数字から始まっている(SyntaxError: Invalid or unexpected tokenエラー)
var 123; // NG: 数字のみで構成されている(SyntaxError: Invalid or unexpected tokenエラー)
  • 予約語(構文として意味を持つキーワード)を変数名として使用することは出来ません。こちらもエラーが発生します。SyntaxError: Unexpected token '予約語'
var var; // NG: `var`は変数宣言のために予約されているので利用できない(SyntaxError: Unexpected token 'var')
var if; // NG: `if`はif文のために予約されているので利用できない(SyntaxError: Unexpected token 'var')

constは必ずしも定数ではない

  • constは「再代入できない変数」を定義する変数宣言ですが、必ずしも定数を定義するわけではありません。
  • 定数とは、一度定義した名前(変数名)が常に同じ値を示すものです。
  • 数値や文字列などのプリミティブな値をconstで定義した場合は、値の変更ができないため定数として定義できます。
  • プリミティブな値とは、値の変更できない文字列、数値、BigInt、真偽値などのことです。
  • 対して、オブジェクト、配列、関数などの値は、値を変更できます。
// TEN_NUMBERという変数は常に10という値を示す
const TEN_NUMBER = 10;
  • しかし、オブジェクト、配列、関数などをconst宣言した場合は、オブジェクトという値そのものは、初期化したあとでも変更できてしまうため、厳密に定数とは言えなくなってしまいます。
  • イメージとしては、オブジェクトという入れ物をconstで宣言します。しかし、オブジェクトという入れ物に入っている値は出し入れ自由ということです。
// `const`でオブジェクトを定義している
const object = {
    key: "値"
};
// オブジェクトそのものは変更できてしまう
object.key = "新しい値";

JavaScript において、プリミティブ (primitive、プリミティブ値、プリミティブデータ型) はオブジェクトでなく、メソッドを持たないデータのことです。 6 種類のプリミティブデータ型があります。文字列、数値、BigInt、真偽値、undefined、そしてシンボル (ECMAScript 2016 で追加) です。

すべてのプリミティブ値は、イミュータブルimmutable、つまり変更できません。変数には新しい値を再割り当てすることができますが、既存の値については、オブジェクト、配列、関数が変更できるのに対して、プリミティブ値は変更することができません。

Primitive (プリミティブ)

参照

JSPrimer

Primitive (プリミティブ)