第一部: 基本文法
データ型とリテラル
-
typeof null
はobject
となる -
""
と''
は全く同じ - 複数行の文字を入れるなら`
-
undefined
はただのグローバル変数で、undefined
という値を持っているだけ- リテラルではない
演算子
- JSでは
10 + 0.5
は10.5
となる> 数値は内部的にIEEE 754方式の浮動小数点数として表現されています
-
NaN === NaN
はfalse
になる -
==
は使うべきではない- 暗黙的な型変換をするから
1 + true; // => 2
- 使ってよい例外:
value == null
でnullとundefindを両方比較するとき- しかし意図が読めないときもある
- 暗黙的な型変換をするから
-
indexOf
は見つからないとき-1
を返す-
if (~str.indexOf("木"))
で 0 となる- 0 は false なので実行されない
-
-
includes("木")
で見つけられる -
""
(空文字列)はfalse
暗黙的な型変換
1 + "2"; // => "12"
-
Number.parseInt("1", 10);
で1をパースして10進数で取り出す- 文字列とundefindは
NaN
となる-
Number.isNaN(NaN);
でNaNとわかる
-
- 文字列とundefindは
- sumでundefinedが入ると"NaN"になる
-
if (typeof value !== "number")
で防いだりする
-
- 空文字列は
typeof str === "string" && str.length === 0;
で判定する -
const obj1 = { foo: 'bar' }; const obj2 = { foo: 'bar' }; console.log(obj1 === obj2);
- falseになる
- 別々に作ったら別のオブジェクトだから
- falseになる
関数と宣言
- 仮引数より呼び出し時の引数が少ない時、余ったものは
undefined
になる- 溢れたときは無視される
-
prefix || 'デフォルト';
だと空文字列の時にもデフォルトが入ってしまう- デフォルト引数にする
-
prefix ?? 'デフォルト';
にする
- まとめて入れたり、残りをまとめたり、まとめて出したりは
...array
でできる- Rest parameters
- 関数の中でのみ使える
arguments[0]
-
function() { console.log(arguments[0]); }
のように引数が定義されてなくても使える- Rest parameters が使えるならこれは使わない方がいい
-
-
function printUserId({ id }) { ... }
でuser.id
のidだけとれる-
const { id } = user;
でもとれる- 分割代入
-
- 関数は関数オブジェクト
- アロー関数
- 常に名前がない
-
this
が決まっている - 短く書ける
-
new
できない -
argument
変数は使えない
- 同じ名前の関数宣言は上書きされる
-
function
とvar
を使ったときだけ起こる
-
- 引数となる関数はコールバック関数とよぶ
- コールバック関数を使う関数やメソッドを高階関数とよぶ
- JSでは関数とメソッドの違いはあまりない
文と式
- 式
- 値を生成し、変数に代入できるもの
- 文
- if文など
- ブロックで終わる文にはセミコロンは不要
- 匿名関数は式だからセミコロンは必要
条件分岐
- falsyな値
false
undefined
null
0
0n
NaN
-
""
(空文字列)
- falsyな値以外は真偽値に変換すると
true
となる
ループと反復処理
-
some
は一度でもtrue
が返ってくると反復処理を終了するnumbers.some(isEven);
-
filter
はtrue
になった値だけを集められるarray.filter(isEven);
-
reduce
で反復処理ができる-
array.reduce((前回の値, 現在の値) => { return 次の値; }, 初期値);
numbers.reduce((total, num) => { return total + num }, 0);
-
オブジェクト
- オブジェクトとはプロパティの集合
- プロバティはキーとバリューの対
- プロパティ名と変数名が同じだと
{ name }
と省略して書ける- importの時などに使う
- プロパティ名に変数を使うときは
[]
を使うlanguages[myLang]
- オブジェクトのプロパティを変数として使うときは分割代入する
const { ja, en } = languages;
- プロパティの削除は
delete obj.key1
- JSの
const
は再代入を防ぐだけ- 値の変更はできてしまう
-
const obj = { key: "value" };
でもobj.key = "Hi!";
できる
-
- 変更防止は
Object.freeze
を使う
- 値の変更はできてしまう
- 存在しないプロパティにアクセスすると
undefined
となる- 例外は発生しない
- プロパティを持つか確認するには?
-
undefined
と比較する-
obj.key !== undefined
- 値が
undefined
のときと区別できない- 最終的に取得したいのが値の時に使う
- 値が
-
-
in
演算子if ("key" in obj) { ... };
-
hasOwnProperty
メソッドif (obj.hasOwnProperty("key")) { ... }
-
- nullの可能性があるときには
obj?.a?.b
widget?.window?.title ?? "未定義"
-
[]
でも使えるlanguages?.[ja]?.[messageKey]
-
toString()
とString()
は同じ - オブジェクトのプロパティ名は暗黙的に文字列に変換される
- オブジェクトを配列にする
-
Object.keys(obj)
でkeyの配列にする -
Object.values(obj)
でvalueの配列にする -
Object.entries(obj)
でkeyとvalueの配列にする
-
-
Object.assign
で複製やマージができる-
const merged = Object.assign({}, objectA, objectB);
- プロパティが重複すると後ろで上書きされる
-
const merged = { ...objectA, ...objectB };
でもマージできる -
const cloneObj = Object.assign({}, obj);
で浅い複製できる
-
プロトタイプオブジェクト
- 空オブジェクトでも
.toString()
を呼び出せる-
Object.prototype
のメソッドを継承しているから- プロトタイプメソッド
- 同名のインスタンスメソッドがあればそれが優先
- プロトタイプメソッド
-
-
hasOwnProperty
とin
の違い-
in
はプロトタイプまで遡る
-
-
Object.create(null)
で本当に空のオブジェクトを作れる- 昔は
Map
の代わりに使われていた-
const obj = {};
でもobj["toString"]
でアクセスできてしまうから
-
- 昔は
配列
- 最後の要素へのアクセスは
array[array.length - 1]
でできる - 存在しないインデックスへのアクセスは
undefined
になる- オブジェクトでも同じ
- 配列は常に
length
の数だけ要素を持っているとは限らない- 疎な配列
-
const sparseArray = [1,, 3];
-
sparseArray.length
は3
となる-
sparseArray[1]
はundefined
-
-
-
- 疎な配列
- 配列かどうか確認するには
isArray
-
typeof array
では"object"
となる
-
- 配列も分割代入できる
- 疎な配列があるから本当に未定義なものと区別できない
-
hasOwnProperty
で区別する-
sparseArray.hasOwnProperty(1)
がfalse
となる
-
-
- どの位置にあるか知りたい
-
indexOf
とlastIndexOf
-
ary.indexOf("JS");
- なければ
-1
が返ってくる
- なければ
-
- オブジェクトには
findIndex
を使うcolors.findIndex((obj) => { return obj.color === "blue" });
-
- 条件に一致する要素を取得する
colors.find((obj) => { return obj.color === "white" });
- 指定範囲の要素を取得する
slice(1, 4)
- 目当てのものが含まれているか確認する
-
inclues
- オブジェクトには使えない
-
some
colors.some((obj) => return obj.color === "blue"; });
-
- オブジェクトには使えない
-
- 追加と削除
-
push
とpop
-
unshift
とshift
-
- 結合したい
array.concat(["D", "E"]);
- flattenにしたい
-
newArray = ["X", "Y", "Z", ...array];
-
["X", ...array, "Z"]
もできる
-
newArray = ["X", "Y", "Z"].concat(array);
- ES2019なら
flat(Infinity)
も使える- これ以上フラット化できなくてもそのまま返す
-
- 任意のインデックス要素を削除する
-
array.splice(インデックス, 削除する要素数)
- 自動的に詰められるから疎にはならない
-
- すべての要素を削除
-
array.length = 0;
とすると配列が空になる- その要素数に切り詰められるから
-
- 注意する破壊的メソッド
sort
- 非破壊でコピーする
array.slice()
-
array.concat()
- 引数なしで呼び出すとコピーを返す
- コピーしてから破壊的メソッドを使う
- 引数なしで呼び出すとコピーを返す
- 指定の要素だけ集める
array.filter((currentValue, index, array) => { return currentValue % 2 == 1; });
-
reduce
array.reduce((累積値, 要素, インデックス, 配列) => { return 処理;}, 初期値)
文字列
- 分解したり繋げたりする
const strings = "赤・青・黄".split("・").join("、");
- 正規表現を使って抜き出す
const strings = str.split(/\s+/);
- "?"以降を抜き出す
-
const indexOfQuery = url.indexOf("?"); const queryString = url.slice(indexOfQuery):
-
slice
とsubstring
はほとんど同じ
-
-
- 文字列の検索
str.startsWith()
str.endsWith()
str.includes()
- 正規表現
const patternA = /パターン/フラグ;
-
const patternB = new RedExp("パターン文字列", "フラグ");
- 関数として呼び出されるまで評価されない
- 動的に変更できる
- 関数として呼び出されるまで評価されない
str.search()
-
"文字列".match(/パターン/);
- マッチしない時はnullを返す
-
/[a-zA-Z]+/g
で見つかっても最後までやる -
/バターン1(パターン2)/
でカッコを取り出せる
- そのパターンにマッチするものがあるか調べる
-
/^にわ/.test(str)
- 繰り返しや文字の集合なども検索できる
-
- 基本はStringメソッドでやる
- 柔軟性や曖昧検索のときは正規表現+コメントする
- 置換
str.replace("文字", "");
-
str.replace(/文字/, "");
- コールバックもできる
dateString.replace(/(\d{4})-(\d{2})-(\d{2})/, (all, year, month, day) => { ... };
- コールバックもできる
- URLは
getResource()
-
scheme
、host
、pathname
- 最後の
/
は削除してから使う- Node.jsのPathモジュールを使う
- 最後の
-
- タグ付きテンプレート関数
- tag
template ${0} literal ${1}
; -
function tag(strings, ...values) { ... };
- valuesが取れる
String.raw()
- tag
文字列とUnicode
- JSでは「文字列は
Code Unit
が順番に並んだもの」として扱われる- リンゴの絵文字の length は 2 になる
- サロゲートペア
-
const codePoints = Array.from("リンゴ🍎"); // => ["リ", "ン", "ゴ", "🍎"]
- 完璧ではない
- ビルトインだけでは難しい
- 完璧ではない
- リンゴの絵文字の length は 2 になる
- 正規表現のときは
u
をつける-
const [all, fish] = "𩸽のひらき".match(/(.)のひらき/);
- 文字化けする
-
const [all, fish] = "𩸽のひらき".match(/(.)のひらき/u);
- 基本的には
u
を付ける
- 基本的には
-
ラッパーオブジェクト
- なぜ型がメソッド呼び出しできるのか
- プリミティブ型は自動的にラッパーオブジェクトに変換されるからメソッド呼び出しできる
- JSはすべてがオブジェクトのように「見える」
- すべてがオブジェクトではない
- JSはすべてがオブジェクトのように「見える」
- プリミティブ型は自動的にラッパーオブジェクトに変換されるからメソッド呼び出しできる
関数とスコープ
- 関数を定義する
- 新しいスコープを作るということ
- スコープとは
- 参照できる範囲を決めるもの
- 関数スコープ
function fn() { 関数スコープ };
- ブロックスコープ
if() { ブロックスコープ };
- スコープチェーン
-
{ { } }
- 内側から外側を順番に参照できる
-
- グローバルスコープ
- グローバル変数
- ビルトインオブジェクト
-
undefined
やisNaN
-
Array
やRegExp
-
document
やmodule
-
- むやみにグローバルスコープへ変数定義しない
- 外側の変数が隠蔽されるから
- shadowing
- 関数を使って小さなスコープにして書く
- 外側の変数が隠蔽されるから
-
var
は巻き上げする- ブロックスコープを無視してしまう
- クロージャー
- 「外側のスコープにある変数への参照を保持できる」
- グローバル変数を減らせる
- 高階関数を作る
- 静的スコープ
- 変数の中身は静的に決まる
- 内側にいないなら一つ外側のスコープを確認する
- 変数の中身は静的に決まる
- メモリ管理の仕組み
- 解放はあくまでそのデータが参照されているかどうかで決まる
- 「外側のスコープにある変数への参照を保持できる」
関数とthis
- 実行コンテキスト
- Script
-
this
はwindow
オブジェクト
-
- Module
-
this
はundefined
- ES2020では
globalThis
- ES2020では
-
- Script
- アロー関数以外の関数のthis
- ベースオブジェクト
-
self
のようなもの- 実行時に決定される
- なければ
undefined
-
-
call
、apply
、bind
で明示的に指定できる
- ベースオブジェクト
- アロー関数の
this
- 外側で最も近い関数の
this
となる- 静的に決まる
- 外側で最も近い関数の
非同期処理:コールバック/Promise/Async Function
- 同期的なブロック処理
- ブラウザでは大問題になる
- スクロールが効かなくなる
- ブラウザでは大問題になる
- 非同期処理はメインスレッドで実行される
- 非同期処理も同期処理の影響を受ける
- 非同期なタイミングで実行される処理
- 普通に
try...catch
書くと非同期処理のエラーは処理できない - エラーファーストコールバック
- 共通ルールの一つ
- 処理が失敗したら
error
にエラーオブジェクトを渡す - 処理が成功なら2番目以降の引数に結果を渡す
fs.readfile("./example.txt", (error, data) => { if(error) { ...} else { ... } });
- 処理が失敗したら
- 共通ルールの一つ
- Promise
- 成功したら
resolve
- 失敗したら
reject
const executor = (resolve, reject) => {};
-
then
メソッドで成功時と失敗時の処理を渡す -
try...catch
を使わなくても例外がキャッチされる-
catch
はpromise.then(undefined, onRejected)
のシンタックスシュガー
-
- 成功したら
- Promiseの状態
- Fullfilled
-
resolve
成功したとき
-
- Rejected
-
reject
失敗したとき
-
- Pending
- FullfilledまたはRejectedではないとき
-
new Promise
でインスタンスを作成したとき- 最初はPendingで、一度でも変化したらそこからは変わらない
-
resolve
した後のreject
は呼び出されない -
resolve
した後にもう一度resolve
しても呼び出されない
-
- 最初はPendingで、一度でも変化したらそこからは変わらない
- Fullfilled
-
Promise.resolve
- 最初からFullfilledな
Promise
インスタンスを作る-
Promise.reject
もある- テストコードで使われる
- 短く書けるから
- テストコードで使われる
-
- 最初からFullfilledな
- Promiseチェーン
- 失敗時は一番近い失敗処理が呼び出される
- 途中の
then
は無視される- 失敗を一度キャッチするとまたチェーンに戻る
-
catch
はFullfilled状態のPromiseインスタンスを作成するから -
return Promise.reject(new Error("失敗"));
- これはRejected状態となる
- キャッチしてもRejected状態を継続できる
- これはRejected状態となる
-
- 失敗を一度キャッチするとまたチェーンに戻る
- 例外時も同じ
- 途中の
-
return
で値を返すと次のthen
へ引数として渡せる -
Promise#finally
は必ず呼び出される-
isLoading
をfalse
にするなど
-
-
then
ごとに配列に値をpush
していく使い方もできる-
Promise.all
で複数のPromiseもまとめられる- どれを先に取得しても問題ないとき
- 一つでもRejectedとなったら失敗処理が呼び出される
- どれを先に取得しても問題ないとき
-
-
Promise.race
- 1つでもSettled状態になれば次の処理をする
- 非同期処理のタイムアウトが作れる
- 一定時間経過しても処理が終わってないならエラーとする
-
timeout
とやりたい処理をPromise.race
の引数にする
-
- 一定時間経過しても処理が終わってないならエラーとする
- 非同期処理のタイムアウトが作れる
- 1つでもSettled状態になれば次の処理をする
- 失敗時は一番近い失敗処理が呼び出される
- Async Function
async function doAsync() { return "値"; }
-
function doAsync() { return Promise.resolve("値"); }
- 同じ
- 必ず
Promise
インスタンスを返す-
Promise
を返すただの関数と何も変わらない
-
-
await
が使える
- 必ず
- 同じ
- どれにでもつけられる
async function fn1() {}
const fn2 = async function() {};
const fn3 = async() => {};
const obj = { async method() {} };
-
await
- 同期処理のように書ける
-
Promise
のresolveされた値がawait
の返り値になる-
const value = await Promise.resolve(42);
- 例外があったらRejectedなPromiseが返る
-
try...catch
構文でキャッチできる- 同期処理と同じ
-
- 例外があったらRejectedなPromiseが返る
-
- 非同期でもループ処理ができる
-
Promise.all
も使える- Promiseを複数作ってから
Promise.all
に渡す-
const promises = resources.map(function(resource) { return fetch(resource); });
const responses = await Promise.all(promises);
-
- Promiseを複数作ってから
- Async Funtion の中でのみ使える
- 外の処理は止まらない
- UIなどの処理が止まってしまうから
- コールバック関数の時に注意
-
for
をforEach
に単純に変えられない -
Promise.all
でまとめるか
-
- 外の処理は止まらない
Map/Set
- Map
- 初期値で渡せるのはエントリーの配列
new Map([["key1", "value1"], ["key2", "value2"]]);
-
size
で数がわかる -
set
で追加する- 同じキーは上書きされる
-
get
で取り出す -
has
でそのキーがあるか確認できる -
delete
で削除する -
clear
で全て削除する -
keys
,values
が返すのは配列ではない-
Array.from(map.keys());
- 配列に変換して反復処理をする
-
- マップとしての
Object
との違い- デメリット
-
Object
はprototypeメソッドで意図しない動きをすることがある- 昔は
Object.create(null)
のようにして使っていた-
Map
が導入された
-
- 昔は
- キーは
Symbol
のみ
-
- メリット
- リテラル表現で作成しやすい
-
JSON.stringify
で変換できる - 多くの場所で使われている
- デメリット
- 初期値で渡せるのはエントリーの配列
- WeakMap
- キーを弱い参照で持つ
- ガベージコレクションを妨げない
- iterableではない
-
keys
やsize
がない
-
- 使い方
- イベントリスナーを管理する
- 使われなくなったら消える
- キャッシュとして一時的に計算結果を保存する
- イベントリスナーを管理する
- キーを弱い参照で持つ
- Set
- 同じ値を入れると1つのみ格納される
- インデックスはない
-
forEach
が使える
- WeakSet
- データの一意性を確認することに特化したセット
JSON
-
JSON.parse
-
json = [1, 2, 3]
なら返り値も配列 - パースできないと例外が投げられる
- 基本的に
try...catch
される
- 基本的に
-
-
JSON.stringify
- 第二引数に
replacer
を渡せる- keyがnullならundefinedにする処理
- ホワイトリストとしてkeyの配列を渡せる
-
["id", "name"]
だけを抜き出す
-
- 第三引数にフォーマット時のインデントを設定できる
-
JSON.stringify(obj, null, 2)
ならスペース2個でインデント-
"\t"
もできる
-
-
- Symbolやundefinedは変換されない
- オブジェクト内に
toJSON
がある場合- その返り値のみ使う
- 特殊な形式でシリアライズできる
- その返り値のみ使う
- 第二引数に
Date
-
Date.UTC(2006, 0, 2, 15, 4, 5, 999)
- UTCなのでローカルのタイムゾーンに影響されない
-
getMonth
で取得すると+1
する必要があるconst mm = String(date.getMonth() + 1).padStart(2, "0");
- ほとんどライブラリを使う
moment()
Math
- 乱数をつくる
Math.random()
ECMAScriptモジュール
- なぜモジュールを使うのか
- 保守性
- 依存性の高いコードをまとめて、他への依存性を減らせる
- 名前空間
- グローバルを汚染しない
- 再利用性
- コピペせずに再利用できる
- 保守性
- 名前つきエクスポート/インポート
export { foo };
export function bar() {};
import { foo, bar } from "./my-module.js";
- エイリアス
export { internalFoo as foo };
import { foo as myFoo } from "./named-export-alias.js";
- デフォルトエクスポート/インポート
- デフォルトエクスポートに名前をつけてインポートする
ECMAScript
- その機能がどのような経緯で入ったのかを調べる手段を持っておく
- その機能が何を解決するために導入されたのかを知る
- 調べたいと思ったときに調べることができるように、調べ方を知っておくことが重要
- その機能が何を解決するために導入されたのかを知る