JavaScriptを有効にしてください

Javascript本格入門を読んだら語彙力が上がった

 ·  ☕ 11 分で読めます

家族や友達と会わず家に引きこもっている。SNSも見てない。世の中との繋がりはLINEと日経新聞だけ。今何月何日なの?って感じです。そんな訳で時間が有り余ってるんですが、やることがない。どうせなら会社で借りた本を読もう!と思い、Javascript本格入門を読み進めてます。結構自律神経が整う。コードレビューでなんとなく感じていた違和感を言語化してくれてる。あざす。Chromeのconsoleでデバッグをすぐ出来るから5感を使える。記憶に定着する。

参照型

参照型の比較

参照型(オブジェクト・配列・関数など)は、実体(メモリアドレス)を参照する値のことを言う。

const a = [1, 2] const b = [1, 2]
のように、同じ要素を持つ配列を作っていても、
それぞれは別の場所(アドレス)に格納される。

そのため、a == b や a === b はどちらも falseになる。

1
2
3
4
5
6
![](https://storage.googleapis.com/zenn-user-upload/668e0d8d17d4-20241228.png)

const a = [1, 2];
const b = [1, 2];

console.log(a === b); // false

同一性比較 (===) は、中身ではなく 参照先が同じかどうか を確認しているため。

「要素の内容がすべて同じかどうか」を厳密に確かめたいなら、ループで値同士を直接比較するか、JSON.stringify して比較するといった工夫が必要(ただし、ネストが深いオブジェクトの場合は性能注意)。

演算子

+ (加算/文字列連結)

演算子 + の片方が文字列であれば、もう片方のオペランドも文字列に変換され、文字列連結となる

1
'10' + 1; *// '101'*

両方数値であれば数値加算

1
10 + 1; *// 11*

++x と x++(前置加算 / 後置加算)

    • ++x: 値を返す前に x を 1 増やす
  • x++: 値を返してから x を 1 増やす
1
2
3
4
5
6
7
let x = 5;

console.log(++x); // 6 (インクリメント後の値を返す)

console.log(x++); // 6 (インクリメント前の値を返して、あとで 7 になる)

console.log(x);   // 7

減算

もし文字列が数値として解釈できる場合、数値に変換してから減算される

1
'121' - 0; *// 121 (文字列 '121' が数値 121 として扱われる)*

小数点の計算(浮動小数点数の誤差)

JavaScript は 内部的に IEEE 754 の倍精度浮動小数点数 (いわゆる 2 進浮動小数点) で計算している。

そのため、10 進数では正確に表現できる値も、2 進数に変換すると無限に続く小数になる場合がある。

たとえば 0.2 * 6 は 10 進法で正確には 1.2 になるが、2 進浮動小数点の誤差により 0.6000000000000001 などになることがある。

対処法:

  1. 整数として計算し、最後に桁を調整して少数に戻す(0.2 を 2 として扱い、合計後に 0.1 倍するなど)
  2. toFixed Number.EPSILON を使った誤差吸収(ただし、仕組みを理解しないと誤差の罠にハマるので注意)
  3. プロジェクトによっては 任意精度演算ライブラリ(例えば Big.js など)を導入する

ビット演算

JavaScript のビット演算子 (&, |, ^, ~, «, », »>) は、32 ビットの符号付き整数として扱われる。

高速なアルゴリズムを組む際に、加算演算子を使わずに数値を合成・分解したり、ビットフラグを管理したりする場合などに利用される。

メソッド

length

文字列の length は、UTF-16 コードユニットの数を返します。

サロゲートペア(絵文字などの一部の文字)は、内部的に 2 つのコードユニットで表現されるため、見た目の文字数より length が大きくなることがあります。

1
2
console.log('𩸽'.length); *// 2 (見た目は 1 文字だがコードユニットは 2 つ)*
console.log('叱'.length); *// 3 (見た目は 1 文字だがコードユニットは 3 つ)*

substr, substring, slice

それぞれ、文字列を切り出すメソッド。挙動が微妙に異なるから気をつける。

  1. substring(start, end)

• start end のインデックスを指定して切り出します。

• end は含まれないstart ≤ index < end)。

順序が逆でも自動で入れ替える

1
2
3
4
5
6
7
8
const str = "JavaScript";

// 正常な順序
console.log(str.substring(0, 4)); // "Java"(インデックス0~3の文字を取得)
// 順序が逆の場合
console.log(str.substring(4, 0)); // "Java"(自動的に start と end を入れ替える)
// `end` を省略した場合
console.log(str.substring(4)); // "Script"(インデックス4から最後まで切り出す)
  1. substr(start, length)

• start: 開始インデックス

