JacaScript Primer 読書メモ

セミコロンは常に書くようにします (詳細は「文と式」の章で解説します)。
- 実際どうなんだろうか🤔

strict mode
strict modeでは、evalやwithといったレガシーな機能や構文を禁止します。
- 便利そう。ちょっとレガシーな現場のリプレースとかで使われてそう。

JavaScriptの仕様であるECMAScriptは毎年更新され、JavaScriptには新しい構文や機能が増え続けています。
- キャッチアップ頑張ります...

変数について
const | let | var | |
---|---|---|---|
再宣言 | × | × | ○ |
再代入 | × | ○ | ○ |
スコープ | ブロック | ブロック | 関数 |
ホイスティング | エラー | エラー | undefined |

console系について気になっただけ
- そういえば、「console.log()」しか使わんなと思い

"(ダブルクォート)と'(シングルクォート)はまったく同じ意味となります。

- false扱い(多すぎ)
if (false) {
// この行は実行されません
}
if ("") {
// この行は実行されません
}
if (0) {
// この行は実行されません
}
if (undefined) {
// この行は実行されません
}
if (null) {
// この行は実行されません
}

- breakの扱いは言語によって差がでるよね〜
switch文を抜けるようにします。 このbreak;は省略が可能ですが、省略した場合、後ろに続くcase節が条件に関係なく実行されます。

- いろんな書き方ができるね〜
function isEven(num) {
return num % 2 === 0;
}
const numbers = [1, 5, 10, 15, 20];
console.log(numbers.some(isEven));

- 分割代入!
const languages = {
ja: "日本語",
en: "英語"
};
const { ja, en } = languages;
console.log(ja); // => "日本語"
console.log(en); // => "英語"
``

- プロパティ名の間違いについて
JavaScriptでは存在しないプロパティへアクセスした場合に例外が発生しません。
さらにプロパティ名をネストしてアクセスした場合に、初めて例外が発生します。

Nullish coalescing演算子(??)
左辺のオペランドがnullish(nullまたはundefined)の場合は、それ以上評価せずにundefinedを返します。一方で、プロパティが存在する場合は、そのプロパティの評価結果を返します。
const obj = {
a: {
b: "objのaプロパティのbプロパティ"
}
};
console.log(obj?.a?.b); // => "objのaプロパティのbプロパティ"
// ドット記法の場合は例外が発生してしまうけどundefinedで返してくれる
console.log(obj?.notFound?.notFound); // => undefined

Optional chaining演算子(?.)
// widget?.window?.titleが定義されていないなら "未定義"を返してくれる
// ifで制御しなくていい
const title = widget?.window?.title ?? "未定義";

オブジェクトの列挙
Object.keysメソッド: オブジェクトのプロパティ名の配列にして返す
Object.keysメソッド: オブジェクトのプロパティ名の配列にして返す
Object.entriesメソッド[ES2017]: オブジェクトのプロパティ名と値の配列の配列を返す
- 便利だ!知らなかった!

オブジェクトのspread構文でのマージ
オブジェクトのspread構文は、Object.assignとは異なり必ず新しいオブジェクトを作成します。 なぜならspread構文はオブジェクトリテラルの中でのみ記述でき、オブジェクトリテラルは新しいオブジェクトを作成するためです。
- こんな感じでマージできるらしい
const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = {
...objectA,
...objectB
};

shallow/deep コピーについて
JavaScriptのビルトインメソッドは浅い(shallow)実装のみを提供し、深い(deep)実装は提供していないことが多いです。

Object.hasOwn静的メソッドとin演算子の挙動の違いについて
onst obj = {};
// `obj`というオブジェクト自体に`toString`メソッドが定義されているわけではない
console.log(Object.hasOwn(obj, "toString")); // => false
// `in`演算子は指定されたプロパティ名が見つかるまで親をたどるため、`Object.prototype`まで見にいく
console.log("toString" in obj); // => true
// const obj = {} と同じ意味
const obj = Object.create(Object.prototype);
console.log(obj.toString === Object.prototype.toString); // => true

型つき固定長の配列が作れるらしい
JavaScriptの配列は可変長のみですが、TypedArrayという固定長でかつ型つきの配列を扱う別のオブジェクトが存在します。
const typedArray = new Int8Array(8);

「JavaScriptはすべてがオブジェクトである」と言われることがあります。 プリミティブ型はオブジェクトではありませんが、プリミティブ型に対応したラッパーオブジェクトが用意されています(nullとundefinedを除く)。 そのため、「すべてがオブジェクトのように見える」というのが正しい認識となるでしょう。

変数宣言が宣言と代入の2つの部分から構成されていると考えてみましょう。
varによる変数宣言は、
宣言部分が暗黙的にもっとも近い関数またはグローバルスコープの先頭に巻き上げられ、
代入部分はそのままの位置に残るという特殊な動作をします。

関数の実行が終了したことと関数内で定義したデータの解放のタイミングは直接関係ないことがわかります。 そのデータがメモリ上から解放されるかどうかはあくまで、そのデータが参照されているかによって決定されます。

thisについて
実行コンテキストが"Script"である場合のthis
実行コンテキストが"Script"である場合、トップレベルのスコープに書かれたthisはグローバルオブジェクトを参照します。
<script>
// 実行コンテキストは"Script"
console.log(this); // => window
</script>
実行コンテキストが"Module"である場合のthis
実行コンテキストが"Module"である場合、そのトップレベルのスコープに書かれたthisは常にundefinedとなります。
<script type="module">
// 実行コンテキストは"Module"
console.log(this); // => undefined
</script>
Arrow Function以外の関数(メソッドも含む)場合のthis
Arrow Function以外の関数(メソッドも含む)におけるthisは、実行時に決まる値となります。 言い方を変えるとthisは関数に渡される暗黙的な引数のようなもので、その渡される値は関数を実行するときに決まります。
メソッド呼び出しにおけるthis
メソッドの場合は、そのメソッドが何かしらのオブジェクトに所属しています。
- ベースオブジェクトを指す!

