4
0

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.

Ateam LifeDesignAdvent Calendar 2022

Day 16

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

Last updated at Posted at 2022-12-16

State of JavaScript のアンケートとは?

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

image.png

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

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

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

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

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

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

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

どんなアンケートなの?

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

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

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

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

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

17個のボキャブラリー

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

まずはこれ :point_down:

1. Proxies

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

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

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

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()の非同期処理を見てみましょう。

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_必須処理

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

任意処理を失敗させると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()は、途中で処理が失敗しても全ての処理を実行してくれます。

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の中は下記の通りです。

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

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

3. Dynamic Import

CODE EXAMPLE

await import('/modules/my-module.js')

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

使う目的としては下記のケースが多いのではないでしょうか?

  • 初期ロード時のjsの量を小さくして、表示速度を改善したい
  • 環境によってimportするモジュールを切り替えたい
初回ロードが遅くなるimport
import fatModule from 'fat-module';  // 重いモジュール

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

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

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

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

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

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

4. Nullish Coalescing

CODE EXAMPLE

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の場合に右辺を返し、それ以外の場合は左辺を返します。

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

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

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

5. Private Fields

CODE EXAMPLE

class ClassWithPrivateField {
  #privateField
}

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

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

# キーワードと 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

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

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

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

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

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

7. String.prototype.replaceAll()

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

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

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 が付くスポーツの接頭語を取得したい場合はどうなるでしょう? この場合、baseball とbasketball を取得したいケースです。

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

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

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

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

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()の返り値を見てみるとイテレーターになっており、キャプチャグループも含まれていることが分かります。

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

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||=)は、+=演算子と同じ考え方になります。

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

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

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

10. Promise.any()

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

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

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

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が失敗したときは、例外を補足します。

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

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:

13. Temporal [2022]

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

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

Temporalが登場した背景を調べてみます。
Temporalドキュメントの翻訳を確認すると、以下のような問題を解決してくれるそうです。

Temporal は既存の Date の問題を次の方法で解決します。

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

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

14. Array.findLast() [2022]

CODE EXAMPLE

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

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

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

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

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

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

JAVASCRIPT.INFO のカスタムエラー, Error の拡張のサンプルを参考にすると、 causeを活用すると複数のエラーハンドリングを集約して書くことができるようになります。

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

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

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

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

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

17. Regexp Match Indices [2022]

CODE EXAMPLE

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

indicesindexの複数形なんですって。知らなかった。

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

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

正規表現で 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のサイトで調べながら、自分のサービスが推奨するブラウザが対応していれば使っていきたいなと思います。

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

アンケート締め切り記載.png

参考

4
0
0

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
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?