State of JavaScript 2022 アンケートから学んだ17個のボキャブラリー

# State of JavaScript のアンケートとは？

こんな図を見たことありませんか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/05ad8771-11dc-7e4a-56e1-52baff61b921.png)

これは、**`JavaScript`、`CSS`や`GraphQL`をどの言語やフレームワークから始めたらよいか困っている人のために、`Devographics`という会社が世界中の開発者にアンケートを取り、現状を調査するために始めた**そうです。
このアンケートをもとに作成された結果が上記のような形で公表されるのです。

:::note
追記: 2022年の結果が発表されました :tada:
https://2022.stateofjs.com/ja-JP/features/language/
:::

<details><summary>私もアンケートに回答しましたが、散々な結果でした...</summary>

[State Of JS 2022 のアンケート](https://survey.devographics.com/survey/state-of-js/2022)に答えるとと、自分のJSスコアを教えてくれます。ですが、私のスコアは57%という散々なものでした...:sob:（ちなみに昨年は40%台だったような...

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">毎回、悲しいスコア…精進します<br><br>私の <a href="https://twitter.com/hashtag/StateOfJS?src=hash&amp;ref_src=twsrc%5Etfw">#StateOfJS</a> ナレッジスコアは57%でした。回答はここから：<a href="https://t.co/kVe9L1xa00">https://t.co/kVe9L1xa00</a></p>&mdash; mkin (@MakohanKinpo) <a href="https://twitter.com/MakohanKinpo/status/1595173327324471296?ref_src=twsrc%5Etfw">November 22, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

最後に表示されたナレッジスコアの画面には

> あなたは100%以内にはいっています。

という、一瞬、意味不明なコメントが書いてありました。
（つまり、最下層の人間ということですw）

</details>

## どんなアンケートなの？

下記のように、１つの機能について

> 1. 聞いたこともない/知らない
> 1. 知ってる/でも使ったこと無い
> 1. 使ったことある

から選ぶだけの簡単なアンケートです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/90ba2385-ad00-dab5-c66f-3a92acc14c38.png" />
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/6bacbd8a-cb39-29a3-652c-559c290e5a43.png" />

日常では触れないボキャブラリーもあると思いますので、一緒に学んでみませんか？ :relaxed: 

:::note warn
この記事はアンケートの受付期間の終了後に公開しております
:::

# 17個のボキャブラリー

Javascriptのボキャブラリーのアンケートで聞かれた内容を一緒に学んでいきましょう！

まずはこれ :point_down:

## 1. Proxies

（いきなり知らないボキャブラリーを聞かれ、出鼻をくじかれましたw）

`Proxy`は「代理人」という意味なのですが、いったい何を代わりに処理してくれるでしょうか？
サンプルコードを見てみましょう。

```js:Proxyのサンプルコード
const target = { name: 'Taro', age: 20};
const handler = {}
const proxy = new Proxy(target, handler);

console.log(proxy.name);  // Taro
console.log(proxy.age);   // 20
console.log(proxy.hoge);  // undefined
```

このように`proxy`が代理で`target`の`name`をプロパティを取得してくれました。
上の例では`hander`が空`{}`なので何も旨味は感じられませんが、`hander`に下記のような実装をすることで、`target`のプロパティの`get`をインターセプトすることができます。

```diff_javascript:ProxyでオブジェクトのGetをインターセプトする
  const target = { name: 'Taro', age: 20};
+ const handler = {
+   get(target, prop) {
+     if (prop === 'name') {
+       return target[prop];
+     } else if (prop === 'age') {
+       return 'ヒミツ';
+     } else {
+       return 'そんなプロパティはない';
+     }
+   }
+ }
  const proxy = new Proxy(target, handler);

  console.log(proxy.name);  // Taro
+ console.log(proxy.age);   // ヒミツ　
+ console.log(proxy.hoge);  // そんなプロパティはない
```

このような**インターセプトは`get`だけでなく、`set`や関数でも使えるので、ライブラリなどを作る際に活躍しそう**ですね。

## 2. Promise.allSettled()

これは聞いたことあります！（使ったこと無いですが）

`Promise.all()`と比較すると`Promise.allSettled()`の処理が理解しやすくなるので、まずは`Promise.all()`の非同期処理を見てみましょう。

```js:Promise.all()の非同期処理
const require1 = new Promise((resolve, reject) =>
  setTimeout(resolve, 100, '1_必須処理')
);
const option2 = new Promise((resolve, reject) =>
  setTimeout(resolve, 200, '2_任意処理')
);
const require3 = new Promise((resolve, reject) =>
  setTimeout(resolve, 300, '3_必須処理')
);

Promise.all([require1, option2, require3])
  .then((values) => console.log(`成功: ${values}`))
  .catch((error) => console.error(`エラー: ${error}`));
// 成功: 1_必須処理,2_任意処理,3_必須処理
```

３つの非同期処理が成功し、`then`の処理に入ってきましたね。
しかし、**`Promise.all()`は`reject`が返ってきたらそこで処理を中断します。** 具体例として、任意処理`option2`の非同期処理を`reject`するように書き換えます。

```diff_javascript:任意処理を失敗させるとcatchに入る
...
const option2 = new Promise((resolve, reject) =>
- setTimeout(resolve, 200, '2_任意処理')
+ setTimeout(reject, 200, '2_任意処理')
);
...

Promise.all([require1, option2, require3])
  .then((values) => console.log(`成功: ${values}`))
  .catch((error) => console.error(`エラー: ${error}`));
+ // エラー: 2_任意処理
```

一方、**`Promise.allSettled()`は、途中で処理が失敗しても全ての処理を実行してくれます。**

```diff_javascript:Promise.allSettled()に変えるとthenに入る
...

+ Promise.allSettled([require1, option2, require3])
  .then((values) => console.log(`成功: ${values}`))
  .catch((error) => console.error(`エラー: ${error}`));
+ // 成功: [object Object],[object Object],[object Object]
```

任意処理が失敗しても`Promise`は成功しました。上の出力結果では分かりませんが、成功した結果valuesの中は下記の通りです。

```js:Promise.allSettled()の返り値
[
  { status: 'fulfilled', value: '1_必須処理' },
  { status: 'rejected', reason: '2_任意処理' },
  { status: 'fulfilled', value: '3_必須処理' }
]
```

非同期処理でパフォーマンスを高めたいが、その中に失敗しても処理を続けたい処理が含まれている場合は、`Promise.allSettled()`が有効ですね。

## 3. Dynamic Import

> CODE EXAMPLE
> ```js
> await import('/modules/my-module.js')
> ```

上のコードは、アンケートに記載されていた例です。

使う目的としては下記のケースが多いのではないでしょうか？
- 初期ロード時のjsの量を小さくして、表示速度を改善したい
- 環境によってimportするモジュールを切り替えたい

```js:初回ロードが遅くなるimport
import fatModule from 'fat-module';  // 重いモジュール

// 何かの条件のとき、重いモジュールの処理を実行する
if (somethingEvent()) {
  fatModule.hoge();
}
```

これだと条件が発火しないのに重いモジュールをロードしてしまいます。
そこで、`Dynamic Import`の出番です。

```js:初回ロードが早くなるimport
let fatModule;

if (somethingEvent()) {
  fatModule = await import('fat-module');
  fatModule.hoge();
}
```

こうすることで、何かのイベントが発生するまでは重いモジュールのロードはされなくなりました。

ただ、`Dynamic Import`を使っても、とっても重いモジュールだと反応が悪くなっちゃうので、そもそも重いサードパーティのライブラリは使わないようにしていきたいですね。

## 4. Nullish Coalescing

> CODE EXAMPLE
> ```js
> const foo = null ?? 'default string';
> console.log(foo);
> // expected output: "default string"
>
> const baz = 0 ?? 42;
> console.log(baz);
> // expected output: 0
> ```

`Nullish Coalescing` [読み： ヌリッシュ コアレシング]

`Coalesce`は「合体する」という意味なので、「`Null`っぽいものを合体するよ」ということですね。

**`??`という演算子を使うと、左辺が`null`または`undefined`の場合に右辺を返し、それ以外の場合は左辺を返します。**

`??`と区別して使われるのが`||`演算子です。

**`||`演算子の場合は、左辺が偽値（`false`、`0`, `-0`, `0n`, `''`, `NaN`, `null`, `undefined`）の場合に右辺を返し、それ以外の場合は左辺を返します。**

`||` はデフォルト値を設定するときなどに使われるのを見たことがあるのではないでしょうか？


## 5. Private Fields

> CODE EXAMPLE
> ```js
> class ClassWithPrivateField {
>   #privateField
> }
> ```

`#`キーワードは、新しいプライベートフィールドの書き方ですね。JSでクラスを書く機会が少ないので、私はまだ使ったことないです。

今までの **`private`キーワードには、ハッシュ記法でアクセスできてしまう抜け道があった** のですが、`#`キーワードではアクセスできないようになります。

```js:# キーワードと private キーワードの比較
class Sample {
  #new: string;
  private _old: string;

  constructor(value: string) {
    this.#new = value;
    this._old = value;
  }
}

let sample = new Sample("hoge");

sample.#new = "fuga";     // エラー
sample["#new"] = "fuga";  // エラー

sample._old = "fuga";     // エラー
sample["_old"] = "fuga";  // エラーにならない!!
```

**より堅牢なコードになるので、今後は`#`を使っていきたいですね。**

## 6. Numeric Separators

数字の区切り文字に`_`が使えるんですね！

```js:区切り文字の例
const million = 1_000_000;
const billion = 1_000_000_000;

console.log(million);
// 1000000
console.log(billion);
// 1000000000
```

区切り文字がない場合と比べるとぐっと見やすくなりますね！

```js:区切り文字がない場合
const million = 1000000;
const billion = 10000000000;  ←実は間違えているがパッと見で気づかない
```

## 7. String.prototype.replaceAll()

`replace()`だと正規表現で`/g`フラグを使わないと全置換できないです。しかし、`replaceAll()`だと全部置換してくれます。

下の例では、数字の区切り文字を`_`から`,`に変更しています。

```js:replace()とreplaceAll（）の書き方の違い
const million = '1_000_000';

console.log(million.replace('_', ','));
// 1,000_000   ←replace() は最初に一致した文字しか置換しない

console.log(million.replace(/_/g, ','));
// 1,000,000   ← /g フラグを使うと全置換できる

console.log(million.replaceAll('_', ','));
// 1,000,000   ←replaceAll() は全置換できる
```

## 8. String.prototype.matchAll()

先述した`replaceAll()`と違って、`match()` に `/g`フラグを付けるのと同じではないようです。

具体例で説明します。

例えば、スポーツ名を羅列した文字列 `baseball soccer basketball` から `ball` が付くスポーツの接頭語を取得したい場合はどうなるでしょう？ この場合、`base`ball と`basket`ball を取得したいケースです。

```js:match()の場合
const sports = 'baseball soccer basketball';
const regex = /(\w+)ball/g;

console.log(sports.match(regex));
// [ 'baseball', 'basketball' ]  ← (\w+) のキャプチャグループが無視される
```

**`match()`の場合、グローバル`/g`フラグを使用するとキャプチャグループが無視される**ので、スポーツの接頭語を抽出するには、ひと手間かかりますね。

しかし、`matchAll()`を使うと、キャプチャグループも取得できるので、`Array.from（）`と組み合わせるとこのように取得できます。

```js:matchAll() の場合
const sports = 'baseball soccer basketball';
const regex = /(\w+)ball/g;

const prefixes = Array.from(sports.matchAll(regex), m => m[1]);
console.log(prefixes);
// [ 'base', 'basket' ]
```

`matchAll()`の返り値を見てみるとイテレーターになっており、キャプチャグループも含まれていることが分かります。

```js:matchAll() の返り値
console.log([...sports.matchAll(regex)]);
// [
//   ['baseball',　'base', index: 0, input: 'baseball soccer basketball', groups: undefined],
//   ['basketball',　'basket', index: 16, input: 'baseball soccer basketball', groups: undefined]
// ]
```

## 9. Logical Assignment

> CODE EXAMPLE
> ```js
> const a = { duration: 50, title: '' };
>
> a.duration ||= 10;
> console.log(a.duration);
> // expected output: 50
>
> a.title ||= 'title is empty.';
> console.log(a.title);
> // expected output: "title is empty."
> ```

`Logical Assignment`（`||=`）は、`+=`演算子と同じ考え方になります。

**`||=`を使うことで、左辺が偽値の場合に、右辺を代入してくれます。** なので、下記のように、常に代入が発生する書き方をしている場合は、**`||=`を使った書き方変更したほうが不要な代入が発生しなくて良い**ですね。

```js:以下の2つは代入の発生の有無で等価ではない
// 常に代入が発生する
hoge = hoge || 10;

// hoge が真値なら代入が発生しない
hoge ||= 10;
```

## 10. Promise.any()

`any`って「何でもいいよ」って意味を持ってるようです。

つまり、**複数の`promise`で最初に成功したものがあれば、「それでいいよ」ってことになります。**

下記の例では、最初に成功した`successPromiseQuick`の結果が`then`で受け取れていることが分かります。

```js:Promise.any()の動き
const errorPromise = new Promise((_, reject) =>
  setTimeout(reject, 100, '1_エラー処理')
);
const successPromiseQuick = new Promise((resolve) =>
  setTimeout(resolve, 200, '2_成功（早い）')
);
const successPromiseSlow = new Promise((resolve) =>
  setTimeout(resolve, 300, '3_成功（遅い）')
);

Promise.any([errorPromise, successPromiseQuick, successPromiseSlow])
  .then((values) => console.log(`成功: ${values}`))
  .catch((error) => console.error(`エラー: ${error}`));
// 成功: 2_成功（早い）
```

また、全部の`promise`が失敗したときは、例外を補足します。

```js:Promise.any() の非同期処理が全部エラーの場合
Promise.any([errorPromise1, errorPromise2, errorPromise3])
  .then((values) => console.log(`成功: ${values}`))
  .catch((error) => console.error(`エラー: ${error}`));
// エラー: AggregateError: All promises were rejected
```


## 11. Array.prototype.at()

**`at()`の引数に指定した位置の配列要素を取得できます。** これの嬉しいポイントとしては、**マイナス値を指定すると末尾からの要素を取得できます。**
これによって、`[]`にインデックス指定した今までの書き方よりスッキリ書くことができます。

```js:at()で末尾の配列要素を取得する
const numbers = [5, 9, 4, 7, 3];

console.log(numbers.at(-1));
// 3

console.log(numbers.at(-2));
// 7

// インデックス指定だと numbers が2回でてきて冗長な書き方に...
console.log(numbers[numbers.length -1]);
// 3
```

## 12. Top Level await()

この技術により、トップレベルで非同期処理を実行するのに、今までやっていた`async`関数でラップする手間が省けるようになったのと、非同期処理のモジュールをエクスポートすることができるようになったようです。

詳細は、下記の記事がとても参考になりました :relaxed: 

https://qiita.com/uhyo/items/0e2e9eaa30ec2ff05260


## 13. Temporal [2022]

`[2022]`ってのは`ES2022`からの新機能のようです。

`Temporal`は次世代の`Date`オブジェクトですか？ 聞いたことあるけど、使ったことないですね。

`Temporal`が登場した背景を調べてみます。
[Temporalドキュメントの翻訳](https://tc39.es/proposal-temporal/docs/ja/index.html)を確認すると、以下のような問題を解決してくれるそうです。

> Temporal は既存の Date の問題を次の方法で解決します。
> 
> - 日付と時刻を操作する簡単で使いやすい API を提供します
> - DST を考慮した演算と、すべてのタイムゾーンをサポートします
> - オブジェクトは特定の日時や時刻を明確に表します
> - 厳格に定義された文字列をパースします
> - グレゴリオ暦以外のカレンダーをサポートします

グローバルなサービスでは活躍するかもしれませんが、2022年12月時点ではフォーマットは提供されていないので、個人的には今までのように `Day.js` のようなライブラリを使って日付を扱ったほうが嬉しいです。

## 14. Array.findLast() [2022]

> CODE EXAMPLE
> ```js
> const array1 = [5, 12, 50, 130, 44];
> const found = array1.findLast((element) => element > 45);
> console.log(found); 
> // expected output: 130
> ```

これは関数の名前から分かるとおり、 **配列の後ろから判定して最初に見つかった要素を返す関数です。**

今までは下記のように取得していたのでしょうか？

```js:before: reverse()で前後を入れ替えてからfind()
const array1 = [5, 12, 50, 130, 44];
const found = array1.reverse().find((element) => element > 45);
console.log(found); 
> // expected output: 130
```

```js:before: for で後ろから検索
for (var i = array1.length; i >= 0; i--) {
    if (array1[i] > 45) {
        found = array1[i];
        break;
    }
}
console.log(found); 
> // expected output: 130
```

## 15. Error.prototype.cause [2022]

> CODE EXAMPLE
> ```js
> try {
>   connectToDatabase();
> } catch (err) {
>   throw new Error('Connecting to database > failed.', { cause: err });
> }
> ```

上のサンプルコードをみても、「ほ〜ん、エラー元を渡せるのね」くらいの理解ですが、調べてみると、この`cause`のおかげで、呼び出し元のエラーハンドリングをスッキリ書くことができるようです。

[JAVASCRIPT.INFO のカスタムエラー, Error の拡張のサンプル](https://ja.javascript.info/custom-errors)を参考にすると、 **`cause`を活用すると複数のエラーハンドリングを集約して書くことができるようになります。**

下記の例のように、ユーザー情報を読み込む際には、カスタムエラーのバリデーションもあれば、シンタックスエラーも発生しえます。しかし、これらを素直に書いていくと、呼び出し元のエラーハンドリングが煩雑になってしまいます。でも、やりたいことは読み込みエラーに集約されますよね？

これを解決するために、`cause`にエラー元を設定することで、カスタムエラーかシンタックスエラーか判別することができるようになります。

```diff_javascript:Error.prototype.causeを使ったエラーハンドリング
try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
- if (err instanceof ValidationError) {
-   alert("Invalid data: " + err.message);
-   alert(err.name);
-   alert(err.property);
- } else if (err instanceof SyntaxError) {
-   alert("JSON Syntax Error: " + err.message);
+ if (e instanceof ReadError) {           // 読み込みエラーをカスタムエラーに集約しても
+   alert(e);
+   alert("Original error: " + e.cause);  // エラー元は cause から分かる！
  } else {
    throw err;
  }
}
```

## 16. Object.hasOwn() [2022]

> CODE EXAMPLE
> ```js
> const object1 = {
>   prop: 'exists'
> };
> console.log(Object.hasOwn(object1, 'prop'));
> // expected output: true
> ```

サンプルコードを読んだら想像がつきますね。
オブジェクトが指定したプロパティを持っていれば`true`、そうでなければ`false`を返します。

## 17. Regexp Match Indices [2022]

> CODE EXAMPLE
> ```js
> const str1 = "foo bar foo";
> const regex1 = /foo/dg;
> console.log(regex1.exec(str1).indices[0]); 
> // Output: Array [0, 3]
> ```

`indices`は`index`の複数形なんですって。知らなかった。

これは、正規表現で`d`フラグをつけることで、マッチした文字列のインデックス情報を取得してくれるそうです。

ちなみに、`hasIndices`というプロパティも用意されており、`d`フラグをつけると`true`になりました。

```js:正規表現で d フラグをつけると hasIndices プロパティが true になる
const regex1 = /foo/dg;
console.log(regex1.hasIndices);
// true

const regex2 = /foo/g;
console.log(regex2.hasIndices); 
// false
```

# おわり

いかがだったでしょうか？

アンケートで分からなかったことを復習したことで、`JavaScript`のボキャブラリーを増やすことができました。
アンケートでは比較的新しいボキャブラリーもあるため、[Can I use](https://caniuse.com/)のサイトで調べながら、自分のサービスが推奨するブラウザが対応していれば使っていきたいなと思います。

また、本記事はアンケート結果に影響を与えないように、受付を締め切った後に公開しておりますので、ご安心ください:bow:

![アンケート締め切り記載.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/2868bc53-34aa-9033-b044-9ad23f093c94.png)

# 参考

https://survey.devographics.com/survey/state-of-js/2022

https://ja.javascript.info/proxy

https://tc39.es/proposal-temporal/docs/ja/index.html

https://ja.javascript.info/custom-errors