thisが問題になる場合
"use strict";
const person = {
fullName: "Brendan Eich",
sayName: function() {
// `this`は呼び出し元によって異なる
return this.fullName;
}
};
console.log(person.sayName()); // => "Brendan Eich"
- これは当然
const say = person.sayName;
say(); // => TypeError: Cannot read property 'fullName' of undefined
- これが問題
- これは下記の意味になる
"use strict";
const say = function() {
return this.fullName;
};
// `this`は`undefined`となるため例外を投げる
say(); // => TypeError: Cannot read property 'fullName' of undefined
- thisが行方不明になる!
対処法
- call、apply、bindなどでthisを定義できるよ!

クラスについて
- クラスはJavaとかとそんなに変わらないイメージ
外から直接読み書きしてほしくないプロパティを_(アンダーバー)から始まる名前にするのはただの習慣であるため、構文としての意味はありません。
静的メソッド
- そのままやね
class クラス {
static メソッド() {
// 静的メソッドの処理
}
}
// 静的メソッドの呼び出し
クラス.メソッド();

非同期処理


Promiseが生まれた背景
- 複数の非同期処理をコールバック関数を使って連続的につなげて処理すると下記のように書く必要性があり、可読性が下がる
const sleep = (callback, val) => {
setTimeout(() => {
console.log(val++);
callback(val);
}, 1000);
};
sleep((val) => {
sleep((val) => {
sleep((val) => {
sleep((val) => {
sleep((val) => {}, val);
}, val);
}, val);
}, val);
}, 0);
Promiseの状態
pending:非同期処理の実行中の状態を表す
fulfilled:非同期処理が正常終了した状態を表す
rejected:非同期処理が異常終了した状態を表す
コールバック関数は下記の引数を2つとります
- resolve ・・・ Promiseの状態が、fulfilledになったら resolve が実行されます。
- reject ・・・rejectが実行される場合は、cathchメソッド内のコールバック関数が実行されます。promise内にある、rejectの実引数が渡され、実行されます。

Promiseで書くとこうなる
const sleep = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val++);
resolve(val);
}, 1000);
});
};
sleep(0)
.then((val) => sleep(val))
.then((val) => sleep(val))
.then((val) => sleep(val))
.then((val) => sleep(val));
この関数を例に考えよう!
まず、returnしているのは
new Promise(
(resolve) => {
setTimeout(() => {
console.log(val++);
resolve(val);
}, 1000);
});
STEP 1
実行の起点は「sleep(0)」です
sleep(0)
.then((val) => sleep(val)) // 続く
そして
console.log(0++); // インクリメントが起きる
resolve(1); // ここで解決値が1となる
STEP 2
// 解決値はthenに渡されます
resolve が実行された場合は、thenメソッド内部が実行されます。
.then((val) => sleep(val))
STEP3
// thenの中身はアロー関数です
(val) => sleep(val)
(val) に1が代入されて
sleep(val)に1が渡されます
STEP4
次は「sleep(1)」です...(以下省略)

AsyncとAwaitの登場
関数宣言の先頭に、Asyncがついていたら、Promiseオブジェクトがreturnされることを担保しているよ。
と言うことになります。
Awaitは、Promiseを返却する関数の非同期処理が終わるのを待つ
const sleep = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val++);
resolve(val);
}, 1000);
});
};
const run = async () => {
let val = 0;
for (let i = 0; i < 5; i++) {
val = await sleep(val);
}
};
run();
###STEP1
const run = async () => {
let val = 0;
for (let i = 0; i < 5; i++) {
val = await sleep(val);
}
const run = async () => {
関数宣言の先頭に、Asyncがついていたら、Promiseオブジェクトがreturnされることを担保
val = await sleep(val);
Awaitは、Promiseを返却する関数の非同期処理が終わるのを待つ
- 「sleep(val)」はsleep関数が解決されるのを待ち、その解決値をval変数に代入します。
- 次は、let val = 1;になり後続のforが「i < 5」になるまで処理を繰り返されます。
thenも勿論扱える
- 「run();」は asyncで宣言していてPromiseを返すので ** then** が扱えます
run().then(() => console.log("sssss"));

WeakMapについて
- Mapと同じくマップを扱うためのビルトインオブジェクト
- Mapと違う点は、キーを弱い参照(Weak Reference)で持つ
- 弱い参照とは、ガベージコレクション(GC)によるオブジェクトの解放を妨げないための特殊な参照
- 本来、GCによりメモリから解放できるオブジェクトは、どこからも参照されていないものだけ
- 弱い参照は例外的に、該当するオブジェクトへの弱い参照があったとしても、GCはそのオブジェクトを解放できる
- WeakMapでは不要になったキーとそれにひもづいた値が自動的に削除されるため、メモリリークを引き起こす心配がない

const weakMap = new WeakMap();
let obj = { key: 'value' };
weakMap.set(obj, 'Some Value');
// 他の処理...
// オブジェクトへの参照を削除
obj = null;

JSON
JSON.stringifyメソッドにはオプショナルな引数が2つあります。 第二引数はreplacer引数とも呼ばれ、関数あるいは配列を渡せます。
- 初めて知りました
const obj = { id: 1, name: "js-primer", bio: null };
const replacer = (key, value) => {
if (value === null) {
return undefined;
}
return value;
};
console.log(JSON.stringify(obj, replacer)); // => '{"id":1,"name":"js-primer"}'