
ブログをNext.js14(App Router)でリファクタリングしました
ここ数年で追加されたJavaScrip(ECMAScript)tの新機能を遡ってまとめてざっと振り返る記事です。
機能と使い方の要点を簡潔にまとめておさらいするような記事なので、より詳しい使用方法などは適宜リンクなどをたどって下さい。
配列の要素を後ろから検索する、findLast()、findLastIndex()の2つのメソッドが増えました。
メソッドの引数は、先頭から検索するバージョンのfind()とfindIndex()と同じになります。
すなわち、検索してマッチさせる要素の条件を関数として記述します。
const ary = [1, 3, 8, 5, 2]
// 配列の後ろから検索して3より大きい初めての値
ary.findLast(elm => elm > 3) // 5
// 配列の後ろから検索して、3より大きい初めての値のインデックス
ary.findLastIndex(elm => elm > 3) // 3
すなわち、ブラウザ環境でシェバンをコメントとして無視するようになりました。
シェバン(shebang)とは、シバンなどとも言い、Unix系システムにおいて、ファイル先頭行にかかれる!# ~ から始まる一文です。
Unix系のシステムのNode環境でシェバンを書いて実行していたプログラムを、ブラウザ環境等でも使いまわせるようになったということです。
コードのトップレベルならば、わざわざasyncで関数を作らずにawait句を直接かけます。
// トップレベル
const hoge = await fetch(`https://jsonplaceholder.typicode.com/todos/1`)
.then(res => res.json())
console.log(hoge)
あくまでトップレベルでの話なので、関数の中では従来通りasync句が必要です。
// 関数の中はasync句が必須
const func = async () => {
const hoge = await fetch(`https://jsonplaceholder.typicode.com/todos/1`)
.then(res => res.json())
console.log(hoge)
}
func()
2番目の項目が重要で、importしたモジュールがトップレベルawaitを持っていた場合の処理順などに影響します。
静的importか動的importかによって処理順が変わるのですが、詳しい解説がこちらに乗っているのでリンクさせていただきます。
クラスのメンバの前に#をつけることでprivateにできます。
class MyClass {
#name = "taro"
age = 10
showName() {
console.log(this.#name)
}
#showAge() {
console.log(thie.age)
}
}
const c = new MyClass
c.age = 20
console.log(c.age)
// c.#name = 40 //エラー
c.showName()
// c.#showAge() //エラー
static句で静的メンバが使えるようになりました。静的メンバとは、クラスをインスタンス化せずとも利用できるメンバのことです。
class MyClass {
static #name = "taro"
static age = 10
static showName() {
console.log(this.#name)
}
static #showAge() {
console.log(thie.age)
}
}
console.log(MyClass.age)
// console.log(MyClass.#name) //エラー
MyClass.showName()
// MYClass.#showAge() //エラー
クラス内でstaticブロックが利用できます。staticブロックはインスタンス宣言時よりも前のクラス評価時に実行されます。
staticメンバ自身、あるいはそれを利用した初期化処理等に利用できます。
class MyClass {
static name = "taro"
static age
static {
MyClass.age = 10
}
}
console.log(MyClass.age) //10
あるクラスのインスタンスかどうか判定するのに instanceof演算子が利用できますが、setPrototypeOf() を使用すると、あるクラスのインスタンスでないのにtrueを返してしまいます。
class MyClass {}
class YourClass {}
const my = new MyClass
const your = new YourClass
Object.setPrototypeOf(my, your)
console.log(my instanceof YourClass) //true
プライベートメンバとそれに使えるin演算子によって、以下のように厳密にインスタンス判定が可能となります。
class MyClass {
#brand
static isMyClass(instance) {
return #brand in instance
}
}
class YourClass {
#brand
static isYourClass(instance) {
return #brand in instance
}
}
const my = new MyClass
const your = new YourClass
Object.setPrototypeOf(my, your)
console.log(YourClass.isYourClass(my))//false
配列や文字列で従来のブラケット[]アクセスの他に、at演算子でアクセスが可能となります。
このatの便利な点は、負のインデックスを使用することで配列の後ろからアクセスが可能になることです。
つまり後ろから数えて何番目かを簡単に指定できます。python等では従来からある機能ですね。
const hoge = ["foo", "bar", "piyo"]
console.log(hoge.at(1)) // "bar"
console.log(hoge.at(-1)) // "piyo"
console.log(hoge.at(-2)) // "bar"
//従来はhoge[hoge.length - 1]としてアクセスしてた
あるオブジェクトがあるプロパティを持つかどうか判定するために、Obejct.property.hasOwnPropertyが利用されてきました。(Object.hasPropertyはプロパティ汚染があるため非推奨)。Obejct.property.hasOwnPropertyという記述はとても長いため、そのショートハンドとして、Object.hasOwnが利用化できるようになりました。
const obj = {
hoge: 2
}
console.log(Object.hasOwn(obj, "hoge")) // true
次のようなコードでは、従来はエラーがチェーンしていてもどこでエラーが発生しているか伝えるのが困難でした。
function A() {
try {
B();
a
} catch (e) {
throw new Error("Error A");
}
}
function B() {
try {
C();
b
} catch (e) {
throw new Error("Error B");
}
}
function C() {
try {
c;
} catch (e) {
throw new Error("Error C");
}
}
try {
A();
} catch (e) {
console.log(e);
// A,B,Cのどの関数でエラーが発生していようがError A
}
新しく追加されたcauseプロパティを利用すると、エラーが発生したところから親をたどれます。
function A() {
try {
B();
a
} catch (e) {
throw new Error("Error A", {
cause: e,
});
}
}
function B() {
try {
C();
b
} catch (e) {
throw new Error("Error B", {
cause: e,
});
}
}
function C() {
try {
c
} catch (e) {
throw new Error("Error C", {
cause: e,
});
}
}
try {
A();
} catch (e) {
console.log(e); // Error A
console.log(e.cause); // Error B
console.log(e.cause.cause); // Error C
console.log(e.cause.cause.cause); //ReferenceError: c is not defined
}
正規表現にdフラグを付けることで、正規表現でマッチした文字列のインデックス情報がindicesとして付与されます。
const reg = /あ(.*)おか(.*)こ/d
const result = "あいうえききおかかきけこ".match(reg)
console.table(result)
// indices │ [ 0, 12 ] │ [ 1, 6 ] │ [ 8, 11 ]
数値を_(アンスコ)をで区切って人間が見やすく記述することが可能です。マシンからしたらアンスコがあってもなくても変わりません。
const num = 1_000_000_000
// 1000000000と同じ
論理演算子と代入演算子を同時に記述可能な演算子が3つ追加されました。
a ||= 0 // a || a = 0と同じ
a &&= 0 // a && a = 0と同じ
a ??= 0 // a ?? a = 0と同じ
replaceAllメソッドが導入され、文字列の一括置換が簡単にできるようになりました。
const str = "string A string B string C string D"
console.log(str.replaceAll("string", "hoge"))
// hoge A hoge B hoge C hoge D
このように直感的に置換できます。従来はreplaceメソッドと正規表現を組み合わせる必要がありました。
Promise.anyは、複数の非同期処理を同時に走らせ、一番最初にresolveした値を採用します。
const promiseA = new Promise((resolve, reject) => {
setTimeout(() => reject("A"), 1000)
})
const promiseB = new Promise((resolve, reject) => {
setTimeout(() => resolve("B"), 3000)
})
// Aはrejectされるので、Bが表示される
try {
const promise = await Promise.any([promiseA, promiseB])
console.log(promise)
} catch (e) {
console.error(e)
}
これと似たメソッドにPromise.raceがありますが、違いは、一番最初にresolveもしくは『reject』された時点で終了します。anyと違ってrejectも含みますので、resolveされるよりも前にrejectされる処理がある場合エラーとなります。
const promiseA = new Promise((resolve, reject) => {
setTimeout(() => reject("A"), 1000)
})
const promiseB = new Promise((resolve, reject) => {
setTimeout(() => resolve("B"), 3000)
})
// AはBより先にrejectされるので、エラーAが発生する
try {
const promise = await Promise.race([promiseA, promiseB])
console.log(promise)
} catch (e) {
console.error(e)
}
詳しくは次の記事をご覧ください。
WeakRefで弱参照が作れます。弱参照とはオブジェクトがガベージコレクションの対象になるかもしれない参照です。
const obj = { name: "hoge" }
const weakObj = new WeakRef(obj)
console.log(weakObj.deref()) //derefでオブジェクトにアクセス
次の記事に詳しい使い方がまとめられているので、リンクさせていただきます。
従来のNumber型では大きすぎて表すことのできない数値を表現したり操作することが可能となります。
Number型は2の53乗を超えると精度が落ちてしまいますが、BigIntを用いれば正確に整数を扱えます。
使い方は、整数値の最後にnをつけるだけです。
const num = 200n // bigint型
const num2 = BigInt(300) // これでももOK
Numberとは違い、Mathオブジェクトのメソッドで利用できなかったり、Number型の値と混ぜて演算することができません。
その他演算の規則は公式のドキュメントをご覧ください。
オブジェクトのプロパティにnullやundefinedの値があってもチェーンして書ける機能です。
存在しないプロパティにアクセスするとエラーが発生しますが、オプショナルチェーンを利用すると、存在しないプロパティにアクセスしてもundefinedとなり安全にコードの記述が可能です。
存在しないプロパティがいくら続こうがエラーを出さずにチェーンして書き続けることが可能です。
const obj = {
a: "a",
b : {
c: "c"
}
}
// console.log(obj.d.e) // TypeError
console.log(obj.d?.e) // undefined
console.log(obj.d?.e?.f) // undefined
オブジェクト以外にもnullかもしれない関数や配列などにも使用できます。
console.log(hoge?.()) // nullかもしれない関数の呼び出し
console.log(ary?.[1]) // nullかもしれない配列へのアクセス
null合体演算子は2項演算子で、 A ?? B のように使います。その意味は、Aがnullかundefinedの場合にBを評価し、そうでないならAとを評価するといったものです。
const A = null
const B = A ?? "fuga"
console.log(B) // fuga
const C = "hoge"
const D = C ?? "fuga"
console.log(D) // hoge
従来から論理演算子の A || B もありましたが、これはAがfalsy(偽とみなせる値)のときにBが評価されるといったものですので、null合体演算子のほうが対象が狭くなります。
await句やthen句で非同期にモジュールをimportすることが可能となります。
// A.mjs
// export const hoge = "hoge"
// B.mjs
import("./A.mjs").then(console.log)
// [Module: null prototype] { hoge: 'hoge' }
importしたものをそのままexportする処理が一文で書けるようになりました。
// Aからimportしたものをそのままexport
export * as A from "./A.js"
import.metaで現在のモジュールのURLなどのメタ情報にアクセスができます。
console.log(import.meta) // {url: https:~ }
Promise.allSettled()を使うと、同時に複数の非同期処理を行えます。
これと似たメソッドにPromise.all()がありますが、あちらは非同期処理のうちどれか一つでもrejectされるとエラーとなります。その一方で今回採用されたallSettledは、rejectされてもエラーを吐かずに非同期処理が完了します。
const promiseA = new Promise((resolve, reject) => {
setTimeout(() => reject("A"), 1000)
})
const promiseB = new Promise((resolve, reject) => {
setTimeout(() => resolve("B"), 3000)
})
const [a, b] = await Promise.allSettled([promiseA, promiseB])
console.log("resolved!", a, b)
// resolved! { status: 'rejected', reason: 'A' } { status: 'fulfilled', value: 'B' }
このように値とともにreject / fullfiledのステータスも吐きます。
Promiseの並列処理に関しては、詳しくは次の記事をご覧ください。
Webブラウザのグローバルオブジェクトはwindow、Node.jsはglobalと名前が異なります。
ES2020で導入されたglobalThisを用いれば、コードがどちらの環境で実行されているか関係なく、グローバルオブジェクトにアクセスできます。
// ブラウザ
console.log(globalThis === window) // true
// Node
console.log(globalThis === global) // true
文字列.matchAll(正規表現)で、マッチした文字列の情報をイテレータで返します。
const str = "1jg4qgog4hj9q"
console.log([...str.matchAll(/[0-9]+/g)])
/**
* [
[ '1', index: 0, input: '1jg4qgog4hj9q', groups: undefined ],
[ '4', index: 3, input: '1jg4qgog4hj9q', groups: undefined ],
[ '4', index: 8, input: '1jg4qgog4hj9q', groups: undefined ],
[ '9', index: 11, input: '1jg4qgog4hj9q', groups: undefined ]
]
*/
従来はfor-in文で繰り返されるオブジェクト等の順番は必ずしも先頭からイテレートされるのではなく、順不同でした。ES2020からはその順番が保証されます。
trimStartで左側、trimEndで右側だけ文字列の空白をtrimすることが可能になりました。
const str = " Hello, World! "
console.log(str.trimStart()) // "Hello, World! "
console.log(str.trimEnd()) // " Hello, World!"
Object.fronEntriesを使って、[キー, バリュー]のペアの配列から、{ キー: バリュー }のオブジェクトを作ります。
ペアの配列は2次元配列としていくつでも指定可能です。
const ary = [
["name", "Taro"],
["age", 24],
["type", "fairy"]
]
console.log(Object.fromEntries(ary))
// { name: 'Taro', age: 24, type: 'fairy' }
flat(N)でN階層目までネストされた配列をフラットにできます。Nを省略するとN=1と同じになります。
const ary = [
[1, 2], // 1階層ネスト
[
[3, 4], // 2階層
[5, 6],
[
[7, 8] // 3階層
]
]
]
console.log(ary.flat()) // [ 1, 2, [ 3, 4 ], [ 5, 6 ], [ [ 7, 8 ] ] ]
console.log(ary.flat(2)) // [ 1, 2, 3, 4, 5, 6, [ 7, 8 ] ]
console.log(ary.flat(3)) // [ 1, 2, 3, 4, 5, 6, 7, 8 ]
flatMapを使えば、mapした配列に対して1階層flatにできます。
const ary = ["Hello World!", "Soft and Wet"]
console.log(ary.map(a => a.split(" ")))
// [ [ 'Hello', 'World!' ], [ 'Soft', 'and', 'Wet' ] ]
console.log(ary.flatMap(a => a.split(" ")))
// [ 'Hello', 'World!', 'Soft', 'and', 'Wet' ]
以前はJavaScriptの文字列に行区切り\u2028と段落区切り\u2029を含めることが出来ずエラーとなっていました。
その一方でJSONではどちらも用いることが可能でした。
ES2019からはJacaScriptでもこれら区切り文字が使用可能となり、ECMAScriptはJSONのスーパーセットになりました。
catchには引数の指定が必須でしたが、使用しないならば省略できるようになりました。
try {
throw new Error
} catch { // catch(e)としなくていい
console.error("ERROR")
}
タイトル通り、関数.toString()としたときにコメントも文字列になります。
function hoge() {
/* hogehoge */
return null
}
console.log(hoge.toString())
// 出力
// function hoge() {
// /* hogehoge */
// return null
// }
シンボル.descriptionでSymbol定義時に引数に指定した値を返します。
const s = Symbol("hoge")
console.log(s.description) // hoge
以上ES2023からES2019まで遡って新機能をまとめて振り返りました。
TypeScriptではとっくに採用していた機能も多いですね。
最新の記事
カテゴリー一覧
アーカイブ
目次