JavaScrip Primer
thisはいろんな本を読んでやっと理解できるようになってきた
コンテキストの概念をちゃんと理解することで頭の中でイメージがしやすくなった気がする。
thisの参照先は主に次の条件によって変化する。
- 実行コンテキストにおけるthis
- コンストラクタにおけるthis
- 関数とメソッドにおけるthis
- Arrow Functionにおけるthis
プロトタイプオブジェクトの概念はPythonでもあるから動的型付け言語によく用いられる手法なのかな?
メソッド呼び出しにおけるthisが問題となるパターン1
完全に理解した気がする
問題: thisを含むメソッドを変数に代入した場合
"use strict";
const person = {
fullName: "Brendan Eich",
sayName: function() {
// `this`は呼び出し元によって異なる
return this.fullName;
}
};
// `sayName`メソッドは`person`オブジェクトに所属する
// `this`は`person`オブジェクトとなる
console.log(person.sayName()); // => "Brendan Eich"
// `person.sayName`を`say`変数に代入する
const say = person.sayName;
// 代入したメソッドを関数として呼ぶ
// この`say`関数はどのオブジェクトにも所属していない
// `this`はundefinedとなるため例外を投げる
say(); // => TypeError: Cannot read property 'fullName' of undefined
対応方法
- メソッドを変数に代入するな。
- thisを明示的に指定する
- 関数をラップする
メソッド呼び出しにおけるthisが問題となるパターン2
問題: コールバック関数とthis
"use strict";
// strict modeを明示しているのは、thisがグローバルオブジェクトに暗黙的に変換されるのを防止するため
const Prefixer = {
prefix: "pre",
/**
* `strings`配列の各要素にprefixをつける
*/
prefixArray(strings) {
return strings.map(function(str) {
// コールバック関数における`this`は`undefined`となる(strict mode)
// そのため`this.prefix`は`undefined.prefix`となり例外が発生する
return this.prefix + "-" + str;
});
}
};
// `prefixArray`メソッドにおける`this`は`Prefixer`
Prefixer.prefixArray(["a", "b", "c"]); // => TypeError: Cannot read property 'prefix' of undefined
対応方法
- thisを一時変数へ代入する(下を使うことが多い)
- Arrow Functionでコールバック関数を扱う
Arrow Functionとthis
- Arrow Function で定義されたthisは関数の定義時(静的)に決まる
- Arrow Function以外は関数の実行時(動的)に決まる
静的に決まるとは、Arrow Functionの外側のスコープ(関数)のthisを参照する。
さらに、Arrow Function はthisを暗黙的な引数として受け付けない
変数におけるスコープチェーンの仕組みと同じだな〜
コンテキストの種類とスコープ範囲分かると繋がってくるな
急にメソッド短縮記法を書かないで
いや、出てたけど忘れてただけか
this理解するの大変すぎるけど何度目かの挑戦でもう少しで理解できそうだからちゃんと理解するか
はえー、知らなかった。
Errorオブジェクトはインスタンスの作成時に、そのインスタンスが作成されたファイル名や行数などのデバッグに役立つ情報を持っています。
console.error()で出力するとスタックトレースもコンソールに出力してくれるらしい。
ビルトインエラー
- ReferenceError
- SyntaxError
- TypeErro
元のエラーオブジェクトのスタックトレースが失われる
エラーオブジェクト受け取って再度エラーオブジェクトを返すと、元のエラーオブジェクトのスタックトレースが失われる。
それを解決するにはErrorオブジェクトのcauseオプションを使用する
} catch (e) {
throw new Error("二つ目のエラー", {cause: e})
}
実行順番について
なんかエラーをキャッチできないやつあったけどそういうことだったのかー!と理解した。tryという実行コンテキストがあってその中のエラーだけをキャッチしているイメージなのかな?
非同期処理では、try...catch構文を使っても非同期的に発生した例外をキャッチできません。 次のコードでは、10ミリ秒後に非同期的なエラーを発生させています。 しかし、try...catch構文では次のような非同期エラーをキャッチできません。
try {
setTimeout(() => {
throw new Error("非同期的なエラー");
}, 10);
} catch (error) {
// 非同期エラーはキャッチできないため、この行は実行されません
}
console.log("この行は実行されます");
Promiseオブジェクトは非同期処理の状態や結果をラップするオブジェクトという説明がしっくりきた。
catchに.thenを繋ぐとエラー後に実行される
以下の仕様があるため、catchに.thenを繋ぐとエラー後に実行される。
catchはFulfilled状態のPromiseインスタンスを作成して返すため
thenやcatchメソッドは常に新しいPromiseインスタンスを作成して返すという仕様
[ES2022] Module直下ではawait式が使える
確かに、modulesではawaitそのまま使いたい時あるかも
[コラム] エラーファーストコールバック
元のエラーオブジェクトのスタックトレースが失われる話のやつか
Map/Set
簡単かと思ったらこれも重たかった。
Objectと同じかと思ったら結構違くてびっくり
オブジェクトをキーにとれるからDOMノードや要素に付加情報を付けれるのとかなるほどなと思ったけど、明日になったら大体忘れてそう。
使う場面は多いのかな?実務で出てきたらどう使われているか見るのが一番理解しやすそう
イテレータとジェネレータ
概念はあったけど名前は知らなかった
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"}'
// 配列渡すよ〜
const obj = { id: 1, name: "js-primer", bio: null };
const replacer = ["id", "name"];
console.log(JSON.stringify(obj, replacer)); // => '{"id":1,"name":"js-primer"}'
第三引数はspace引数
インデントの設定
これはプロジェクトに従うので使わなそう!!
const obj = { id: 1, name: "js-primer" };
// replacer引数を使わない場合はnullを渡して省略するのが一般的です
console.log(JSON.stringify(obj, null, 2)); // 2を"\t"にするとタブでインデント
/*
{
"id": 1,
"name": "js-primer"
}
*/
配列ないに変換できない値があるとnullにしてくれる
JSON.stringify静的メソッドはJSONで表現可能な値だけをシリアライズします。 そのため、値が関数やSymbol、あるいはundefinedであるプロパティなどは変換されません。
ただし、配列の値としてそれらが見つかったときには例外的にnullに置き換えられます。
MapやSetのインスタンスは空のオブジェクトに!
// 値がMapのプロパティ
const map = new Map();
map.set("foo", "foo");
console.log(JSON.stringify({ x: map })); // => '{"x":{}}'
toJSONメソッドを使ったシリアライズ
これはなんであるん?
オブジェクトがtoJSONメソッドを持っている場合、JSON.stringify静的メソッドは既定の文字列変換ではなくtoJSONメソッドの返り値を使います。
const obj = {
foo: "foo",
toJSON() {
return "bar";
}
};
console.log(JSON.stringify(obj)); // => '"bar"'
console.log(JSON.stringify({ x: obj })); // => '{"x":"bar"}'
Math
特になし!
第二部:応用編(ユースケース)
Ajax通信
Nodeb.jsでCLIアプリ
TodoApp
const app = new App();がReactでの読み込みに似ててなんだか伏線回収しているみたい。
前職にいたつよつよフロントエンジニアがやっていた構成ってちゃんとしたモデルビューの構成になっていたの思い出して感動している。
ロジックはカスタムフックにカプセル化されていたし、アイテムのビューとリストのビューでもちゃんと分けられていたしテストもしやすい構成になっていたんだろうな。
ちゃんとした構成のアプリ作りたくなってきた。ESlintもしっかりやって、Playwright MCPを使ってテストの自動化までしたい。テストの自動化ってどの企業でも課題にしてそうなんだよな。