194
104

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【JavaScript】ES2022の新機能

Last updated at Posted at 2021-12-13

ES2023 / ES2022 / ES2021

もうじき2022年ということで、ES2022にFinished Proposalsとして取り入れられた機能がそれぞれどんなものなのか見てみることにします。

ちなみにFinished Proposalの手前の段階であるStage4が「複数の実装が既に存在する」というのが定義なので、2022という名前にもかかわらず既に一部のブラウザで使用可能、というか一部以外のブラウザで使用可能です。

ES2022

Class Fields

Class Fieldsは似たような3RFCの集合体です。

Private instance methods and accessors

privateメソッドです。
むしろ今まで存在してなかったことにびっくりだよ。

class Foo{
  #privatemethod() {
    return "privatemethod";
  }
  publicmethod() {
    return "publicmethod";
  }
}

c = new Foo();
c.publicmethod(); // "publicmethod"
c.privatemethod(); // c.privatemethod is not a function
c.#privatemethod(); // Private field '#privatemethod' must be declared in an enclosing class

他言語のprivateメソッドと同じで、クラス内からは呼び出し可能ですが、クラス外からFoo.#privatemethod()のようにアクセスすることはできません。
他言語より強力な点として、リフレクションのような手段をもってしてもアクセスすることのできない完全なprivateです。

あとアクセサにも付けられるようになりますが、こちらは正直用途がよくわかりません。

class Foo{
  get #x(){return this.value;}
  set #x(value){this.value = value;}
}

Class Public Instance Fields & Private Instance Fields

public・privateフィールドです。
むしろ今まで存在してなかったことにびっくりだよ。

class Foo{
  x = 1;
  #y = 1;
}

publicについては、JSではいつでもどこでもfoo.x = 1とかpublicフィールドを生やせるので究極的には要らないと言えば要らないのですが、まあ明示できた方がいいですよね。

一方でprivateフィールドは、いくら明示したとしても外からアクセスすることはできません。

ところでこの話が出てきたのってだいぶ前だった気がするのですが、Finishedになったのって今年になってようやくだったんですね。

Static class fields and private static methods

静的フィールド・メソッドです。
むしろ今まで存在してなかったことにびっくりだよ。

class Foo{
  static x = 1;
  static #y = 2;
  
  static bar(){}
  static #baz(){}
}

上で出てきたばかりのprivateフィールドやprivateメソッドにも対応します。

このあたりの機能が出揃ったことによって、素のJavaScriptでもようやくまともなクラス構文が書けるようになりそうですね。

RegExp Match Indices

正規表現マッチングに、マッチした位置の情報を追加します。

const result = /ef(gh)(i)j/d.exec('abcdefghijklmn');
console.table(result.indices);

01.png

正規表現にフラグdを追加すると、返り値にindicesが増えます。
追加が必要なのはパフォーマンス的な理由であり、フラグがない場合はこれまでと同じ挙動になります。

indicesは配列となっていて、0には正規表現全体が文字列にマッチした部分が入ってきます。
例であれば4-10となっていますが、この返り値はsliceでちょうど切り取れる値です。

'abcdefghijklmn'.slice(4, 10); // 'efghij'

1以降はキャプチャグループがあればそのマッチした範囲が入ります。
例であれば1に(gh)、2に(i)のキャプチャグループの結果が入っています。

今回の例では実質的に正規表現を使ってないのであまり意味がないですが、キャプチャ文字列長が不定になるような正規表現を使うときなどは特に便利になることでしょう。

Top-level await

async関数の外側でawaitを使えるようになります。

await 1;

これができて何が便利かというと、さくっとexportしたりできます。

export const hoge = await f();

もっと詳しい記事がこちらにあったので、詳細はここなどを見るとよいと思います。
何言ってるのか9割方わかりませんでした。

だめ
function f(){
  await 1; // Uncaught SyntaxError: await is only valid in async functions
}

なお、asyncでない関数の中では相変わらず使用できません。
なんで。

Ergonomic brand checks for Private Fields

人間工学に基づいたprivateフィールドのチェック
なんのこっちゃ。

inをprivateフィールドにも使用可能にします。

class Foo{
  #privateField = 1;
  
  static isFoo(obj){
    return #privateField in obj;
  }
}

class Bar{
  #privateField = 1;
}

Foo.isFoo(new Foo()); // true
Foo.isFoo(new Bar()); // false

当然privateフィールドは外から見えないので、自身のクラス内でしか使用できません。