• length: 切り出す文字数を指定。

•負の値を渡すと、start は文字列の末尾から数えた位置を基準にする。

1
2
3
4
5
6
7
8
const str = "JavaScript";

// 先頭から4文字を切り出す
console.log(str.substr(0, 4)); // "Java"
// インデックス4から5文字切り出す
console.log(str.substr(4, 5)); // "Scrip"
// 負の値で末尾から2文字
console.log(str.substr(-2)); // "pt"
  1. slice(start, end)

• start end のインデックスを指定して切り出します。

• end は含まれないstart ≤ index < end)。

• 負の値を渡すと、末尾からのオフセットとして扱われる。

1
2
3
4
5
6
7
8
const str = "JavaScript";

// 正常な順序
console.log(str.slice(0, 4)); // "Java"(インデックス0~3の文字を取得)
// 負の値で末尾から4文字切り出す
console.log(str.slice(-4)); // "ript"
// 負の値で範囲を指定
console.log(str.slice(-6, -4)); // "Sc"(末尾から6文字目~4文字目の範囲)

分割代入 (Destructuring)

オブジェクトか配列に対して可能

1
2
3
4
const obj = { x: 10, y: 20 };
const { x, y } = obj; // x=10, y=20
const arr = [1, 2, 3];
const [first, second] = arr; // first=1, second=2

with 文

with (オブジェクト) {…} で、オブジェクトのプロパティを変数のように直接書けるようにする構文。

ただ、可読性やパフォーマンスの低下を招くため、“非推奨” とされている。

ほとんど使われなくなった構文なので、モダンな JavaScript では避けるのが一般的。

インスタンスメソッド / クラスメソッド

JavaScript では厳密には「クラス」という仕組みが後付けされた(ES2015)。

Math オブジェクトは「グローバルにあるメソッドが集まったもの」であり、“クラス” としてインスタンス生成はできない。いわゆる “静的メソッド” (class method) の集まりのようなイメージ

組み込み値 / オブジェクト

Symbol

重複しない一意の識別子を作るための原始値。

“定数名” や “オブジェクトのプロパティキー” が衝突しないようにしたい場合に有用。

1
2
3
4
5
const MY_KEY = Symbol('説明用文字列');

const obj = {
  [MY_KEY]: 'secret value'
};

同じ “説明用文字列” を与えてもSymbolは必ず一意になる仕様になっている。そのため、別々の Symbol を比較しても等価にならない。

1
2
3
4
5
const MY_KEY = Symbol('説明用文字列');

const MY_KEY2 = Symbol('説明用文字列');

console.log(MY_KEY === MY_KEY2) // false

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 は オブジェクトの “列挙可能なプロパティの キー (文字列)” を取り出す

1
2
3
4
const obj = { a: 1, b: 2 };
for (let key in obj) {
  console.log(key); // 'a', 'b'
}

for of は イテラブルなオブジェクト(配列、Map、Set、文字列など)の “” を取り出す

1
2
3
4
const arr = [10, 20];
for (let value of arr) {
  console.log(value); // 10, 20
}

undefined と null

    • undefined

「値が定義されていない状態」 を表す。

例)

①変数を宣言だけして代入していない

②存在しないオブジェクトのプロパティにアクセスした

③関数で return しなかった場合の戻り値

  • null

「空(存在しない)であること」を、明示的に示す値

例)

「検索したが結果が無い」というケースなど、自分で「ここには何もない」とセットしておく

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 1. undefined の例
// (1) 変数を宣言だけして代入していない
let a;
console.log(a); // undefined

// (2) 存在しないオブジェクトのプロパティにアクセスした
const obj = {};
console.log(obj.nonExistentProperty); // undefined

// (3) 関数で return しなかった場合の戻り値
function noReturnFunction() {
  // return 文がない
}
console.log(noReturnFunction()); // undefined

// 2. null の例
// 「空(存在しない)であること」を明示的に示す

// (1) 明示的に null を代入する
let b = null;
console.log(b); // null

// (2) 「検索結果がない」というケースを示す
function search(query) {
  const data = ["apple", "banana", "cherry"];
  return data.includes(query) ? query : null; // 検索結果がなければ null を返す
}
console.log(search("apple")); // "apple"(結果が見つかった)
console.log(search("grape")); // null(結果がない)

// undefined と null の違いを確認
console.log(undefined == null); // true(値が等しいとみなされる)
console.log(undefined === null); // false(型も含めて等しくない)

オブジェクト = プロパティ + メソッド

JS では、

オブジェクトは「名前(キー)と値(プロパティ・メソッド)を持つ入れ物」

配列は “インデックス番号をキーとして持つ特殊なオブジェクト”

