diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f96341fe0..fc345d985 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 504a72a34..e93040949 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 @@ -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 @@ -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 diff --git a/.node-version b/.node-version index ee09fac75..adb070518 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20.11.1 +v22.4.1 diff --git a/README.md b/README.md index dd2f6117c..70bb2ba16 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index 7cb32a93b..3e7e00728 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/source/basic/array/README.md b/source/basic/array/README.md index ab9051d25..f60c1b65b 100644 --- a/source/basic/array/README.md +++ b/source/basic/array/README.md @@ -978,7 +978,7 @@ console.log(totalValue); // => 6 そのため、できる限り変数を`const`で宣言したい場合には`reduce`メソッドは有用です。 一方で、`reduce`メソッドは可読性があまりよくないため、コードの意図が伝わりにくいというデメリットもあります。 -`reduce`メソッドには利点と可読性のトレードオフがありますが、利用する場合は`reduce`メソッドを扱う処理を関数で囲むなど処理の意図がわかるように工夫をする必要があります。 +`reduce`メソッドには利点と可読性のトレードオフがありますが、利用する場合は`reduce`メソッドを扱う処理を関数にするといった処理の意図がわかるように工夫をする必要があります。 {{book.console}} ```js @@ -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}} + +```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オブジェクト**と呼びます。 @@ -1030,7 +1079,7 @@ function myFunc() { myFunc("a", "b", "c"); ``` -Array-likeオブジェクトは配列のようで配列ではないというもどかしさを持つオブジェクトです。`Array.from`メソッド[ES2015]を使うことでArray-likeオブジェクトを配列に変換して扱うことができます。一度配列に変換してしまえばArrayメソッドも利用できます。 +Array-likeオブジェクトは配列のようで配列ではないというもどかしさを持つオブジェクトです。`Array.from`静的メソッド[ES2015]を使うことでArray-likeオブジェクトを配列に変換して扱うことができます。一度配列に変換してしまえばArrayメソッドも利用できます。 {{book.console}} ```js @@ -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のメソッドではないのは、同じメソッド名を実装するウェブサイトが多く存在しており後方互換性がなかったためです。 diff --git a/test/markdown-doc-test.js b/test/markdown-doc-test.js index 10843d859..5a89b8341 100644 --- a/test/markdown-doc-test.js +++ b/test/markdown-doc-test.js @@ -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がサポートされていない } @@ -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を行う @@ -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, @@ -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)