つまり、これとだいたい同じです。

class Foo{
  #privateField = 1;
  
  static isFoo(obj){
    return obj instanceof Foo;
  }
}

細かいところで動作が異なるらしいのですが、そこまでしてこの機能を必要とする理由はよくわかりませんでした。

.at

全てのインデックス可能なクラスArray・String・TypedArrayに、指定位置の値を取得するメソッド.at()を追加します。

'abcdef'.at(0); // a
'abcdef'.at(1); // b
'abcdef'.at(-1); // f
'abcdef'.at(-2); // e

[1, 2, 3].at(0); // 1
[1, 2, 3].at(1); // 2
[1, 2, 3].at(-1); //3

指定位置の値を返します。
他言語でもよくあるやつです。
残念ながら引数はひとつだけで、'abcdef'.at(1, 3) // 'bcd'みたいに範囲を取得することはできません。

一見[]と同じやん、と思いますが、実は[]は負の値を使うことができません。

'abcdef'[0]; // a
'abcdef'[1]; // b
'abcdef'[-1]; // undefined

というより、[]は単にオブジェクトのプロパティにアクセスしているだけであって、たとえばfoo[-1]fooオブジェクトのプロパティ-1を参照しているだけです。
従って末尾から数えたい場合は、いちいち長さを調べてfoo[foo.length-1]としなければなりませんでした。
今後は普通に書けるようになります。

Accessible Object.prototype.hasOwnProperty

hasOwnPropertyを使いやすくする提案。

let object = { foo: false };
Object.prototype.hasOwnProperty.call(object, "foo");

クソ長い!

ということでショートハンドです。

let object = { foo: false };
Object.hasOwn(object, "foo"); // true

ただのショートハンドなので、できることは同じです。

ところで素人目線だと、最初の例みたいな変なことをしないで

let object = { foo: false };
object.hasOwnProperty('foo');

って書けばいいのではと思ってしまうのですが、このような素直な例はプロトタイプ汚染によって死ぬので駄目なのです。
そのせいでObject.prototype.hasOwnProperty.callなんて奇妙な書き方を強制され、そしてそれを短くするためにObject.hasOwnなんてものが現れたと。
こんなことになったのも全てprototypeって奴の仕業なんだ。

Class Static Block

静的初期化ブロックです。

class Foo{
  static {
    console.log('static');
  }
  
  constructor(){
    console.log('constructor');
  }
}

staticブロックは、クラスが定義された時点で実行されます。
Javaの静的初期化ブロックは初回インスタンス化のタイミングで評価されますが、こちらはさらに早く、クラスを読み込んだ時点で評価されます。

引数を渡すことはできません。
そんなタイミングもありませんしね。

ユースケースとしては、静的フィールドの初期値として評価式を入れたいときなど様々なものが考えられるでしょう。

class Foo{
  static #privateValue;
  
  static {
    if(environment.production){
      this.#privateValue = 1;
    }else{
      this.#privateValue = 2;
    }
  }
}

インスタンス化されていないので、静的でないインスタンスフィールドに値を入れたりはできません。

Error Cause

エラーが連鎖したときに、親を.causeで辿れるようにします。

try{
	try{
		try{
			throw new Error('Error1');
		}catch(e){
			throw new Error('Error2', {cause: e});
		}
	}catch(e){
		throw new Error('Error3', {cause: e});
	}
}catch(e){
	console.table(e); // Error3
	console.table(e.cause); // Error2
	console.table(e.cause.cause); // Error1
}

第二引数にcauseを指定しなければなりません。
適当にhogeとか渡しても持って行ってくれません。

エラーをキャッチして何かの付加情報を付けて再度エラーを出すみたいなことは時々やりたくなりますが、これまでは一般的な方法がなく、ライブラリやプロジェクトによって異なる書き方がされていました。
今後はこれに統一されることで、親を辿るのが楽になるでしょう。

感想

地味で地道だ。

いやなんというか、これまでのECMAScriptのProposalってどうも見た目の派手さばっかり求めて足元がおざなりって感じを受けていたのですが、今回ES2022に入ったのは、地味ながらも必要性が高く地道に使い勝手の高いものが多いという印象です。
個人の印象なので実際どうかは知りませんけどね。

新機能の多くはオブジェクトに関するものであり、これで他言語と遜色ないレベルでのクラス構文が書けるようになったと言っていいのではないでしょうか。

あとは早く;必須にして。

194
104
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
194
104

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?