State of JavaScript のアンケートとは?
こんな図を見たことありませんか?
これは、JavaScript
、CSS
やGraphQL
をどの言語やフレームワークから始めたらよいか困っている人のために、Devographics
という会社が世界中の開発者にアンケートを取り、現状を調査するために始めたそうです。
このアンケートをもとに作成された結果が上記のような形で公表されるのです。
追記: 2022年の結果が発表されました
https://2022.stateofjs.com/ja-JP/features/language/
私もアンケートに回答しましたが、散々な結果でした...
State Of JS 2022 のアンケートに答えるとと、自分のJSスコアを教えてくれます。ですが、私のスコアは57%という散々なものでした...(ちなみに昨年は40%台だったような...
毎回、悲しいスコア…精進します
— mkin (@MakohanKinpo) November 22, 2022
私の #StateOfJS ナレッジスコアは57%でした。回答はここから:https://t.co/kVe9L1xa00
最後に表示されたナレッジスコアの画面には
あなたは100%以内にはいっています。
という、一瞬、意味不明なコメントが書いてありました。
(つまり、最下層の人間ということですw)
どんなアンケートなの?
下記のように、1つの機能について
- 聞いたこともない/知らない
- 知ってる/でも使ったこと無い
- 使ったことある
から選ぶだけの簡単なアンケートです。
日常では触れないボキャブラリーもあると思いますので、一緒に学んでみませんか?
この記事はアンケートの受付期間の終了後に公開しております
17個のボキャブラリー
Javascriptのボキャブラリーのアンケートで聞かれた内容を一緒に学んでいきましょう!
まずはこれ
1. Proxies
(いきなり知らないボキャブラリーを聞かれ、出鼻をくじかれましたw)
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
をインターセプトすることができます。
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()
の非同期処理を見てみましょう。
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
するように書き換えます。
...
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([require1, option2, require3])
.then((values) => console.log(`成功: ${values}`))
.catch((error) => console.error(`エラー: ${error}`));
+ // 成功: [object Object],[object Object],[object Object]
任意処理が失敗してもPromise
は成功しました。上の出力結果では分かりませんが、成功した結果valuesの中は下記の通りです。
[
{ 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 fatModule from 'fat-module'; // 重いモジュール
// 何かの条件のとき、重いモジュールの処理を実行する
if (somethingEvent()) {
fatModule.hoge();
}
これだと条件が発火しないのに重いモジュールをロードしてしまいます。
そこで、Dynamic 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
の場合に右辺を返し、それ以外の場合は左辺を返します。
??
と区別して使われるのが||
演算子です。
||
演算子の場合は、左辺が偽値(false
、0
, -0
, 0n
, ''
, NaN
, null
, undefined
)の場合に右辺を返し、それ以外の場合は左辺を返します。
||
はデフォルト値を設定するときなどに使われるのを見たことがあるのではないでしょうか?
5. Private Fields
CODE EXAMPLE
class ClassWithPrivateField { #privateField }
#
キーワードは、新しいプライベートフィールドの書き方ですね。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
数字の区切り文字に_
が使えるんですね!
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()
だと全部置換してくれます。
下の例では、数字の区切り文字を_
から,
に変更しています。
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 を取得したいケースです。
const sports = 'baseball soccer basketball';
const regex = /(\w+)ball/g;
console.log(sports.match(regex));
// [ 'baseball', 'basketball' ] ← (\w+) のキャプチャグループが無視される
match()
の場合、グローバル/g
フラグを使用するとキャプチャグループが無視されるので、スポーツの接頭語を抽出するには、ひと手間かかりますね。
しかし、matchAll()
を使うと、キャプチャグループも取得できるので、Array.from()
と組み合わせるとこのように取得できます。
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()
の返り値を見てみるとイテレーターになっており、キャプチャグループも含まれていることが分かります。
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
(||=
)は、+=
演算子と同じ考え方になります。
||=
を使うことで、左辺が偽値の場合に、右辺を代入してくれます。 なので、下記のように、常に代入が発生する書き方をしている場合は、||=
を使った書き方変更したほうが不要な代入が発生しなくて良いですね。
// 常に代入が発生する
hoge = hoge || 10;
// hoge が真値なら代入が発生しない
hoge ||= 10;
10. Promise.any()
any
って「何でもいいよ」って意味を持ってるようです。
つまり、複数のpromise
で最初に成功したものがあれば、「それでいいよ」ってことになります。
下記の例では、最初に成功したsuccessPromiseQuick
の結果がthen
で受け取れていることが分かります。
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([errorPromise1, errorPromise2, errorPromise3])
.then((values) => console.log(`成功: ${values}`))
.catch((error) => console.error(`エラー: ${error}`));
// エラー: AggregateError: All promises were rejected
11. Array.prototype.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
関数でラップする手間が省けるようになったのと、非同期処理のモジュールをエクスポートすることができるようになったようです。
詳細は、下記の記事がとても参考になりました
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
これは関数の名前から分かるとおり、 配列の後ろから判定して最初に見つかった要素を返す関数です。
今までは下記のように取得していたのでしょうか?
const array1 = [5, 12, 50, 130, 44];
const found = array1.reverse().find((element) => element > 45);
console.log(found);
> // expected output: 130
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
にエラー元を設定することで、カスタムエラーかシンタックスエラーか判別することができるようになります。
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]
indices
はindex
の複数形なんですって。知らなかった。
これは、正規表現でd
フラグをつけることで、マッチした文字列のインデックス情報を取得してくれるそうです。
ちなみに、hasIndices
というプロパティも用意されており、d
フラグをつけるとtrue
になりました。
const regex1 = /foo/dg;
console.log(regex1.hasIndices);
// true
const regex2 = /foo/g;
console.log(regex2.hasIndices);
// false
おわり
いかがだったでしょうか?
アンケートで分からなかったことを復習したことで、JavaScript
のボキャブラリーを増やすことができました。
アンケートでは比較的新しいボキャブラリーもあるため、Can I useのサイトで調べながら、自分のサービスが推奨するブラウザが対応していれば使っていきたいなと思います。
また、本記事はアンケート結果に影響を与えないように、受付を締め切った後に公開しておりますので、ご安心ください
参考