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を返します。
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を返します。
❸ビット排他的論理和(^)
ビット排他的論理和演算子(^)はビットごとのXOR演算した結果を返します。 XOR演算では、オペランドのビットが異なるなら1、両方とも同じなら0となります。
❹ビット否定(~)
各ビットに対して、NOT演算を実行します。 NOT aは、aを反転します。
ビット否定演算子(~)の性質を利用した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;
まとめ
参考
2022/03/12(土) JSprimer(演算子②~比較演算子~)
前回の記事の続きです。
比較演算子
❶厳密等価演算子(===)
// 同じ型で同じ値である場合に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
❷厳密不等価演算子(!==)
// 異なる型か異なる値である場合に、trueを返す console.log(1 !== 1); // => false console.log(1 !== "1"); // => true
❸等価演算子(==)
- 等価演算子(
==
)は、2つのオペランドを比較します。 - 同じデータ型のオペランドを比較する場合は、厳密等価演算子(
===
)と同じ結果になります。 - しかし、等価演算子(
==
)はオペランド同士が異なる型の値であった場合に、 同じ型となるように暗黙的な型変換が行われてから比較します。 - この暗黙的な型変換は、バグを引き起こすことがあるため、注意が必要です。
- 等価演算子(==)は、nullとundefinedを比較する時にだけ使うようにしましょう。
- その他の比較の際は厳密等価演算子を使うことを推奨します。
// 同じ型同じ値の場合は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 であることを判定したい場合に唯一等価演算子(==)が使われる場合⏬
- そもそもundefinedとnullは大きなくくりで「値がない」ことを表現します。
- そのため、nullまたはundifinedのどちらかが入っていれば値がないことを判定できます。
- しかし、厳密にはundefinedとnullで型が異なるなどの違いがあるため、厳密等価演算子(
===
)を使って比較すると、それぞれと比較する必要が出てきてしまいます。 - 結果的に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演算子を使うと、nullはobject型、undefinedはundefined型と判定されます。
console.log(typeof undefined); //=> "undefined" console.log(typeof null); //=> "object"
❹不等価演算子(!=)
- 不等価演算子(
!=
)は、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
参照
2022/03/8~ 3/11JSprimer(演算子①)
演算子
// +演算子で値同士を足し算する加算演算を行う // この場合、1と2の2つの値がオペランド、演算する対象です。 1 + 2;
二項演算子
// 二項演算子とオペランドの関係
左オペランド 演算子 右オペランド
プラス演算子(+)
- 2つの数値を加算する演算子です。
console.log(1 + 1); // => 2
- JavaScriptでは、数値は内部的に浮動小数点数として表現されています。
- そのため、整数と浮動小数点数の加算も型変換を行うことなく、プラス演算子のみで計算できます。
// 数値型の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
単項演算子(算術)
let num = 1; //どちらもnum += 1;と同じ意味 num++; // 1つのオペランドを用いて評価をしているので、単行演算子 // または ++num; // 1つのオペランドを用いて評価をしているので、単行演算子
❶単項プラス演算子(+)
// 数値の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
❸インクリメント演算子(++)
- インクリメント演算子(++)は、オペランドの数値に+1する演算子です。
- オペランドの前後どちらかにインクリメント演算子を置くことで、オペランドの値に対して+1した値を返します。
- インクリメント演算子(++)は、オペランドの後ろに置くか前に置くかで、それぞれで評価の順番が異なります。
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
の評価をする演算子です。 - デクリメント演算子は、インクリメント演算子と同様に、オペランドの前後のどちらかに置くことができます。
- デクリメント演算子も、前後どちらかの置く場所によって評価の順番が変わります。
// 後置デクリメント演算子(値が返されてから、-1の評価が行われる) let x = 1; console.log(x--); // => 1 console.log(x); // => 0 // 前置デクリメント演算子(-1の評価がされてから値が返る) let y = 1; console.log(--y); // => 0 console.log(y); // => 0
参照
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から追加された一意で不変な値のデータ型
イミュータブルな値の例⏬
// 文字列値を持つ新しいオブジェクトが作成されます。文字列はイミュータブル(不変)な値です。 var immutableString = "Hello"; console.log(immutableString); //=> Hello // 今、既存の値に "World" を追加しています。(ここで重要なのは、文字列HelloにWorldを追加した新しい文字列を作成していることです。) immutableString = immutableString + "World"; // 確かに、既存の値にworldを追加した値が出力されるが、これは既存の値を変更したものではなく、新しい値を作成したものを参照しています。 console.log(immutableString); //=> HelloWorld
- ここで重要なのは、"Hello"という既存の文字列を変更しているわけではないことです。
- 既存の文字列を参照する変数immutableStringと" World"を連結して新たな文字列「"Hello World"」を生成しimmutableStringに再代入しています。
- この時、新たに作成された文字列は新しいメモリー空間を参照することになります。ここからも既存の値自体が変更されたわけではないことが分かります。
- 既存の値が参照されていたメモリーは使わなくなるため、ガベージコレクションに追加されます。
まとめると、イミュータブルな値は変更できません。上記の例は、一見値の変更が行われているように見えます。しかし、実際は変更されているのではなく、変更が加えられた新しいオブジェクトが作成されているだけなのです。(元の値は、変わっていません。)
元の値を変更したい時使われるのは、既存の値ではなくそれをコピーして参照した値になります。こうすることで、元の値に影響が及ぶことはありません。
オブジェクト
- オブジェクトは複数のプリミティブ型の値またはオブジェクトからなる値の集合のことです。
- オブジェクトは、一度作成した後もその値自体を変更できるためミュータブル(mutable)の特性を持ちます。
- オブジェクトは、値そのものではなく値への参照を経由して操作されるため、参照型のデータとも言います。
- プリミティブ型以外のデータをオブジェクトであると覚えていれば問題ありません。
- オブジェクト(複合型)
- プリミティブ型以外のデータ
- オブジェクト、配列、関数、正規表現、Dateなど
typeof演算子
を使うことで、値のデータ型を調べることができます。typeof演算子
は、基本的にプリミティブ型またはオブジェクトかを判別するものです。typeof nullが"object"
となるのは、歴史的経緯のある仕様のバグです。本来は、nullと出力されるべきです。
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)
真偽値にはtrueとfalseのリテラルがあり、trueとfalseの値を返します。
true; // => true false; // => false
リテラル表現を持つプリミティブ型②:数値(Number)
数値には42のような整数リテラルと3.14159のような浮動小数点数リテラルがあります。
❶整数リテラル
整数リテラルには次の4種類があります。
- 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入れたい"
- テンプレートリテラル内で${変数名}と書いた場合に、その変数の値を埋め込むことができます。
const str = "文字列"; console.log(`これは${str}です`); // => "これは文字列です"
リテラル表現を持つプリミティブ型⑤:nullリテラル
// null値を持つfooという変数を定義=>fooを値がない変数として定義し、参照できる const foo = null; console.log(foo); // => null
true、false、nullなどはグローバル変数ではなくリテラルであるため、同じ名前の変数を定義することはできません。 リテラルは変数名として利用できない予約語のようなものであるため、再定義しようとすると構文エラー(SyntaxError)となります。
リテラル表現が用意されているオブジェクト①: オブジェクトリテラル
オブジェクトはあらゆるものの基礎となります。 そのオブジェクトを作成する方法として、オブジェクトリテラルがあります。 オブジェクトリテラルは{}(中カッコ)を書くことで、新しいオブジェクトを作成できます。
オブジェクトリテラルはオブジェクトの作成と同時に中身を定義できます。 オブジェクトのキーと値を:で区切ったものを {} の中に書くことで作成と初期化が同時に行えます。 キー名には、文字列または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
参照
2022/03/3(木) JSprimer(変数と宣言)
変数と宣言
プログラミング言語には、文字列や数値などのデータに名前をつけることで、繰り返し利用できるようにする変数という機能があります。
- データを繰り返し利用できるようにする仕組みに変数があります。
- JSでは、変数を宣言するキーワードとしてconst、let、varの3つがあります。
- varは、昔からあった変数宣言のキーワードですが、意図しない挙動をしやすいという問題がありました。
- それらの問題を改善するために作られたのが、ECMAScript 2015で導入されたconstとletです。
- 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キーワードには同じ名前の変数を再定義できてしまう問題があります。
- letやconstでは、同じ名前の変数を再定義しようとすると、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] で導入されたconstかletに書き換えが可能になっています。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は巻き上げによりブロックスコープを無視して、宣言部分を自動的に関数スコープの先頭に移動するという予測しにくい問題を持っています。
変数名に使える名前のルール
- 半角のアルファベット、_(アンダースコア)、$(ダラー)、数字を組み合わせた名前にする
- 変数名は数字から開始できない
- 予約語と被る名前は利用できない
- 変数名が数字から始まっていたり、数字のみで構成されている場合は
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、つまり変更できません。変数には新しい値を再割り当てすることができますが、既存の値については、オブジェクト、配列、関数が変更できるのに対して、プリミティブ値は変更することができません。
参照
2022/03/1(火) JSprimer (コメント)
コメント
- コメントはプログラムとして評価されないため、ソースコードの説明や注釈を書くのに使われます。
- コメントの書き方には、一行コメントと複数行コメントの2種類があります。
①一行コメント
- 一行コメントは、一行分をコメントとして扱いたい時に用います。
//
以降から行末までがコメントとして扱われ、プログラムとして評価されることはありません。
// 一行コメント // この部分はコードとして評価されない // console.log("この部分はコメントとして扱われ、表示されません");
②複数行コメント
- 複数行コメントは、複数行まとめてコメント扱いとしたい場合に用います。
/*
と*/
で囲まれた範囲がコメントとして扱われます。
/* 複数行コメント 途中に改行を含むことができます。 囲まれている範囲がコードとして評価されません。 console.log("この部分はコメントとして扱われ、表示されません"); */
- ただし、複数行コメントの中に、ネストさせて複数行コメントを書くことはできません。構文エラーになってしまいます。
/* ネストされた /* 複数行コメント */ は書けない */ //=> 構文エラーになります。
③[ES2015] HTML-likeコメント(非推奨)
- ECMAScript 2015(ES2015)から後方互換性のための仕様として追加されたコメントです。
- 後方互換性とは、例えると、古い製品の仕様を新しい製品でも使えるようにすることです。
- 以前は、JavaScriptをサポートしていない古来のブラウザが誤ってjsのコードをテキストとして表示されるのを防ぐためにHTMLに似たコメント機能を使って、jsのコードをコメントアウトしていました。
- しかし現在では、JavaScriptに対応できていないブラウザはないので、使う必要はありません。
- 古い本や解説サイトを参考にしたであろうサイトが現存するため、そのコードが理解できるようにと配慮された仕様になります。
これは本文ではなくてAnnex(付属文書)に記述されている、いわゆる後方互換、歴史的な事情の維持のための文法です。古来のブラウザ(IE3とか、MS-DOSとかそういうレベルの時代)ではscriptタグに非対応で、プログラムがまるごと文章中に表示されてしまう可能性がありました。そこで、scriptの中身をコメントアウトして書くことがマナーでした。
<script language="javascript"> <!-- document.bgColor = "brown"; // --> </script>
日本の一般人にまでインターネットが普及した時代にはもう不要のマナーだったかもしれませんが、古い本や解説サイトを参考にしたであろうサイトが現存します。これをある程度解釈できるようにという配慮がこのHTML-likeコメントです。
JavaScriptにはあまり知られていない「5種類」のコメントがある
参考
2022/03/1(火) JSprimer (JavaScriptとは)
JavaScriptとは
JavaScriptは、主にウェブブラウザの中で動くプログラミング言語です。 ウェブサイトで操作をしたら表示が書き換わったり、ウェブサイトのサーバーと通信してデータを取得したりと主にユーザーに見える部分の動作を担う言語です。仕様は、ECMAScriptによって動作が決められています。
- JavaScriptは、ウェブブラウザ上で動くプログラミング言語です。
- 仕様は、ECMAScriptによって決められています
ECMAScriptとは?
ECMAScriptはJavaScriptの仕様を定義したものです。仕様とは決まりごとのことで、ブラウザなどがJavaScriptを読み込んだときに、どのような文法を解釈しなければならないか、処理がどのように動くべきかといったことを決めたものです。
- 関数宣言の書き方はこういう文法になる
- 変数が宣言されたとき、JavaScriptエンジンはこういう動作になる
- StringやArrayオブジェクトにはこういうメソッドがある
- 例えば、上記のようなJavaScriptにおける基本的な決まりを定めているものです。
- ECMAScriptでは、どの実行環境でも共通な動作のみが定義されているため、基本的にどの実行環境でも同じ動作をします。
- まとめると、ECMAScriptとは、どの実行環境でも共通な動作のみが定義されているJavaScriptの核となる仕様のことです。
- 「ECMAScript」はどの実行環境でも共通の部分を表す
- 「JavaScript」はECMAScriptと実行環境の固有機能も含んだ範囲を表す
- まとめると、JavaScriptでは、基本的な共通の使用であるECMAScriptに独自の実行環境の固有機能を加えたものであると考えると分かりやすいでしょう。
JavaScriptの言語的な特徴①大文字と小文字を区別する
- JavaScriptは大文字小文字を区別します。
- 同じnameでもNAMEとnameでは、別々の名前として認識されるということです。
JavaScriptの言語的な特徴②予約語を持つ
- JavaScriptには特別な意味を持つキーワードのことを予約語と呼びます。
- 予約語と同じ名前の変数や関数は宣言できません。
JavaScriptの言語的な特徴③文はセミコロンで区切られる
JavaScriptは、文(Statement)ごとに処理していき、文はセミコロン(;)によって区切られます。 特殊なルールに基づき、セミコロンがない文も、行末に自動でセミコロンが挿入されるという仕組みも持っています。しかし、暗黙的なものへ頼ると意図しない挙動が発生するため、セミコロンは常に書くようにします
- 基本的には、文をセミコロン(
;
)で区切るように記述していきます。 - スペース、タブ文字などは空白文字(ホワイトスペース)と呼ばれ、文に記述しても挙動に影響はありません。
JavaScriptの言語的な特徴④strict mode
JavaScriptにはstrict modeという実行モードが存在しています。 古く安全でない構文や機能が一部禁止されています。
use strict
という文字列をファイルまたは関数の先頭に書くことで、strict modeが有効になります。
"use strict"; // このコードはstrict modeで実行される
- "Module"の実行コンテキストでは、このstrict modeがデフォルトの仕様となっています。
- strict modeでは開発者が安全にコードを書けるように、レガシーな機能や構文を禁止したり、明らかな問題を含んだコードに対しては早期的に例外を投げたりすることでJavaScriptの落とし穴を一部ふさいでくれます。
JavaScriptの言語的な特徴⑤実行コンテキスト: ScriptとModule
- JavaScriptの実行コンテキストとして"Script"と"Module"があります。
- 実行コンテキストとは、実行環境のことです。
- "Module"の実行コンテキストは、デフォルトがstrict modeとなり、古く安全でない構文や機能は一部禁止されています。
コンテキストとは?
同じコード記述やプログラム上の要素が、その置かれているプログラム内での位置や、実行される際の内部状態などによって異なる振る舞いをしたり、異なる制約を受けたりすることを指してコンテキストということがある。 コンテキストe-Words
- (変数)の中身によって異なる処理を行う場合はstate
- (変数)の中身によらず同じ処理を行う場合はcontext
- contextの「状態」が「りんご」でも「バナナ」でも「グラボ」でも何であっても、カートに入れて、レジ通して、支払して、バッグに入れるという一連の「処理」は変わりません。