Skip to content

Commit

Permalink
feat(array): Object.groupBy静的メソッドを追加 (#1749)
Browse files Browse the repository at this point in the history
* feat(array): Object.groupBy静的メソッドを追加

* fix

* fix

* fix

* fix

* CI: ES2024のサポート

* chore: 必要なNode.jsのバージョンをv22.4.1に変更

* chore: npmもアップデート

* fix test

- Object.groupByはObject.create(null)なので一致しない

* fix empty line

* fix

* fix

* fix

* CI: use 22.5.1
  • Loading branch information
azu committed Jul 20, 2024
1 parent 2f98e66 commit 4cbdccd
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 24 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- run: npm install
- run: npm run build
- name: Deploy
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [18]
node-version: [20]
os: [macOS-latest, windows-latest, ubuntu-latest]
name: "Build on Node.js: ${{ matrix.node-version }} OS: ${{ matrix.os }}"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Node.js ${{ matrix.node-version }}"
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand All @@ -29,12 +29,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
node-version: [20, 22.5.1]
name: "Test on Node.js ${{ matrix.node-version }}"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Node.js ${{ matrix.node-version }}"
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand All @@ -43,10 +43,10 @@ jobs:
runs-on: ubuntu-latest
name: E2E
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- run: npm ci
- run: npm run e2e
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.11.1
v22.4.1
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ IssueやPull Requestについては、次のページを参照してください

npm install

Node.js v20.11.1以上とnpm 10.2.4以上が必要です
Node.js v22.4.1以上とnpm 10.8.2以上が必要です

```
$ node -v
v20.11.1
v22.4.1
$ npm -v
10.2.4
10.8.2
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"license": "MIT",
"version": "4.0.0",
"description": "📖 JavaScript Primer - 迷わないための入門書",
"packageManager": "npm@10.2.4",
"packageManager": "npm@10.8.2+sha512.c7f0088c520a46596b85c6f8f1da943400199748a0f7ea8cb8df75469668dc26f6fb3ba26df87e2884a5ebe91557292d0f3db7d0929cdb4f14910c3032ac81fb",
"type": "module",
"directories": {
"test": "test"
Expand Down
55 changes: 53 additions & 2 deletions source/basic/array/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ console.log(totalValue); // => 6
そのため、できる限り変数を`const`で宣言したい場合には`reduce`メソッドは有用です。
一方で、`reduce`メソッドは可読性があまりよくないため、コードの意図が伝わりにくいというデメリットもあります。

`reduce`メソッドには利点と可読性のトレードオフがありますが、利用する場合は`reduce`メソッドを扱う処理を関数で囲むなど処理の意図がわかるように工夫をする必要があります
`reduce`メソッドには利点と可読性のトレードオフがありますが、利用する場合は`reduce`メソッドを扱う処理を関数にするといった処理の意図がわかるように工夫をする必要があります

{{book.console}}
```js
Expand All @@ -991,6 +991,55 @@ function sum(array) {
console.log(sum(array)); // => 6
```

### [ES2024] `Object.groupBy`静的メソッド {#object-group-by}

`Array.prototype.reduce`メソッドを使うことで、配列から数値やオブジェクトなど任意の値を作成できます。

先ほどは配列の合計の数値を計算する例でしたが、配列からオブジェクトを作成することもできます。
配列からオブジェクトを作成したいユースケースとして、配列の要素を条件によってグループ分けしたいケースがあります。
たとえば、数値からなる配列の要素を奇数と偶数の配列に分けたい場合などです。

`Array.prototype.reduce`メソッドを使って、数値からなる配列を奇数と偶数に分けるコードは次のようになります。

{{book.console}}
```js
const array = [1, 2, 3, 4, 5];
const grouped = array.reduce((accumulator, currentValue) => {
// 2で割った余りが0なら偶数(even)、そうでないなら奇数(odd)
const key = currentValue % 2 === 0 ? "even" : "odd";
if (!accumulator[key]) {
accumulator[key] = [];
}
// グループ分けしたキーの配列に要素を追加
accumulator[key].push(currentValue);
return accumulator;
}, {});
console.log(grouped.even); // => [2, 4]
console.log(grouped.odd); // => [1, 3, 5]
```

しかし、`reduce`メソッドは使い方がやや複雜であるため、可能なら避けたほうが読みやすいコードとなりやすいです。
ES2024では、`Object.groupBy`静的メソッドが追加され、配列からグループ分けしたオブジェクトを作成できるようになっています。

`Object.groupBy`静的メソッド[^1]は、第1引数に配列を、第2引数にグループ分けの条件を返すコールバック関数を渡します。
第2引数のコールバック関数が返した値をキーとして、配列の要素をグループ分けしたオブジェクトが作成されます。

先ほどのコードを`Object.groupBy`静的メソッドを使って書き換えると、次のようになります。

{{book.console}}
<!-- doctest:meta:{ "ECMAScript": "2024" } -->
```js
const array = [1, 2, 3, 4, 5];
const grouped = Object.groupBy(array, (currentValue) => {
// currentValueが偶数なら"even"、そうでないなら"odd"の配列に追加される
return currentValue % 2 === 0 ? "even" : "odd";
});
console.log(grouped.even); // => [2, 4]
console.log(grouped.odd); // => [1, 3, 5]
```

`Object.groupBy`静的メソッドを使うことで、配列からグループ分けしたオブジェクトを簡潔に作成できます。

## [コラム] Array-likeオブジェクト {#array-like}

配列のように扱えるが配列ではないオブジェクトのことを、**Array-likeオブジェクト**と呼びます。
Expand Down Expand Up @@ -1030,7 +1079,7 @@ function myFunc() {
myFunc("a", "b", "c");
```

Array-likeオブジェクトは配列のようで配列ではないというもどかしさを持つオブジェクトです。`Array.from`メソッド<sup>[ES2015]</sup>を使うことでArray-likeオブジェクトを配列に変換して扱うことができます。一度配列に変換してしまえばArrayメソッドも利用できます。
Array-likeオブジェクトは配列のようで配列ではないというもどかしさを持つオブジェクトです。`Array.from`静的メソッド<sup>[ES2015]</sup>を使うことでArray-likeオブジェクトを配列に変換して扱うことができます。一度配列に変換してしまえばArrayメソッドも利用できます。

{{book.console}}
```js
Expand Down Expand Up @@ -1123,3 +1172,5 @@ console.log(versionNames); // => ["ECMAScript 1", "ECMAScript 2", "ECMAScript 3"
[Lodash]: https://lodash.com/ "Lodash"
[Immutable.js]: https://immutable-js.com/ "Immutable.js"
[Arrayについてのドキュメント]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array

[^1]: `Array.prototype.groupBy`メソッドのようなArrayのメソッドではないのは、同じメソッド名を実装するウェブサイトが多く存在しており後方互換性がなかったためです。
13 changes: 8 additions & 5 deletions test/markdown-doc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ const sourceDir = path.join(__dirname, "..", "source");
/**
* 指定したECMAScriptバージョンをmetaにもつコードは実行環境によってはサポートされてないので無視する
* 最新版のNodeでは無視しない
* @type {string[]}
* @type {string[]} サポートしてないECMAScriptバージョン
*/
const IgnoredECMAScriptVersions = (() => {
if (semver.cmp(process.version, ">=", "20.0.0")) {
if (semver.cmp(process.version, ">=", "22.0.0")) {
return []; // すべて通る前提
}
if (semver.cmp(process.version, ">=", "20.0.0")) {
return ["2024"]; // Object.groupByがサポートされていない
}
if (semver.cmp(process.version, ">=", "18.0.0")) {
return ["2023"]; // Array.prototype.withがサポートされていない
}
Expand All @@ -35,7 +38,7 @@ const IgnoredECMAScriptVersions = (() => {
// Top-Level await をサポートしていない
return ["2021", "2022"];
}
return ["2017", "2018", "2019", "2020", "2021", "2022"];
return ["2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024"];
})();
/**
* Markdownファイルの CodeBlock に対してdoctestを行う
Expand All @@ -55,7 +58,7 @@ describe("doctest:md", function() {
]);
files.forEach(filePath => {
const normalizeFilePath = filePath.replace(sourceDir, "");
describe(`${normalizeFilePath}`, function () {
describe(`${normalizeFilePath}`, function() {
const content = fs.readFileSync(filePath, "utf-8");
const parsedCodes = parse({
filePath,
Expand All @@ -66,7 +69,7 @@ describe("doctest:md", function() {
parsedCodes.forEach((parsedCode, index) => {
const codeValue = parsedCode.code;
const testCaseName = codeValue.slice(0, 32).replace(/[\r\n]/g, "_");
it(dirName + ": " + testCaseName, function () {
it(dirName + ": " + testCaseName, function() {
return test({
...parsedCode,
code: toTestCode(parsedCode.code)
Expand Down

0 comments on commit 4cbdccd

Please sign in to comment.