#jsprimer 読み直し
久しぶりに読むー
- 2^53-1 (9007199254740991)
- 2進数:
0b01010
- 8進数:
0o644
- 16進数:
0x1234a
- ES2021 から区切り文字としてのアンダースコアが使える
1_000_000_000_000
- いつも Groovy とかと混ざってしまうけど、
'
と"
の文字列は同じ
テンプレートリテラル
- 変数の展開(
${xxx}
)が使えるのはテンプレートリテラル - テンプレートリテラルは3つ重ねなくても( ``` ってしなくても)改行が使える
- インデントはそのまま反映される(Javaとは違う)
// 自分自身とも一致しない
console.log(NaN === NaN); // => false
// Number型である
console.log(typeof NaN); // => "number"
// Number.isNaNでNaNかどうかを判定
console.log(Number.isNaN(NaN)); // => true
falsy values
false
undefined
null
0
0n
NaN
""(空文字列)
nullish とは、評価結果が
null
またはundefined
となる値のことです。
falsy じゃないんだな。だから 0
や false
や空文字を渡しても、右辺の値は使われない
console.log(false ?? "右辺の値"); // => false
console.log(0 ?? "右辺の値"); // => 0
console.log(("" ?? "a") === ""); // => true
へー。
あぁ、そうか。||
が falsy だから Nullish coalescing operator ができたんだ(すぐ後に書いてあった
-
Boolean
コンストラクター関数を使うとboolean
に変換できる - falsy な値(上記の7種類)を渡すと
false
それ以外はtrue
になる -
new Boolean
するとBoolean
オブジェクトになるから動きが違う
ちょっと休憩
けっこう休憩したな?
文字列への変換
-
String
コンストラクター関数を使うと文字列に変換できる - プリミティブに留めておくと良さそう
文字列→数値への変換
-
Number
コンストラクター関数 -
Number.parseInt(文字列, 基数)
も利用可。Number.parseFloat
もある。 - 変換できなかった場合
NaN
になるので、結果がNumber.isNaN
かどうかを確認
NaN
- number型
- 計算に出てくると結果は
NaN
になる -
NaN
同士の比較はfalse
になる - チェックは
isNaN
またはNumber.isNaN
でできる
宣言
- 同じ名前の関数宣言は上書きされる
返り値
-
return;
や、return
なしの場合は、返り値はundefined
引数
- 指定されなかった引数は
undefined
- デフォルト引数(ES2015)は
undefined
に対してのみ適用される。null
を渡した場合はデフォルト値になることはなくnull
が使用される- デフォルト引数登場前は
||
がよく使われていたがこれには falsy の問題がある。 - もし似たようなことをやりたいなら ES2020 で登場した
??
を使う方が良い。けど、デフォルト値で済むはず
- デフォルト引数登場前は
- Rest Parameters [ES2015]
- Destructuring assignment [ES2015]
関数式
- 通常は匿名関数でいいけど、再帰を使うときは名前をつければ内部からはそれで呼び出せる。外部からは呼び出せないので、変数名の方で呼び出す。
- Arrow Function は名前を持つことができない
メソッド
- メソッドは ES2015 で短縮記法が導入された
const obj = {
method() {
return "this is method";
}
};
文と式、条件分岐、は問題なし。
反復処理のところは、for...in 文を使わない、のと
-
Symbol.iterator
という名前のメソッドを実装したオブジェクトはiterable
と呼ばれる -
iterable
オブジェクトは for...of 文で反復処理できる
くらいかな。
キーと値をコロンで区切る
const obj = {
"key": "value"
};
プロパティ名のクォートは省略できる
const obj = {
key: "value"
};
(変数の識別子として利用できないプロパティ名はクォートが必要)
複数の場合はカンマ区切り
const color = {
red: "red",
green: "green",
blue: "blue"
};
プロパティ名と変数名が同じ場合は省略記法が使える(ES2015)
const name = "名前";
const obj = {
name
};
プロパティへのアクセス
- ドット記法とブラケット記法が使える
- けど、キーが識別子の命名規則を満たさない場合はドット記法は使えなくてブラケット記法のみ
- ブラケット記法の場合は変数も使える
- シンボルをキーにする場合もブラケット記法になる
Computed property names (ES2015)
ブラケット記法でキーの部分に変数を使用することができる
const key = "key-string";
const obj = {
[key]: "value"
};
プロパティの追加
存在しないプロパティに値をいれると作成される。やらないほうがいいけど。
プロパティの削除
削除したい場合は delete
演算子でできる
delete obj.key1;
Object.freeze
- strict mode のみ
-
Object.freeze
したあとにプロパティの追加や変更をすると例外が発生するようになる
プロパティの存在確認
in
演算子を使う
if ("key" in obj) {
hasOwnProperty
を使う
if (obj.hasOwnProperty("key")) {
in
はプロトタイプオブジェクトまで見に行く。 hasOwnProperty
はそのオブジェクト自身が持っているかを判定する。
Optional chaining 演算子
- ES2020
- nullish なら undefined を返す
const title = widget?.window?.title ?? "未定義";
console.log(languages?.[langJapanese]?.[messageKey]); // => "こんにちは!"
toString メソッド
実はStringコンストラクタ関数は、引数に渡されたオブジェクトのtoStringメソッドを呼び出しています。 そのため、Stringコンストラクタ関数とtoStringメソッドの結果はどちらも同じになります。
そっか。プリミティブは String コンストラクタ、オブジェクトは toString
って感じの使い分けが読みやすそうかな。
プロパティ名は文字列化される
Symbol 以外。へー。
列挙
Object.keys
-
Object.values
(ES2017) -
Object.entries
(ES2017)
console.log(Object.keys(obj)); // => ["one", "two", "three"]
console.log(Object.values(obj)); // => [1, 2, 3]
console.log(Object.entries(obj)); // => [["one", 1], ["two", 2], ["three", 3]]
オブジェクトのマージ
-
Object.assign
はもう使わなさそう。spread 構文を使う - spread 構文は、オブジェクトリテラルの中だけで使用可能
const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = {
...objectA,
...objectB
};
console.log(merged); // => { a: "a", b: "b" }
プロパティ名がかぶった場合は、後ろのオブジェクトが優先される
Shallow Clone も spread で書けば良さそう
- オブジェクトはObject.prototypeを継承することでtoStringメソッドなどを呼び出せるようになってる
- プロトタイプメソッドとインスタンスメソッドではインスタンスメソッドが優先される
- Object.createメソッドを使うことでプロトタイプオブジェクトを継承しないオブジェクトを作成できる
- でも Map が ES2015 で導入されたから Map が使いやすいところでは Map を使うと良さそう
- それでもオブジェクトリテラルなどが便利なところは多いのでオブジェクトをマップっぽく使うのは続きそう
- インデックスは
0
以上2^32 - 1
未満の整数 - 存在しないインデックスへのアクセスは
undefined
を返す - sparse array (疎な配列)は、未定義の要素が含まれている
-
hasOwnProperty
を使えば要素自体の存在判定ができる
-
- 配列かどうかは
Array.isArray
でチェック-
typeof
では判別できない。"object"
になるため。
-
- 分割代入できる
検索
インデックスを取得
-
Array#indexOf
:===
で比較。なければ-1
を返す -
Array#findIndex
(ES2015) : Predicate を渡す。なければ-1
を返す
要素を取得
-
Array#find
(ES2015) : Predicate を渡す。なければundefined
を返す
指定範囲の要素を取得
- slice を使う
- 新しい配列が作られるが、その要素は Shallow コピーなところに気をつけておく
含まれているかどうか
-
indexOf
ではなくincludes
(ES2016) を使えば、真偽値が返される - Predicate を使いたい場合は
some
を使う
追加と削除
- push/pop 末尾
- unshift/shift 先頭
結合
-
Array#concat
があるけど、ES2015 からは spread 構文を使うだけで良さそう
const array = ["A", "B", "C"];
const newArray = ["X", ...array, "Z"];
console.log(newArray); // => ["X", "A", "B", "C", "Z"]
フラット化
-
Array#flat
(ES2019) で、フラット化ができる - 引数なしだと1段階。すべてをフラット化する場合は
Infinity
を指定する
削除
-
Array#splice
で削除できる。同時に追加もできる。 - 全部削除するなら
length
を0
にすれば良い。その要素数までに配列が切り詰められる - もしくは空の配列をアサインすればいい
Array-like オブジェクト
- length を持つ
- インデックスアクセスができる
- isArray は false
-
Array.from
(ES2015) を使えば配列に変換できる
const argumentsArray = Array.from(arguments);
一部を取得
- slice, substring それぞれの挙動の違いに注意して使う
検索
- indexOf, lastIndexOf
真偽値
- startsWith, endsWith, includes (いずれも ES2015)
正規表現
-
new RegExp
だと呼び出した時点で生成、リテラル (e.g./a+/
) だとパースした時点で生成され不正な場合は構文エラーになる -
new RegExp
だと動的に正規表現を決めることができる
→基本はリテラルで、動的にしたい場合だけコンストラクターかな
正規表現で検索
- search で検索してインデックスを取得できる
- match はマッチした文字列に関する情報を返す
- マッチしない場合は
null
- マッチした場合は結果を含む特殊な配列を返す
- マッチしない場合は
- matchAll (ES2020)
g
フラグ付き用。マッチした結果を Iterator で返す- 以前は
RexExp#exec
でやってたけど、今は matchAll を使えば良い
- 以前は
正規表現で真偽値
-
RegExp#test
がある。
文字列の置換
-
String#replace
- 渡した文字列を置換文字列で置換する。最初に一致したものだけを置換
- 正規表現を指定することもできる。
g
フラグを使ったら全部置換
-
String#replaceAll
(ES2021)- 指定した文字列に一致するものすべてを置換
- 正規表現も指定できる
- どちらもコールバック関数を渡すことで、キャプチャに対する置換ができる
const result = "今日は2017-03-01です".replace(
/(\d{4})-(\d{2})-(\d{2})/g, (all, year, month, day) => {
return `${year}年${month}月${day}日`;
});
console.log(result); // => "今日は2017年03月01日です"
タグつきテンプレート関数
()
を使わずに 関数`テンプレート`
とすると (strings, ...values)
の形で引数が渡される
- 第一引数にはテンプレートの中身が
${}
で区切られた文字列の配列 - 第二引数以降は
${}
の中に書いた式の評価結果
これによって、処理を付け加えることができる。
// 変数をURLエスケープするタグ関数
function escapeURL(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + encodeURIComponent(values[i - 1]) + str;
});
}
String.raw
(ES2015) を使うとそのまま返す
console.log(String.raw`template ${0} literal ${1}`); // => "template 0 literal 1"
Appolo の GraphQL のやつで使われてた気がするー。
- Code Unit = UTF-16 の単位(16ビット = 2バイト)
- Code Point = 文字に対応
- サロゲートペアの場合は2つのCode Unitの組み合わせ(4バイト)
基本的には文字列はCode Unit単位で扱われる
console.log("🍎".length); // => 2
ES2015からCode Pointごとに扱うメソッドや構文が追加されている
-
CodePoint
を名前に含むメソッド -
u
フラグが有効化されている正規表現 - 文字列のIteratorを扱うもの (Destructing, for...of, Array.from など)
u
フラグが有効化されている正規表現
const [all, fish] = "𩸽のひらき".match(/(.)のひらき/u);
console.log(all); // => "𩸽のひらき"
console.log(fish); // => "𩸽"
へー。u
常につけてて良さそう。
length
length
プロパティには、Code Unit単位の長さが入ってるので、文字列の長さを知りたければ Array.from
で一回Code Point単位の配列に変換してあげる:
console.log("リンゴ🍎".length) // => 5
console.log(Array.from("リンゴ🍎").length); // => 4
JavaScriptでは、プリミティブ型の値に対してプロパティアクセスするとき、自動で対応するラッパーオブジェクトに変換されます。
そっかー!
一方、明示的に作成したラッパーオブジェクトからプリミティブ型の値を取り出すこともできます。
ラッパーオブジェクト.valueOf
メソッドを呼び出すことで、ラッパーオブジェクトから値を取り出せます。
ふむふむ
この2つを明示的に使い分ける利点はないため、常にリテラルを使うことを推奨します。
すっきりしたー!!
じゃ、ラッパーオブジェクトの new
をすることはなくて、コンストラクター関数で変換するのに使うくらいか。
-
var
の巻き上げ (hositing) は、一番近い関数またはグローバルスコープ - 関数宣言も一番近い関数またはグローバルスコープに巻き上げられる
即時実行関数
jQuery とかでよく見たやつ
(function() {
// 関数のスコープ内でfoo変数を宣言している
var foo = "foo";
console.log(foo); // => "foo"
})();
var
だとブロック内で宣言しても巻き上げられちゃうから↑みたいにしないといけなかったけど、ES2015 で let
と const
が導入されたから、ブロックスコープでやればいい
{
const foo = "foo";
console.log(foo); // => "foo"
}
Arrow Function 以外
- メソッドとして定義されている関数はメソッドとして呼ぶこと
どうしても this
を固定したい場合には
関数.call(thisの値, ...関数の引数);
関数.apply(thisの値, [関数の引数1, 関数の引数2]);
で呼び出すか
関数.bind(thisの値, ...関数の引数); // => thisや引数がbindされた関数
で束縛した関数を作り出す
Arrow Function
- ES2015 より前は
var that=this
みたいにしてた - けど、ES2015 では Arrow Function が導入されたのでこれを使えば良い
- Arrow Function自体は
this
を持たず、外側のthis
を参照する - これは関数の定義時に Static に決まる
自分の結論
-
this
はオブジェクトかクラスの中だけで使うのが良さそう - コールバック関数には Arrow Function を使えば良さそう
クラス宣言
class MyClass {
constructor() {
}
}
クラス式
const MyClass = class MyClass {
constructor() {}
};
const AnonymousClass = class {
constructor() {}
};
コンストラクタ関数
- コンストラクタ関数は省略可能
- 省略すると空のコンストラクタ関数が定義される
- コンストラクタ関数は
new
演算子でインスタンス化する際に呼び出される。
プロパティ
- コンストラクタ関数内では
this
は、これから新しく作るオブジェクトを指す -
this
にプロパティを設定することでクラスにプロパティを設定する
コンストラクタ関数は直接は呼び出せない
MyClass(); // => TypeError: class constructors must be invoked with |new|
- コンストラクタ関数では
return
文で値を返すべきではない - 慣習としてクラス名は大文字で始める
ES2015以前はクラス構文がなかったので、関数でクラスを表現していた
- ただ、それだと関数として呼び出せてしまうので、クラス構文を使ったほうが良い
プロトタイプメソッド
- オブジェクトリテラルと異なり、
key: fun
の形式では定義できない - また、メソッド同士の間にカンマは入れない
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
}
インスタンスに対するメソッド
-
this
に対してメソッドを定義すればよい - Arrow Function を使うことができる(ので
this
を bind できる - プロトタイプメソッドよりインスタンスメソッドが優先される
class Counter {
constructor() {
this.count = 0;
this.increment = () => {
this.count++;
};
}
}
アクセッサプロパティ
-
get
set
でアクセッサプロパティを定義できる - それによって、値の取得や設定時に別の処理を差し込むことができる
class NumberWrapper {
constructor(value) {
this._value = value;
}
get value() {
console.log("getter");
return this._value;
}
set value(newValue) {
console.log("setter");
this._value = newValue;
}
}
ES2021 時点では、プライベートプロパティを定義できなので、直接読み書きしてほしくないプロパティを _value
のように習慣としてアンダーバーで開始する(けどただの習慣なので普通にアクセスはできる
(#value
みたいにしてプライベートなプロパティやメソッドを定義できるようになりそうではある)
静的メソッド(クラスメソッド)
- メソッド名の前に
static
をつければ良い - 静的メソッドの
this
はクラス自身を指す。継承してる場合は子クラスが入るっぽい。それは便利そう。 - 静的メソッドも子クラス経由で呼び出せる(プロトタイプチェーンによる
- super 経由で親の静的メソッドも呼び出せるみたいだけど・・・僕は使わなさそう
Map
初期化
const map = new Map([["key1", "value1"], ["key2", "value2"]]);
サイズプロパティ
map.size
追加と取り出し
map.set("key", "value1");
map.get("key");
チェック
map.has("key");
削除
map.delete("key");
map.clear();
反復処理
map.forEach((value, key) => {
results.push(`${key}:${value}`);
});
for (const key of map.keys()) {
keys.push(key);
}
for (const [key, value] of map.entries()) {
entries.push(`${key}:${value}`);
}
// map.entries() と同じ
for (const [key, value] of map) {
results.push(`${key}:${value}`);
}
Object と Map
Map でだいたいのことはできるけど、Object を渡される設計になっていたり、Object の方がリテラルで書きやすかったりするので、Object を使うことも多い
キーの等価性と NaN
- Map のキーの判定は基本的には
===
- ただし
NaN
だけは違っていてNaN
同士は常に等価とみなされる
Set
重複する値を持たない
// 初期化
const set = new Set(["value1", "value2", "value2"]);
// サイズプロパティ
set.size
// 追加
set.add("a");
// 確認
set.has("a")
// 削除
set.delete("a");
set.clear();
反復処理
set.forEach((value) => {
results.push(value);
});
for (const value of set) {
results.push(value);
}
WeakMap と WeakSet は欲しくなったときに詳しくみるくらいでもいいかな
JSON
オブジェクトの
-
JSON.parse
でパース -
JSON.stringify(obj)
で JSON に変換
JSON.stringify
JSON.stringify(value[, replacer[, space]])
replacer は関数または配列
関数の場合
関数の場合、 key と文字列化される value の2つの引数を取ります。キーをもつオブジェクトが replacer では this 引数として提供されます。
最初、 replacer 関数が、文字列化されるオブジェクト自体を表すキーとして空文字列で呼び出されます。それから、文字列化されるオブジェクトのそれぞれのプロパティや配列に対して呼び出されます。
面白い。使うときにはドキュメント見ながら UT 書かなきゃな。
配列の場合
replacer が配列である場合、その配列の値は結果の JSON 文字列に含めるプロパティの名前を示します。
space 引数
インデントを調整できる。これも使うときによもっと。
toJSON
オブジェクトが toJSON メソッドを持つ場合は JSON 化するときに、それが使われる
toISOString()
で ISO 8601 形式の文字列に変換できる
console.log(new Date().toISOString()); // 2021-10-06T12:16:48.940Z
任意の時刻のインスタンス化
// エポックタイムからのミリ秒
new Date(1136214245999);
// ISO 8601文字列から
new Date("2006-01-02T15:04:05.999Z");
// 非推奨(ローカルのタイムゾーンに依存してしまうから)
new Date(year, month, day, hour, minutes, seconds, milliseconds);
// 代わりに UTC を使ってミリ秒を取得して生成する
const ms = Date.UTC(2006, 0, 2, 15, 4, 5, 999);
const date2 = new Date(ms);
ビルトインの Date は機能が不十分なので、ライブラリを使ったほうが良いみたい。
けど Moment.js もメンテナンスモードだし、何がいいんだろう?
Temporal が Stage3 か
使うときに MDN 見たら良いかなとは思うけど
// 三角関数
Math.sin(rad90);
// 乱数
Math.random();
// 最大最小
Math.max(...numbers);
Math.min(...numbers);
// 切り捨て・切り上げ・四捨五入
Math.floor(1.3);
Math.ceil(1.3);
Math.round(1.3);
// 小数点以下を切り落とす(ES2015)
// 負の値のときに↑の floor とは動きが異なる
Math.trunc(1.3);
第一部読み終わった。基本の復習はこんなところかな。