関数は “call メソッドを持つオブジェクト”

と言える。

配列操作

shift(): 配列の先頭要素を取り除いて返す

unshift(x): 配列の先頭に要素 x を追加

pop(): 配列の末尾から要素を取り除いて返す

push(x): 配列の末尾に要素 x を追加

splice(start, count, …items): 配列を破壊的に書き換えながら、任意位置に要素を挿入・削除できる

forEachとmapの違い

forEach: 返り値がない(コールバックを各要素に対して実行するだけ)

map: 各要素を変換し、新しい配列を返す

sort: デフォルトでは、文字列としてのコード順で並び替える

数値を正しく並べるには、比較関数を渡す必要がある

1
arr.sort((a, b) => a - b);

文字列の順序が複雑な場合(ロケール順など)、localeCompare や外部の比較ロジックを使うことも

Map オブジェクト

オブジェクトリテラルと Map の違い

オブジェクトリテラル

{ key: value } の形で定義する書き方を “オブジェクトリテラル” と呼ぶ。

かつては「連想配列」と表現されていたが、厳密には “配列” ではなく「キーが文字列 (Symbol) のみ許容」されるオブジェクト” がオブジェクトリテラルである。

Map

Map は キーにあらゆる値 (オブジェクト含む) を利用することができる

通常のオブジェクト {} は、「文字列または Symbol」だけをプロパティキーにできる。

が、Map は同値比較 (同一参照かどうか)=== に近いルールでキーを比較するので、NaN キーを正しく扱えたりする。

NaN キーは、オブジェクトリテラルだと obj[NaN] = ‘something’ → 実質 obj[‘NaN’] として扱われるなどのややこしさあり。

例: Map にオブジェクトをキーにできる

1
2
3
4
5
6
7
8
9
const m = new Map();
const key = {};
m.set(key, 3);

console.log(m.get(key)); // 3
// 下記は別アドレスに保管されたとJSは認識する

m.set({}, 99);
console.log(m.get({})); *// undefined (同じリテラルでも別オブジェクト)*

オブジェクトリテラル ({}) と違い、イテレーションがしやすい

m.keys(), m.values(), m.entries()
で、キーや値、[キー,値] ペアを取り出せる。

一度に大量のデータを扱うときは Map が便利な場合が多い。

Map の初期化

new Map() の引数に [キー, 値] の配列の配列を渡すと、一気に初期化できる:

1
2
3
4
const m = new Map([
  [ 'key1', 'value1' ],
  [ 'key2', 'value2' ]
]);

Set オブジェクト

重複しない要素のコレクション

NaN は同じものとして扱われ、オブジェクトリテラル同様、同じリテラル {} でも毎回別参照なので重複にはならない

1
2
3
4
5
6
7
8
const s = new Set();
s.add(NaN);
s.add(NaN);
console.log(s.size); *// 1 (NaN は同じ扱い)*

s.add({});
s.add({});
console.log(s.size); *// 3 ( {} は毎回異なるオブジェクト参照 )*

WeakMap / WeakSet

キー(または要素)となるオブジェクトへの参照が弱い(ガベージコレクタによる解放が許可されやすい)ため、キャッシュ実装などに使われる。

イテレーションメソッドが存在しないなど、通常の Map / Set とは使い勝手が異なるので注意。

テンプレートリテラル

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals

バッククォート ( ``   ) を使った文字列リテラル

変数や式を埋め込むとき、 ${…} の形で書ける

1
2
3
4
![](https://storage.googleapis.com/zenn-user-upload/790db2ee0299-20241228.png)
const name = 'Alice';

console.log(`Hello, ${name}!`); // "Hello, Alice!"

改行やインデントもそのまま文字列に含まれる。長い文字列でも +で連結しなくて済むため、可読性が高まる。

文字列連結 (.concat) を大量に使っている部分をテンプレートリテラルに置き換えるとスッキリ書ける。

レビューの時、名前が出てこなくて「これ」って言ってしまう。

.concatで繋げている部分、テンプレートリテラルで書けないですか?とレビューで言えたらちょび成長。

所感

破壊的なメソッドとそうでないものの違いやサロゲートペアによって確保されるバイト数が増えてしまう問題など、なんとなく知ってはいたけど調べてこなかったな〜という内容を背景から学べてよかった。滅多に使わないけど、+演算で文字列連結されることやビット演算などを知っておくことは、コードリーディングの効率が上がるので、引き出しに入れておく。

参考

改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで: https://www.amazon.co.jp/改訂新版JavaScript本格入門-モダンスタイルによる基礎から現場での応用まで-山田-祥寛/dp/477418411X

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals


octpsubaru
著者
octpsubaru
Web Developer