Skip to content

Commit 60c7676

Browse files
authored
feat(array): Change Array by copyの対応 (#1679)
* feat(array): Change Array by copyの対応 * fix * add meta * add meta * CI: use Node.js 20 * fix copiedArray * fix * fix * fix * fix * add links * fix * fix * update * fix * fix * fix * fix * fix
1 parent 79f2dae commit 60c7676

File tree

5 files changed

+89
-14
lines changed

5 files changed

+89
-14
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- name: Setup Node
1616
uses: actions/setup-node@v3
1717
with:
18-
node-version: 16
18+
node-version: 18
1919
- run: npm install
2020
- run: npm run build
2121
- name: Deploy

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ${{ matrix.os }}
1515
strategy:
1616
matrix:
17-
node-version: [16]
17+
node-version: [18]
1818
os: [macOS-latest, windows-latest, ubuntu-latest]
1919
name: "Build on Node.js: ${{ matrix.node-version }} OS: ${{ matrix.os }}"
2020
steps:
@@ -29,7 +29,7 @@ jobs:
2929
runs-on: ubuntu-latest
3030
strategy:
3131
matrix:
32-
node-version: [16, 18]
32+
node-version: [18, 20]
3333
name: "Test on Node.js ${{ matrix.node-version }}"
3434
steps:
3535
- uses: actions/checkout@v3
@@ -47,6 +47,6 @@ jobs:
4747
- name: Setup Node.js
4848
uses: actions/setup-node@v3
4949
with:
50-
node-version: 16
50+
node-version: 18
5151
- run: npm ci
5252
- run: npm run e2e

source/basic/array/README.md

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -659,9 +659,11 @@ console.log(array.length); // => 0
659659

660660
## 破壊的なメソッドと非破壊的なメソッド {#mutable-immutable}
661661

662-
これまで紹介してきた配列を変更するメソッドには、破壊的なメソッドと非破壊的メソッドがあります。この破壊的なメソッドと非破壊的メソッドの違いを知ることは、意図しない結果を避けるために重要です。
663-
破壊的なメソッドとは、配列オブジェクトそのものを変更し、変更した配列または変更箇所を返すメソッドです。
664-
非破壊的メソッドとは、配列オブジェクトのコピーを作成してから変更し、そのコピーした配列を返すメソッドです。
662+
これまで紹介してきた配列を変更するメソッドには、破壊的なメソッドと非破壊的メソッドがあります。
663+
この破壊的なメソッドと非破壊的メソッドの違いを知ることは、意図しない結果を避けるために重要です。
664+
665+
破壊的なメソッド(Mutable Method)とは、配列オブジェクトそのものを変更し、変更した配列または変更箇所を返すメソッドです。
666+
非破壊的メソッド(Immutable Method)とは、配列オブジェクトのコピーを作成してから変更し、そのコピーした配列を返すメソッドです。
665667

666668
<!-- 具体例:破壊的なメソッド -->
667669

@@ -698,7 +700,7 @@ console.log(myArray === newArray); // => false
698700
```
699701

700702

701-
JavaScriptにおいて破壊的なメソッドと非破壊的メソッドを名前から見分ける方法はありません
703+
JavaScriptにおいて破壊的なメソッドと非破壊的メソッドを名前から見分けるのは難しいという問題があります
702704
また、配列を返す破壊的なメソッドもあるため、返り値からも判別できません。
703705
たとえば、Arrayの`sort`メソッドは返り値がソート済みの配列ですが破壊的メソッドです。
704706

@@ -748,7 +750,8 @@ console.log(array); // => ["A", "C"]
748750
一方、非破壊的メソッドは配列のコピーを作成するため、元々の配列に対して影響はありません。
749751
この`removeAtIndex`関数を非破壊的なものにするには、受け取った配列をコピーしてから変更を加える必要があります。
750752

751-
JavaScriptには`copy`メソッドそのものは存在しませんが、配列をコピーする方法としてArrayの`slice`メソッドと`concat`メソッドが利用されています。`slice`メソッドと`concat`メソッドは引数なしで呼び出すと、その配列のコピーを返します。
753+
JavaScriptには`copy`メソッドそのものは存在しませんが、配列をコピーする方法としてArrayの`slice`メソッドと`concat`メソッドが利用されています。
754+
`slice`メソッドと`concat`メソッドは引数なしで呼び出すと、その配列のコピーを返します。
752755

753756
{{book.console}}
754757
```js
@@ -783,9 +786,78 @@ console.log(newArray); // => ["A", "C"]
783786
console.log(array); // => ["A", "B", "C"]
784787
```
785788

786-
このようにJavaScriptの配列には破壊的なメソッドと非破壊的メソッドが混在しています。そのため、統一的なインターフェースで扱えないのが現状です。
787-
このような背景もあるため、JavaScriptには配列を扱うためのさまざまライブラリが存在します。
788-
非破壊的な配列を扱うライブラリの例として[immutable-array-prototype][][Immutable.js][]などがあります。
789+
このようにJavaScriptの配列には破壊的なメソッドと非破壊的メソッドが混在しています。
790+
名前からも区別することが難しく、副作用を避けるためにコピーを作ってから破壊的メソッドを使うというパターンが利用されていました。
791+
792+
しかし、ES2023でこの状況を改善する変更が追加されています。
793+
今まで、破壊的なメソッドしかなかった、`splice``reverse``sort`に対して、
794+
非破壊的なバージョンである`toSpliced``toReversed``toSorted`が追加されました。
795+
796+
これらの`to`から始まる非破壊的メソッドが受け取る引数は破壊的なメソッドと同じですが、非破壊的に変更した配列を返す点が異なります。
797+
次のコードの`toSpliced`メソッドは、配列を複製してから変更するため、元々の配列である`array`には影響を与えていないことがわかります。
798+
799+
{{book.console}}
800+
<!-- doctest:meta:{ "ECMAScript": "2023" } -->
801+
```js
802+
const array = ["A", "B", "C"];
803+
// `toSpliced`は`array`を複製してから変更する
804+
const newArray = array.toSpliced(1, 1);
805+
console.log(newArray); // => ["A", "C"]
806+
// コピー元の`array`には影響がない
807+
console.log(array); // => ["A", "B", "C"]
808+
```
809+
810+
先ほど`removeAtIndex`関数の実装では、`slice`メソッドで配列をコピーしてから`splice`メソッドを呼び出していました。
811+
次のコードでは、`toSpliced`メソッドを使うことで、より簡潔に非破壊的な`removeAtIndex`関数を実装しています。
812+
813+
{{book.console}}
814+
<!-- doctest:meta:{ "ECMAScript": "2023" } -->
815+
```js
816+
// `array`の`index`番目の要素を削除した配列を返す関数
817+
function removeAtIndex(array, index) {
818+
// コピーを作成してから変更する
819+
return array.toSpliced(index, 1);
820+
}
821+
const array = ["A", "B", "C"];
822+
// `array`から1番目の要素を削除した配列を取得
823+
const newArray = removeAtIndex(array, 1);
824+
console.log(newArray); // => ["A", "C"]
825+
// 元の`array`には影響がない
826+
console.log(array); // => ["A", "B", "C"]
827+
```
828+
829+
また、ES2023では配列の指定したインデックスの要素を非破壊的に変更する`with`メソッドも追加されました。
830+
`array[index] = value`の代入処理は、元々の配列を変更する破壊的な処理です。
831+
これに対して`with`メソッドは、配列を複製してから指定したインデックスの要素を変更した配列を返す非破壊的なメソッドです。
832+
833+
{{book.console}}
834+
<!-- doctest:meta:{ "ECMAScript": "2023" } -->
835+
```js
836+
const array = ["A", "B", "C"];
837+
// `array`の1番目の要素を変更した配列を返す
838+
const newArray = array.with(1, "B2");
839+
console.log(newArray); // => ["A", "B2", "C"]
840+
```
841+
842+
次の表では、破壊的な方法に対応する非破壊的な方法をまとめています。
843+
844+
| 破壊的な方法 | 非破壊な方法 |
845+
| ---------------------------------------- | ------------- |
846+
| `array[index] = item` | [`Array.prototype.with`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/with)<sup>[ES2023]</sup> |
847+
| [`Array.prototype.pop`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/pop) | [`array.slice(0, -1)`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)[`array.at(-1)`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/at)<sup>[ES2022]</sup> |
848+
| [`Array.prototype.push`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/push) | `[...array, item]`<sup>[ES2015]</sup> |
849+
| [`Array.prototype.splice`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) | [`Array.prototype.toSpliced`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced)<sup>[ES2023]</sup> |
850+
| [`Array.prototype.reverse`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) | [`Array.prototype.toReversed`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toReversed)<sup>[ES2023]</sup> |
851+
| [`Array.prototype.sort`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) | [`Array.prototype.toSorted`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted)<sup>[ES2023]</sup> |
852+
| [`Array.prototype.shift`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/shift) | [`array.slice(1)`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)[`array.at(0)`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/at)<sup>[ES2022]</sup> |
853+
| [`Array.prototype.unshift`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) | `[item, ...array]`<sup>[ES2015]</sup> |
854+
| [`Array.prototype.copyWithin`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin)<sup>[ES2015]</sup> | なし |
855+
| [`Array.prototype.fill`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fill)<sup>[ES2015]</sup> | なし |
856+
857+
破壊的なメソッドは、シンプルですが元の配列も変更してしまうため、意図しない副作用が発生しバグの原因となる可能性があります。
858+
非破壊的なメソッドは、使い分けが必要ですが元の配列を変更せずに新しい配列を返すため、副作用が発生することはありません。
859+
860+
そのため、まず非破壊的な方法で書けるかを検討し、そうではない場合に破壊的な方法を利用するとよいでしょう。
789861

790862
## 配列を反復処理するメソッド {#array-iterate}
791863

source/basic/variables/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ object.key = "新しい値";
245245

246246
`var`はほとんどすべてのケースで`let``const`に置き換えが可能です。
247247
`const`は再代入できない変数を定義するキーワードです。再代入を禁止することで、ミスから発生するバグを減らすことが期待できます。
248-
このため変数を宣言する場合には、まず`const`で定義できないかを検討し、できない場合は`let`を使うことを推奨しています。
248+
そのため変数を宣言する場合には、まず`const`で定義できないかを検討し、できない場合は`let`を使うことを推奨しています。
249249

250250
<!-- textlint-enable eslint -->
251251

test/markdown-doc-test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ const sourceDir = path.join(__dirname, "..", "source");
2020
* @type {string[]}
2121
*/
2222
const IgnoredECMAScriptVersions = (() => {
23-
if (semver.cmp(process.version, ">=", "18.0.0")) {
23+
if (semver.cmp(process.version, ">=", "20.0.0")) {
2424
return []; // すべて通る前提
2525
}
26+
if (semver.cmp(process.version, ">=", "18.0.0")) {
27+
return ["2023"]; // Array.prototype.withがサポートされていない
28+
}
2629
if (semver.cmp(process.version, ">=", "16.0.0")) {
2730
// Array.prototype.findLastIndex をサポートしていない
2831
return ["2023"];

0 commit comments

Comments
 (0)