家族や友達と会わず家に引きこもっている。SNSも見てない。世の中との繋がりはLINEと日経新聞だけ。今何月何日なの?って感じです。そんな訳で時間が有り余ってるんですが、やることがない。どうせなら会社で借りた本を読もう!と思い、Javascript本格入門を読み進めてます。結構自律神経が整う。コードレビューでなんとなく感じていた違和感を言語化してくれてる。あざす。Chromeのconsoleでデバッグをすぐ出来るから5感を使える。記憶に定着する。
参照型
参照型の比較
参照型(オブジェクト・配列・関数など)は、実体(メモリアドレス)を参照する値のことを言う。
const a = [1, 2]と const b = [1, 2]
のように、同じ要素を持つ配列を作っていても、
それぞれは別の場所(アドレス)に格納される。
そのため、a == b や a === b はどちらも falseになる。
|
|
同一性比較 (===) は、中身ではなく 参照先が同じかどうか を確認しているため。
「要素の内容がすべて同じかどうか」を厳密に確かめたいなら、ループで値同士を直接比較するか、JSON.stringify して比較するといった工夫が必要(ただし、ネストが深いオブジェクトの場合は性能注意)。
演算子
+ (加算/文字列連結)
演算子 + の片方が文字列であれば、もう片方のオペランドも文字列に変換され、文字列連結となる
|
|
両方数値であれば数値加算
|
|
++x と x++(前置加算 / 後置加算)
-
- ++x: 値を返す前に x を 1 増やす
- x++: 値を返してから x を 1 増やす
|
|
減算
もし文字列が数値として解釈できる場合、数値に変換してから減算される。
|
|
小数点の計算(浮動小数点数の誤差)
JavaScript は 内部的に IEEE 754 の倍精度浮動小数点数 (いわゆる 2 進浮動小数点) で計算している。
そのため、10 進数では正確に表現できる値も、2 進数に変換すると無限に続く小数になる場合がある。
たとえば 0.2 * 6 は 10 進法で正確には 1.2 になるが、2 進浮動小数点の誤差により 0.6000000000000001 などになることがある。
対処法:
- 整数として計算し、最後に桁を調整して少数に戻す(0.2 を 2 として扱い、合計後に 0.1 倍するなど)
- toFixed や Number.EPSILON を使った誤差吸収(ただし、仕組みを理解しないと誤差の罠にハマるので注意)
- プロジェクトによっては 任意精度演算ライブラリ(例えば Big.js など)を導入する
ビット演算
JavaScript のビット演算子 (&, |, ^, ~, «, », »>) は、32 ビットの符号付き整数として扱われる。
高速なアルゴリズムを組む際に、加算演算子を使わずに数値を合成・分解したり、ビットフラグを管理したりする場合などに利用される。
メソッド
length
文字列の length は、UTF-16 コードユニットの数を返します。
サロゲートペア(絵文字などの一部の文字)は、内部的に 2 つのコードユニットで表現されるため、見た目の文字数より length が大きくなることがあります。
|
|
substr, substring, slice
それぞれ、文字列を切り出すメソッド。挙動が微妙に異なるから気をつける。
- substring(start, end)
• start と end のインデックスを指定して切り出します。
• end は含まれない(start ≤ index < end)。
• 順序が逆でも自動で入れ替える
|
|
- substr(start, length)
• start: 開始インデックス
• length: 切り出す文字数を指定。
•負の値を渡すと、start は文字列の末尾から数えた位置を基準にする。
|
|
- slice(start, end)
• start と end のインデックスを指定して切り出します。
• end は含まれない(start ≤ index < end)。
• 負の値を渡すと、末尾からのオフセットとして扱われる。
|
|
分割代入 (Destructuring)
オブジェクトか配列に対して可能
|
|
with 文
with (オブジェクト) {…} で、オブジェクトのプロパティを変数のように直接書けるようにする構文。
ただ、可読性やパフォーマンスの低下を招くため、“非推奨” とされている。
ほとんど使われなくなった構文なので、モダンな JavaScript では避けるのが一般的。
インスタンスメソッド / クラスメソッド
JavaScript では厳密には「クラス」という仕組みが後付けされた(ES2015)。
Math オブジェクトは「グローバルにあるメソッドが集まったもの」であり、“クラス” としてインスタンス生成はできない。いわゆる “静的メソッド” (class method) の集まりのようなイメージ。
組み込み値 / オブジェクト
Symbol
重複しない一意の識別子を作るための原始値。
“定数名” や “オブジェクトのプロパティキー” が衝突しないようにしたい場合に有用。
|
|
同じ “説明用文字列” を与えてもSymbolは必ず一意になる仕様になっている。そのため、別々の Symbol を比較しても等価にならない。
|
|
Pythonのタプルと同様、Symbolをkey valueストアのkeyにすることができるのは、Symbolが必ず一意だから。
ループ
for (let [key, value] of m) と for (let [key, value] of m.entries())
Mapオブジェクトなどでは m.entries()がキーと値のペア [key, value]を返すイテレーター。
for (let [k, v] of m) **は、**mSymbol.iterator が [key, value]ペアを返すよう実装されているので、
結果は m.entries() と同じ動きになる。
for in と for of の違い
for in は オブジェクトの “列挙可能なプロパティの キー (文字列)” を取り出す
|
|
for of は イテラブルなオブジェクト(配列、Map、Set、文字列など)の “値” を取り出す
|
|
undefined と null
-
- undefined
「値が定義されていない状態」 を表す。
例)
①変数を宣言だけして代入していない
②存在しないオブジェクトのプロパティにアクセスした
③関数で return しなかった場合の戻り値
- null
「空(存在しない)であること」を、明示的に示す値
例)
「検索したが結果が無い」というケースなど、自分で「ここには何もない」とセットしておく
|
|
オブジェクト = プロパティ + メソッド
JS では、
オブジェクトは「名前(キー)と値(プロパティ・メソッド)を持つ入れ物」
配列は “インデックス番号をキーとして持つ特殊なオブジェクト”
関数は “call メソッドを持つオブジェクト”
と言える。
配列操作
shift(): 配列の先頭要素を取り除いて返す
unshift(x): 配列の先頭に要素 x を追加
pop(): 配列の末尾から要素を取り除いて返す
push(x): 配列の末尾に要素 x を追加
splice(start, count, …items): 配列を破壊的に書き換えながら、任意位置に要素を挿入・削除できる
forEachとmapの違い
forEach: 返り値がない(コールバックを各要素に対して実行するだけ)
map: 各要素を変換し、新しい配列を返す
sort: デフォルトでは、文字列としてのコード順で並び替える
数値を正しく並べるには、比較関数を渡す必要がある
|
|
文字列の順序が複雑な場合(ロケール順など)、localeCompare や外部の比較ロジックを使うことも
Map オブジェクト
オブジェクトリテラルと Map の違い
オブジェクトリテラル
{ key: value } の形で定義する書き方を “オブジェクトリテラル” と呼ぶ。
かつては「連想配列」と表現されていたが、厳密には “配列” ではなく「キーが文字列 (Symbol) のみ許容」されるオブジェクト” がオブジェクトリテラルである。
Map
Map は キーにあらゆる値 (オブジェクト含む) を利用することができる。
通常のオブジェクト {} は、「文字列または Symbol」だけをプロパティキーにできる。
が、Map は同値比較 (同一参照かどうか)=== に近いルールでキーを比較するので、NaN キーを正しく扱えたりする。
NaN キーは、オブジェクトリテラルだと obj[NaN] = ‘something’ → 実質 obj[‘NaN’] として扱われるなどのややこしさあり。
例: Map にオブジェクトをキーにできる
|
|
オブジェクトリテラル ({}) と違い、イテレーションがしやすい
m.keys(), m.values(), m.entries()
で、キーや値、[キー,値] ペアを取り出せる。
一度に大量のデータを扱うときは Map が便利な場合が多い。
Map の初期化
new Map() の引数に [キー, 値] の配列の配列を渡すと、一気に初期化できる:
|
|
Set オブジェクト
重複しない要素のコレクション
NaN は同じものとして扱われ、オブジェクトリテラル同様、同じリテラル {} でも毎回別参照なので重複にはならない
|
|
WeakMap / WeakSet
キー(または要素)となるオブジェクトへの参照が弱い(ガベージコレクタによる解放が許可されやすい)ため、キャッシュ実装などに使われる。
イテレーションメソッドが存在しないなど、通常の Map / Set とは使い勝手が異なるので注意。
テンプレートリテラル
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals
バッククォート ( `` ) を使った文字列リテラル
変数や式を埋め込むとき、 ${…} の形で書ける
|
|
改行やインデントもそのまま文字列に含まれる。長い文字列でも +で連結しなくて済むため、可読性が高まる。
文字列連結 (.concat) を大量に使っている部分をテンプレートリテラルに置き換えるとスッキリ書ける。
レビューの時、名前が出てこなくて「これ」って言ってしまう。
.concatで繋げている部分、テンプレートリテラルで書けないですか?とレビューで言えたらちょび成長。
所感
破壊的なメソッドとそうでないものの違いやサロゲートペアによって確保されるバイト数が増えてしまう問題など、なんとなく知ってはいたけど調べてこなかったな〜という内容を背景から学べてよかった。滅多に使わないけど、+演算で文字列連結されることやビット演算などを知っておくことは、コードリーディングの効率が上がるので、引き出しに入れておく。
参考
改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで: https://www.amazon.co.jp/改訂新版JavaScript本格入門-モダンスタイルによる基礎から現場での応用まで-山田-祥寛/dp/477418411X
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals