diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 6735d51c06..61e00aab05 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -82,7 +82,7 @@ alert("some code"); `"use strict"` でスクリプトを開始することをオススメします...が何がよいか知っているでしょうか? -モダンな JavaScript は "クラス" や "モジュール" --- 高度な言語構造をサポートしており、これらは `use strict` は自動的に有効にします。したがって、それらを利用する場合、`"use strict"` を追加する必要はありません。 +モダンな JavaScript は "クラス" や "モジュール" --- 高度な言語構造をサポートしており、これらは `use strict` を自動的に有効にします。したがって、それらを利用する場合、`"use strict"` を追加する必要はありません。 **なので、今のところ `"use strict";` はスクリプトの先頭に書くことを推奨します。後でコードがすべてクラスとモジュールに含まれている場合は省略できます。** diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 37b8907543..d8c12edb52 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -98,7 +98,7 @@ let str2 = 'Single quotes are ok too'; let phrase = `can embed ${str}`; ``` -JavScriptでは3種類の引用符があります。 +JavaScriptでは3種類の引用符があります。 1. ダブルクォート: `"Hello"`. 2. シングルクォート: `'Hello'`. diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 369378b85c..4b4a8fbaab 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -66,7 +66,7 @@ alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 回) alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 回) ``` -数学的には、べき乗は非整数値も同様に定義されています。例えば、平方根は 1/2` によるべき乗です: +数学的には、べき乗は非整数値も同様に定義されています。例えば、平方根は 1/2 によるべき乗です: ```js run alert( 4 ** (1/2) ); // 2 (1/2 の累乗は平方根と同じです) diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md index 41df5d6346..7d73111424 100644 --- a/1-js/02-first-steps/09-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -185,7 +185,7 @@ alert( null >= 0 ); // (3) *!*true*/!* ### 比べるものがない undefined -値 `undefined` は比較に関与しません。: +値 `undefined` は比較に関与しません。: ```js run alert( undefined > 0 ); // false (1) diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index d826f78472..292f02a55d 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -16,7 +16,7 @@ - オブジェクトプロパティのループは [for..in](info:object#forin) です。 - 配列や反復可能オブジェクトに対するループは [for..of](info:array#loops) と [iterables](info:iterable) です。 -それ以外の場合は、続けて呼んでみてください。 +それ以外の場合は、続けて読んでみてください。 ``` ## "while" ループ diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md index 052ad963e2..60e3fd1480 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -316,7 +316,7 @@ Linter はコードのスタイルを自動でチェックし改善が提案で 1. [Node.js](https://nodejs.org/) をインストールします。 2. `npm install -g eslint` コマンドで ESLint をインストールします(npm は Node.js パッケージインストーラです) -3. JavaScriptプロジェクト(すべてのファイルを含むフォルダ)のルートに `.ellintrc` という名前の設定ファイルを作ります +3. JavaScriptプロジェクト(すべてのファイルを含むフォルダ)のルートに `.eslintrc` という名前の設定ファイルを作ります 4. ESlint と統合するエディタのプラグインをインストール/有効化します。エディタの大多数はそれを持っています。 `.eslintrc` の例です: diff --git a/1-js/05-data-types/04-array/5-array-input-sum/solution.md b/1-js/05-data-types/04-array/5-array-input-sum/solution.md index 2f307c8c7d..cad00e4bf2 100644 --- a/1-js/05-data-types/04-array/5-array-input-sum/solution.md +++ b/1-js/05-data-types/04-array/5-array-input-sum/solution.md @@ -1,4 +1,4 @@ -些細ですが重要な解法の詳細に注意してください。私たちは、`prompt` のあと、すぐに `value` を数値に変換しません。なぜなら、`value = +value` の後に、ゼロ(有効数字)から空の文字列(停止のサイン)を伝えることができないからです。私たちは後ほど代わりにそれを行います。 +些細ですが重要な解法の詳細に注意してください。私たちは、`prompt` のあと、すぐに `value` を数値に変換しません。なぜなら、`value = +value` の後に、ゼロ(有効数字)と空の文字列(停止のサイン)を区別することができないからです。私たちは後ほど代わりにそれを行います。 ```js run demo diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js index 02299e307e..c065c9ee40 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js @@ -8,13 +8,14 @@ describe("groupById", function() { ]; assert.deepEqual(groupById(users), { - john: {id: 'john', name: "John Smith", age: 20} + john: {id: 'john', name: "John Smith", age: 20}, ann: {id: 'ann', name: "Ann Smith", age: 24}, pete: {id: 'pete', name: "Pete Peterson", age: 31}, }); }); it("works with an empty array", function() { - assert.deepEqual(groupById(users), {}); + let users_empty = []; + assert.deepEqual(groupById(users_empty), {}); }); }); diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 5ba1ebb98a..3f77c3a491 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -447,7 +447,7 @@ alert(arr); // *!*1, 2, 15*/!* ```` ````smart header="ベストなアロー関数" -[アロー関数](info:function-expressions-arrows#arrow-functions) を覚えていますか? すっきりしたソートを書くために使えます。: +[アロー関数](info:arrow-functions-basics) を覚えていますか? すっきりしたソートを書くために使えます。: ```js arr.sort( (a, b) => a - b ); @@ -537,7 +537,7 @@ alert( str ); // Bilbo;Gandalf;Nazgul ### reduce/reduceRight -配列に対して繰り返し処理が必要なときは、`forEach`, `for` あるいは `for..if` を使うことができます。 +配列に対して繰り返し処理が必要なときは、`forEach`, `for` あるいは `for..of` を使うことができます。 各要素のデータを反復して返す必要があるときには、`map`を使うことができます。 diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 97876d4c49..290bb43c3f 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -14,7 +14,7 @@ 例えば、配列ではありませんが、`for..of` に適したオブジェクトを持っています。 -以下は数値の間隔を表す `range` オブジェクト: +以下は数値の間隔を表す `range` オブジェクトです: ```js let range = { @@ -105,7 +105,7 @@ for (let num of range) { 今、`range[Symbol.iterator]()` は `range` オブジェクト自身を返します: `next()` メソッドを持ち、`this.current` で現在の反復の状況を覚えています。 -欠点は、オブジェクトに対して同時に2つの `for..of` ループを実行することは不可能だということです。: イテレータ が1つしかないので、オブジェクトは繰り返し状態を共有します。ですが、2つの並列 for-of はたとえ非同期のりナリオにおいてもまれです。 +欠点は、オブジェクトに対して同時に2つの `for..of` ループを実行することは不可能だということです。: イテレータ が1つしかないので、オブジェクトは繰り返し状態を共有します。ですが、2つの並列 for-of はたとえ非同期のシナリオにおいてもまれです。 ```smart header="無限のイテレータ" 無限の イテレータ もまた実行可能です。例えば、 `range.to = Infinity` で、`range` が無限大になります。または、擬似乱数の無限のシーケンスを生成する反復可能なオブジェクトを作ることができます。これもまた役立つことがあります。 diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md index 2e1e5932ca..af8852f504 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -16,7 +16,7 @@ map.set("name", "John"); let keys = map.keys(); *!* -// Error: numbers.push is not a function +// Error: keys.push is not a function keys.push("more"); */!* ``` diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index 4e651cfd06..ffa4727123 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -63,7 +63,7 @@ visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -オブジェクトをキーとして使用することは、最も注目に値する重要な `Map` の機能の1つです。同じことは `Object` ではカウントされません。`Object` ではキーとして文字列使用は問題ありませんが、キーとして別のオブジェクトを使用することはできません。 +オブジェクトをキーとして使用することは、最も注目に値する重要な `Map` の機能の1つです。同じことを `Object` で行うことはできません。`Object` ではキーとして文字列を使用することは問題ありませんが、オブジェクトを使用することはできません。 やってみましょう: diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index eb77c20aeb..1f8a90dbb7 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -114,7 +114,7 @@ john = null; // 参照を上書きします キーとしてオブジェクトを使用して、`WeakMap` にデータを格納し、オブジェクトがガベージコレクションされたとき、データも同様自動的に消えます。 ```js -weakMap.put(john, "secret documents"); +weakMap.set(john, "secret documents"); // もし john がなくなった場合、秘密のドキュメントは破壊されるでしょう ``` @@ -248,7 +248,7 @@ obj = null; - オブジェクトは、別の場所から到達可能である間、`Set` に存在します。 - `Set` 同様、`add`, `has`, `delete` をサポートしますが、`size`, `keys()` とイテレーションはサポートしません。 -"弱い" ので、追加の格納場所としても使えます。ですが、任意のデータではなく、むしろ "はい/いいえ" の事実のためです。`WeakSet` のメンバーはオブジェクトについてなにかを意味する場合があります。 +"弱い" ので、追加の格納場所としても使えます。ですが、任意のデータではなく、むしろ "はい/いいえ" という boolean ライクな情報を記憶するためのものとしてです。`WeakSet` のメンバーはオブジェクトについてなにかを意味する場合があります。 例えば、ユーザを `WeakSet` に追加して、サイトにアクセスしたユーザを追跡できます。: diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md index 9945c255c2..21e0d3e753 100644 --- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md @@ -16,7 +16,7 @@ let salaries = { 支払い額がトップの名前を返す関数 `topSalary(salaries)` を作ってください。 -- `salaries` がからの場合、`null` を返します。 +- `salaries` が空の場合、`null` を返します。 - トップの人が複数いる場合、それらのいずれかを返します。 P.S. キー/値ベアを反復するために `Object.entries` と分割代入を使ってください。 diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index 77cf784b9f..f89cd3b6e3 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -469,7 +469,7 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -現実の問題の1つは、どうやって引数の順番を覚えるか、です。コードがしっかりドキュメント化されていれば、通常は IDE が助けてくれます。しかし、他にも問題があります。ほとんどのパタメータがデフォルトでOKの場合の関数の呼び方です。 +現実の問題の1つは、どうやって引数の順番を覚えるか、です。コードがしっかりドキュメント化されていれば、通常は IDE が助けてくれます。しかし、他にも問題があります。ほとんどのパラメータがデフォルトでOKの場合の関数の呼び方です。 こうなりますか? diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 6f46754f4b..d94f3406c2 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -101,7 +101,7 @@ let user; // SyntaxError: 'user' has already been declared ```js run var user = "Pete"; -var user = "John"; // "var" 花にもしません (宣言済み) +var user = "John"; // "var" は何もしません (宣言済み) // ...エラーは発生しません alert(user); // John diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md index 42a6a00468..0183a47997 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md @@ -15,7 +15,7 @@ function count() { if (i == 1000000000) { alert("Done in " + (Date.now() - start) + 'ms'); - cancelInterval(timer); + clearInterval(timer); } } diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index 11fd5f10b1..044df46577 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -218,7 +218,7 @@ setTimeout(function run() { ![](settimeout-interval.svg) -**再帰的な `setInterval` は固定の遅延 (ここでは 100ms) を保証します。** +**再帰的な `setTimeout` は固定の遅延 (ここでは 100ms) を保証します。** 新しい呼び出しは、以前の呼び出しの終わりに計画されるためです。 @@ -230,7 +230,7 @@ setTimeout(function run() { setTimeout(function() {...}, 100); ``` -`setInterval` では `cancelInterval` が呼ばれるまで、関数はメモリ上に存在し続けます。 +`setInterval` では `clearInterval` が呼ばれるまで、関数はメモリ上に存在し続けます。 そこには副作用があります。関数は外部のレキシカル環境を参照するので、それが生きている間は外部の変数も生き続けます。それらは関数自身よりもはるかに多くのメモリを必要とする場合があります。従って、スケジュールされた機能がもう必要ないときは、たとえそれが非常に小さいとしても、それをキャンセルする方がいいです。 ```` diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md index f742440656..858a6f8315 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md @@ -12,7 +12,7 @@ function delay(f, ms) { ここで、アロー関数がどう使われているか注意してください。ご存知の通り、アロー関数は独自の `this` や `arguments` を持ちません。なので、`f.apply(this, arguments)` はラッパーから `this` と `arguments` を取ります。 -もし、通常の関数を渡す場合、`setTimeout` はそれを引数なしで、 `this=window` (ブラウザの場合) で呼び出します。なので、ラッパーからそれらをわすようにコードを書く必要があります。: +もし、通常の関数を渡す場合、`setTimeout` はそれを引数なしで、 `this=window` (ブラウザの場合) で呼び出します。なので、ラッパーからそれらを渡すようにコードを書く必要があります。: ```js function delay(f, ms) { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index 7a60dfc5ed..424dc3fa64 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -400,7 +400,7 @@ E.g. 上記の例では、`slow` 関数にあるプロパティが含まれて 一部のデコレータは独自のプロパティを提供することがあります。E.g. 関数が何回実行されたか、どれだけ時間がかかったかをカウントし、ラッパープロパティを介してこの情報を公開する、といったケースです。 -関数のプロパティへのアクセスを維持するデコレータを作成する方法はありますが、これには、関数をラップするために特別な `Proxy` オブジェクトを使用する必要があります。これについては、後ほど で設営します。 +関数のプロパティへのアクセスを維持するデコレータを作成する方法はありますが、これには、関数をラップするために特別な `Proxy` オブジェクトを使用する必要があります。これについては、後ほど で説明します。 ## サマリ diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index d431943d4e..d8b17ef2a9 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -5,7 +5,7 @@ libs: # 関数バインディング -オブジェクトメソッドで `setTimeout` 使ったり、オブジェクトメソッドを渡すような場合、"`this` を失う" という既知の問題があります。 +オブジェクトメソッドで `setTimeout` を使ったり、オブジェクトメソッドを渡すような場合、"`this` を失う" という既知の問題があります。 突然、`this` が正しく動作するのをやめます。この状況は初心者の開発者には典型的ですが、経験者でも同様に起こりえます。 @@ -275,7 +275,7 @@ alert( triple(5) ); // = mul(3, 5) = 15 ## Going partial without context -仮に引数のいくつかを固定したいけど、コンテキスト `this` は固定したくない場合はどうしますか?例えば、オブジェクトメソッドです。 +仮に引数のいくつかを固定したいが、コンテキスト `this` は固定したくない場合はどうしますか?例えば、オブジェクトメソッドです。 ネイティブの `bind` はそれは許可しません。コンテキストを省略して引数だけ指定することはできません。 @@ -321,8 +321,8 @@ user.sayNow("Hello"); メソッド `func.bind(context, ...args)` はコンテキスト `this` を固定した関数 `func` の "束縛されたバリアント" を返します。 -通常は、オブジェクトメソッドで `this` を固定するために `bind` を適用し、どこかに渡すことができるようにします。たとえば、`setTimeout` に。 +通常は、オブジェクトメソッドで `this` を固定するために `bind` を適用し、どこか (たとえば `setTimeout`) に渡すことができるようにします。 -When we fix some arguments of an existing function, the resulting (less universal) function is called *partially applied* or *partial*. +既存の関数において、引数のいくつかを固定した汎用性の低い関数は、部分関数と呼ばれます。 -Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it. +部分関数は同じ引数を何度も繰り返したくないときに便利です。例えば、`send(from, to)` という関数があって、`from`はどのタスクでも常に同じになるはずなら、部分関数を利用してそれを処理できます。 diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index 08fb1d4e7d..9647d82679 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -122,7 +122,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name'... これで、`defineProperty` で上書きをしない限りは、誰も user.name を変えることはできません。 -```smart header="strict mode の場合にのいエラーが表示されます" +```smart header="strict mode の場合のみエラーが表示されます" 非 strict mode の場合、書き込み不可プロパティへの書き込みをしてもエラーは発生しません。ですが、操作は依然として成功はしません。非 strict 下では、フラグ違反の操作は単に無視されます。 ``` diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index d98f076971..94c960f4ad 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -122,7 +122,7 @@ alert(longEar.jumps); // true (rabbit から) ![](proto-animal-rabbit-chain.svg) -Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`. +ここで `longEar` から何かしらを読み取ろうとして、それが見つからなかった場合、JavaScript はまず `rabbit`、次に `animal` の順に探しに行きます。 実際には、2つの制限があります。: diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg new file mode 100644 index 0000000000..28daef3af8 --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg @@ -0,0 +1,48 @@ + + + + rabbit-animal-object.svg + Created with sketchtool. + + + + + toString: function + hasOwnProperty: function + ... + + + Object.prototype + + + + animal + + + + [[Prototype]] + + + [[Prototype]] + + + + [[Prototype]] + + + null + + + eats: true + + + + rabbit + + + + jumps: true + + + + \ No newline at end of file diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 396f16ca77..4980210b93 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -169,11 +169,13 @@ user.sayHi(); 他の違いもあります。この後見ていきます。 2. クラス メソッドは列挙不可です + クラス定義は、`"prototype"` にあるすべてのメソッドに対して `enumerable` フラグを `false` にセットします。 オブジェクトを `for...in` するとき、通常はクラスメソッドは必要ないのでこれは便利です。 3. クラスは常に `use strict` です + クラス構造の中のコードはすべて自動で strict モードです。 加えて、`class` 構文には後で説明するような多くの機能があります。 @@ -210,7 +212,7 @@ new User().sayHi(); // 動作します, MyClass の定義を表示 alert(MyClass); // error, MyClass の名前はクラスの外からは見えません ``` -次のように。クラスを動的に "要求に応じて" 作ることもできます。: +次のように、クラスを動的に "要求に応じて" 作ることもできます。: ```js run function makeClass(phrase) { diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 9443cba432..2a6a7f0930 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -247,7 +247,7 @@ JavaScriptでは、継承しているクラスのコンストラクタ関数(い なので、親(元になる)コンストラクタを実行するために、派生コンスタクタは `super` の呼び出しが必要になります。そうしないと、`this` のオブジェクトは生成されないからです。結果、エラーになるでしょう。 -`Rabbit` コンストラクタを動作させるために、`this` を使う前に `super()` を呼びます。: +`Rabbit` コンストラクタを動作させるために、`this` を使う前に `super()` を呼びます: ```js run class Animal { @@ -294,7 +294,7 @@ alert(rabbit.earLength); // 10 メソッドだけではなく、クラスフィールドもオーバーライドすることができます。 -ですが、親のコンストラクタでオーバーライドされたフィールドにアクセスする際、多くの他他のプログラミング言語とは大きく異る、トリッキーな振る舞いがあります。 +ですが、親のコンストラクタでオーバーライドされたフィールドにアクセスする際、多くの他のプログラミング言語とは大きく異なる、トリッキーな振る舞いがあります。 この例を考えます: @@ -374,7 +374,7 @@ new Rabbit(); // rabbit 幸い、この振る舞いはオーバーライドされたフィールドが親コンストラクタの中で使用されている場合にのみです。出くわすと何が起こっているのか理解するのが難しいかもしれないので、ここで説明しています。 -問題担った場合には、フィールドの代わりにメソッドあるいは getter/setter を使用して修正できます。 +問題になった場合には、フィールドの代わりにメソッドあるいは getter/setter を使用して修正できます。 ## Super: internals, [[HomeObject]] diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index df0a9b706d..0653aefa60 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -20,7 +20,7 @@ 多くの構成要素があります。しかし、何も知らなくても私たちは使うことができます。 -コーヒーメーカーはとても信頼性が高いですね。何年も使え、調子が悪い場合にだけ修正に持っていきます。 +コーヒーメーカーはとても信頼性が高いですね。何年も使え、調子が悪い場合にだけ修理に持っていきます。 コーヒーメーカーの信頼性とシンプルさの秘密は、すべての構成要素がよく調整され、内部に _隠れている_ ことです。 @@ -169,7 +169,7 @@ class CoffeeMachine { new CoffeeMachine().setWaterAmount(100); ``` -これは少し長く見えますが、関数はより柔軟です。たとえいま時点では必要ないとしても、この方法の場合、複数の引数を受け取ることができます。そのため、将来なにかをリファクタする必要がある場合に備えるなら、関数はより安全な選択肢です。 +これは少し長く見えますが、関数はより柔軟です。たとえ現時点では必要ないとしても、この方法の場合、複数の引数を受け取ることができます。そのため、将来なにかをリファクタする必要がある場合に備えるなら、関数はより安全な選択肢です。 もちろん、これはトレードオフです。一方で get/set 構文はより短くかけます。ここに厳密なルールはないので、決めるのはあなた次第です。 ```` diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index f6825e489f..8709ab8399 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -109,15 +109,15 @@ new User("Dude").sayHi(); // Hello Dude! さて、実践のためのミックスインを作ってみましょう。 -多くのブラウザオブジェクト(例えば)の重要な特徴は、イベントを生成できることです。イベントは、それを必要とするものへ "情報をブロードキャスト" する優れた方法です。そのため、簡単にイベントに関連する関数を任意の class/object に追加できるよう minxin を作成しましょう。 +多くのブラウザオブジェクト(例えば)の重要な特徴は、イベントを生成できることです。イベントは、それを必要とするものへ "情報をブロードキャスト" する優れた方法です。そのため、簡単にイベントに関連する関数を任意の class/object に追加できるよう mixin を作成しましょう。 - mixin はなにか重要なことが起こったときに、"イベントを生成" するためのメソッド `.trigger(name, [...data])` を提供します。`name` 引数はイベント名で、オプションでイベントデータを含む追加の引数が続きます。 -- また、指定された名前のイベントのリスナーとして `handler` 関数を追加するメソッド `.on(name, handler)` も提供します。指定された `name` のイベントがとリガーされたときに呼ばれ、`.trigger` 呼び出しから引数と取得します。 +- また、指定された名前のイベントのリスナーとして `handler` 関数を追加するメソッド `.on(name, handler)` も提供します。指定された `name` のイベントがトリガーされたときに呼ばれ、`.trigger` 呼び出しから引数と取得します。 - そして、`handler` リスナーを削除するためのメソッド `.off(name, handler)`。 -この minxin を追加したあと、オブジェクト `user` は、訪問者がログインするときに、`"login"` イベントを生成することができるようになります。また、別のオブジェクト、例えば `calendar` はそのようなイベントをリッスンし、ログインした人のカレンダーを読み込みます。 +この mixin を追加したあと、オブジェクト `user` は、訪問者がログインするときに、`"login"` イベントを生成することができるようになります。また、別のオブジェクト、例えば `calendar` はそのようなイベントをリッスンし、ログインした人のカレンダーを読み込みます。 -あるいは、`menu` はメニュー項目が選択されたときにイベント `"select"` を生成でき、他のオブジェクトはそのイベントに反応するためにハンドラを割り当てることができます。などです。 +あるいは、`menu` はメニュー項目が選択されたときにイベント `"select"` を生成でき、他のオブジェクトはそのイベントに反応するためにハンドラを割り当てることができます。 これはそのコードです: diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 208b5d5376..417961962a 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -353,7 +353,7 @@ try { もちろん、すべての可能性があります! プログラマはミスをするものです。何十年も何百万人もの人が使っているオープンソースのユーティリティであっても、突然酷いバグが発見され、ひどいハッキングにつながることがあります( `ssh` ツールで起こったようなものです)。 -私たちのケースでは、`try..catch` は "不正なデータ" エラーをキャッチすることを意図しています。しかし、その性質上、`catch` は `try` からの *すべての* エラーを取得します。ここでは予期しないエラーが発生しますが、同じ `"JSON Error"` メッセージが表示されます。これは誤りでコ、ードのでバッグをより難しくします。 +私たちのケースでは、`try..catch` は "不正なデータ" エラーをキャッチすることを意図しています。しかし、その性質上、`catch` は `try` からの *すべての* エラーを取得します。ここでは予期しないエラーが発生しますが、同じ `"JSON Error"` メッセージが表示されます。これは誤りでコードのデバッグをより難しくします。 このような問題を避けるため、"再スロー" という手法が利用できます。ルールはシンプルです: diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index f7332aa7a1..fc787b6c96 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -273,7 +273,7 @@ let promise = new Promise(resolve => resolve("done!")); promise.then(alert); // done! (すぐに表示されます) ``` -これは、実際の "購読者リスト" のシナリオよりも promise をより協力にすることに注目してだくさい。シンガーが既に新曲をリリース済みで、その後にファンが購読者リストにサインアップした場合、彼らはおそらくその曲は受け取れないでしょう。実世界の購読者はイベントの前に登録を完了していなければなりません。 +これは、実際の "購読者リスト" のシナリオよりも promise をより強力にすることに注目してだくさい。シンガーが既に新曲をリリース済みで、その後にファンが購読者リストにサインアップした場合、彼らはおそらくその曲は受け取れないでしょう。実世界の購読者はイベントの前に登録を完了していなければなりません。 Promise はより柔軟性があります。いつでもハンドラが追加できます。結果が既にでている場合でも実行します。 ```` diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index 0866a94031..2125eeca41 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -166,7 +166,7 @@ loadScript("/article/promise-chaining/one.js") このチェーンにより多くの非同期アクションが追加できます。ここで、このコードは依然として "フラット" であり、右にではなく、下に成長していることに注目してください。"破滅のピラミッド" の兆候はありません。 -技術的にはそれぞれお `loadScript` に直接 `.then` を書くことも可能でです: +技術的にはそれぞれを `loadScript` に直接 `.then` を書くことも可能です: ```js run loadScript("/article/promise-chaining/one.js").then(function(script1) { @@ -226,7 +226,7 @@ JavaScript は行 `(*)` で `.then` ハンドラによって返却されたオ フロントエンドのプログラミングでは、promise はネットワークリクエストで頻繁に使われます。その例も見てみましょう。 -リモートサーバからユーザに関する情報をロードすのに [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) メソッドを使います。メソッドは[別の章](info:fetch)で説明するように多くの任意パラメータがありますが、基本の使い方はとてもシンプルです: +リモートサーバからユーザに関する情報をロードするために [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) メソッドを使います。メソッドは[別の章](info:fetch)で説明するように多くの任意パラメータがありますが、基本の使い方はとてもシンプルです: ```js let promise = fetch(url); diff --git a/1-js/12-generators-iterators/2-async-iterators-generators/article.md b/1-js/12-generators-iterators/2-async-iterators-generators/article.md index 08d754261f..404f0803d2 100644 --- a/1-js/12-generators-iterators/2-async-iterators-generators/article.md +++ b/1-js/12-generators-iterators/2-async-iterators-generators/article.md @@ -96,7 +96,7 @@ let range = { */!* *!* - // asyc next の中で、 "await" が使えます + // async next の中で、 "await" が使えます await new Promise(resolve => setTimeout(resolve, 1000)); // (3) */!* @@ -250,7 +250,7 @@ for(let value of range) { ````smart header="内部的な違い" 技術的には、ジェネレータの詳細を覚えている読者であれば、内部的な違いに気づくかもしれません。 -非同期ジェネレータの場合、`generator.nex()` メソッドは非同期であり、promise を返します。 +非同期ジェネレータの場合、`generator.next()` メソッドは非同期であり、promise を返します。 通常のジェネレータでは、`result = generator.next()` を使用して値を取得します。非同期ジェネレータでは、次のように `await` を追加する必要があります: diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index 9e903ca5ec..a240dd1227 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -1,20 +1,21 @@ # モジュール, 導入 -アプリケーションが大きくなるにつれ、それを複数のファイルに分割したくなります。いわゆる 'モジュール' です。 -通常、モジュールはクラスや便利な関数のライブラリを含みます。 +アプリケーションが大きくなるにつれ、それを複数のファイルに分割したくなります。いわゆる 'モジュール' です。通常、モジュールはクラスや便利な関数のライブラリを含みます。 -長い間、JavaScript には言語レベルのモジュール構文は存在しませんでした。当初はスクリプトが小さくて単純だったため、それは問題ではありませんでした。そのため、モジュールの仕組みは必要ありませんでした。 +長い間、JavaScript には言語レベルのモジュール構文は存在しませんでした。当初はスクリプトが小さくて単純だったため問題ではありませんでした。そのため、モジュールの仕組みも必要ありませんでした。 -しかし、スクリプト徐々に複雑になってきました。そのため、コミュニティはコードをモジュールにまとめるための様々な方法を発明しました。 +しかし、スクリプトが徐々に複雑になってきたため、コミュニティはコードをモジュールにまとめるための様々な方法を発明しました。 -例えば: +いくつか挙げます: - [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- 最も古いモジュールシステムの1つで、最初はライブラリ[require.js](http://requirejs.org/)で実装されました。 - [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- Node.js サーバ用に作られたモジュールシステムです。 - [UMD](https://github.com/umdjs/umd) -- もう1つのモジュールシステムで、ユニバーサルなものとして提案されています。AMD と CommonJS と互換性があります。 -今や、これらはゆっくりと歴史の一部になっていますが、依然として古いスクリプトの中で利用されています。言語レベルのモジュールシステムの標準は 2015 年に登場し、それ以来徐々に進化し、今ではすべての主要なブラウザとNode.js でサポートされています。 +今や、これらはゆっくりと歴史の一部になっていますが、依然として古いスクリプトの中で利用されています。 + +言語レベルのモジュールシステムの標準は 2015 年に登場し、それ以来徐々に進化し、今ではすべての主要なブラウザとNode.js でサポートされています。なので、ここからはモダンな JavaScript モジュールについて学んでいきます。 ## モジュールとは? @@ -44,17 +45,25 @@ alert(sayHi); // function... sayHi('John'); // Hello, John! ``` -このチュートリアルでは言語自身に焦点を当てていますが、デモ環境としてブラウザを利用します。なので、ブラウザでモジュールを動作させる方法を見ておきましょう。 +`import` ディレクティブは現在のファイルからの相対パス `./sayHi.js` のモジュールを読み込み、エクスポートされた関数 `sayHi` を対応する変数に割り当てます。 + +ブラウザで例を実行してみましょう。 + +モジュールは特別なキーワードと機能を提供するので、` ``` -ここではブラウザで確認していますが、同じことはどのモジュールにも当てはまります。 - ### モジュールレベルのスコープ 各モジュールには独自の最上位のスコープがあります。つまり、モジュール内の最上位の変数や関数は他のスクリプトからは見えません。 -下の例では、2つのスクリプトがインポートされており、`hello.js` は `user.js` で宣言されている変数 `user` を使おうとして、失敗します: +下の例では、2つのスクリプトがインポートされており、`hello.js` は `user.js` で宣言されている変数 `user` を使おうとします。が、別々のモジュールなので失敗します(コンソールでエラーが確認できます): [codetabs src="scopes" height="140" current="index.html"] -モジュールは、外部からアクセス可能にしたいものは `export` をし、必要なものは `import` することを期待しています。 +モジュールは、外部からアクセス可能にしたいものは `export` を行い、必要なものは `import` が必要です。 + +- `user.js` は `user` 変数のエクスポートが必要です。 +- `hello.js` は `user.js` モジュールからのインポートが必要です。 -したがって、`index.html` の代わりに、直接 `hello.js` で `user.js` をインポートする必要があります。 +つまり、モジュールでは、グローバル変数に依存するのではなく、インポート/エクスポートを使用します。 これは正しい例です: [codetabs src="scopes-working" height="140" current="hello.js"] -ブラウザでは、各 ` ``` -もしも、本当に "グローバルな" ブラウザ内変数を作る必要がある場合は、それを明示的に `window` に割り当て、`window.user` としてアクセスします。しかし、これは例外であり正当な理由がある場合のみです。 +```smart +ブラウザでは、e.g. `window.user = "John"` のように、変数を明示的に `window` プロパティに割り当てることで、ウィンドウレベルのグローバルな変数を作ることができます。 + +以降、`type="module"` の有無に関わらず、すべてのスクリプトはそれが参照できます。 + +とはいえ、このようなグローバル変数の作成は、よく思われない行為です。このようなことは避けるようにしてください。 +``` ### モジュールコードはインポート時の初回にのみ評価されます もし同じモジュールが複数の他の場所でインポートされる場合、そのコードは初回のみ実行されます。その後エクスポートしたものはすべてのインポートしているモジュールで利用されます。 -これは重要な結果をもたらします。例を見てみましょう。 +1度限りの評価は重要な結果をもたらすため、注意が必要です。 + +いくつか例を見てみましょう。 まず、メッセージを表示すると言ったような、副作用をもたらすモジュールコードを実行する場合、複数回インポートしてもトリガされるのは1度だけです(初回)。: @@ -126,9 +146,11 @@ import `./alert.js`; // Module is evaluated! import `./alert.js`; // (nothing) ``` -実際、最上位のモジュールコードは主に初期化に使われます。データ構造を作成し、それらを事前に設定します。そして、何かを再利用可能にしたいとき、それらをエクスポートします。 +モジュールはすでに評価済みなので、2つ目のインポートは何も表示しません。 + +ルールがあります: 初期化やモジュール固有の内部データ構造の作成には、トップレベルのモジュールのコードを使用する必要があります。複数回呼び出し可能にする必要がある場合は、上記の `sayHi` で行ったように、関数としてエクスポートする必要があります。 -これはより高度な例です。 +より高度な例を考えてみましょう。 モジュールがオブジェクトをエクスポートするとしましょう: @@ -158,38 +180,52 @@ alert(admin.name); // Pete */!* ``` -繰り返しましょう -- モジュールは一度だけ実行されます。エクスポートが生成され、それがインポータ間で共有されます。そのため、なにかが `admin` オブジェクトを変更した場合、他のモジュールにもその変更が見えます。 +ご覧の通り、`1.js` がインポートした `admin` の `name` プロパティを変更すると、`2.js` は新しい `admin.name` が参照できます。 + +これがまさにモジュールが1度のみ実行されるためです。エクスポートが生成され、インポートする側でそれらを共有するため、何かが `admin` オブジェクトを変更すると、他のインポートしたスクリプトはその変更が見えます。 + +**このような振る舞いは、モジュールを *構成(configure)* できるため実際に非常に便利です。** -このような振る舞いは、コンフィグレーションが必要なモジュールにとっては便利です。最初のインポートで必要なプロパティを設定することができ、以降のインポートではすでに準備ができている状態です。 +言い換えると、モジュールはセットアップが必要な汎用機能が提供できます。例.認証には資格(credential)が必要です。そして、外部のコードが割り当てることを期待した構成用のオブジェクトをエクスポートします。 -例えば、`admin.js` モジュールは特定の機能を提供するかもしれませんが、外部から `admin` オブジェクトにクレデンシャル情報が来ることを期待します。: +これは古典的なパターンです: +1. モジュールはいくつかの構成手段をエクスポートします。例.構成オブジェクト +2. 初回インポート時にそれらを初期化し、そのプロパティへ書き込みます。トップレベルのアプリケーションスクリプトがそれを行うかもしれません。 +3. 以降のインポートでは、そのモジュールを使用します。 + +例えば、`admin.js` モジュールは特定の機能(例. 認証など)を提供するかもしれませんが、外部から `admin` オブジェクトにクレデンシャル情報が来ることを期待します。: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -ここで、`init.js` は我々のアプリケーションの最初のスクリプトで、`admin.name` をセットします。その後、`admin.js` 自身の内側から行われる呼び出しを含め、誰もがそれを見ることができます。: +ここで、`admin.js` は `config` オブジェクトをエクスポートします(初期は空ですが、デフォルトプロパティもある場合もあります)。 + +次に `init.js` 、我々のアプリの最初のスクリプトで、`config` をインポートし、`config.user` を設定します: ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +...これでモジュール `admin.js` は構成されました。 -alert(admin.name); // *!*Pete*/!* +以降のインポートはこれを呼び出すことができ、現在のユーザが正しく表示されます: + +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta オブジェクト `import.meta` は現在のモジュールに関する情報を含んでいます。 @@ -198,15 +234,18 @@ sayHi(); // Ready to serve, *!*Pete*/!*! ```html run height=0 ``` -### 最上位の "this" は undefined +### モジュールでは、最上位の "this" は undefined です -これは小さな特徴ですが、完全性のために言及しておきます。 +これはささいな特徴ですが、完全性のために言及しておきます。 -モジュールでは、最上位の `this` は非モジュールスクリプトにおけるグローバルオブジェクトとは対照的に、undefined です。 +モジュールでは、最上位の `this` は undefined です。 + +`this` がグローバルオブジェクトである非モジュールスクリプトとの比較です:。 ```html run height=0 +以下の通常のスクリプトと比較してください: + ``` -バンドルツールを使用する場合、モジュールも一緒にバンドルされると、それらの `import/export` 文は特別なバンドル呼び出しに置き換えられます。したがって、結果として生じるビルドは `type=module` を必要としません。なので、それを通常のスクリプトに置くことができます。: - -```html - - -``` - ## ビルドツール 現実には、ブラウザモジュールが "生" の形式で使用されることはほとんどありません。通常、それらを [Webpack](https://webpack.js.org/) などの特別なツールを使って一緒にまとめて、プロダクションサーバにデプロイします。 @@ -355,7 +389,14 @@ Node.js やバンドルツールのような特定の環境では、モジュー - 最先端の JavaScript 構文は、[Babel](https://babeljs.io/)] を使用して同様の機能を持つ古い構文に変換されます - 結果のファイルの minify (スペースの削除、変数を短い名前に置換するなど) -とはいえ、ネイティブモジュールも使用可能です。なので、ここでは Webpack は使用しません。もちろん後で設定することは可能です。 +バンドルツールを使用し、次にスクリプトが1つのファイル(またはいくつかのファイル)にバンドルされると、それらの `import/export` 文は特別なバンドル呼び出しに置き換えられます。したがって、結果として生じるビルドは `type=module` を必要としません。なので、それを通常のスクリプトに置くことができます。: + +```html + + +``` + +とはいえ、ネイティブモジュールも使用可能です。したがって、ここでは Webpack を使用しません: 後で構成できます。 ## サマリ @@ -374,4 +415,4 @@ Node.js やバンドルツールのような特定の環境では、モジュー プロダクション環境では多くの場合、パフォーマンスや他の理由で、モジュールを1つにまとめるために [Webpack](https://webpack.js.org) などのバンドラを使用します。 -次のチャプターでは、より多くのモジュールの例と、どのようにエクスポート/インポートされるかを見ていきます。 +次の章ではより多くのモジュールの例と、どのようにエクスポート/インポートされるかを見ていきます。 diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 41191645f1..d4968f6ea6 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -1,9 +1,8 @@ - # エクスポートとインポート -エクスポート(export)とインポート(import)ディレクティブは非常に用途が広いです。 +エクスポート(export)とインポート(import)ディレクティブにはいくつかの構文パターンがあります。 -前のチャプターでは、シンプルな使用例を見ました。ここではより多くの例を見ていきましょう。 +前章ではシンプルな使用例を見ました。ここではより多くの例を見ていきましょう。 ## 宣言の前の export @@ -27,9 +26,9 @@ ``` ````smart header="export class/function の後にセミコロンはありません" -クラスや関数の前の `export` はそれを [関数式](info:function-expressions-arrows) にはしないことに注意してください。エクスポートされていますが、依然として関数宣言です。 +クラスや関数の前の `export` はそれを [関数式](info:function-expressions) にはしないことに注意してください。エクスポートされていますが、依然として関数宣言です。 -ほとんどの JavaScript のスタイルガイドは文のあとにセミコロンを推奨しますが、関数とクラス宣言の後は推奨しません。 +ほとんどの JavaScript のスタイルガイドは、関数とクラス宣言の後のセミコロンは推奨しません。 そういうわけで、`export class` と `export function` の末尾にはセミコロンがありません。 @@ -43,11 +42,11 @@ export function sayHi(user) { ## 宣言とは別に export する -また、別に `export` を置くこともできます。 +別に `export` を記述することもできます。 ここでは、最初に宣言をし、その後エクスポートしています: -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -66,7 +65,7 @@ export {sayHi, sayBye}; // エクスポートされた変数のリスト ## import * -通常は、次のようにインポートするものの一覧を `import {...}` に置きます。: +通常は、次のようにインポートするものの一覧を波括弧 `import {...}` に置きます。: ```js // 📁 main.js @@ -78,7 +77,7 @@ sayHi('John'); // Hello, John! sayBye('John'); // Bye, John! ``` -しかし、リストが長い場合、`import * as ` を使用してオブジェクトとしてすべてをインポートすることができます。例: +しかし、import の数が多い場合、`import * as ` を使用してオブジェクトとしてすべてをインポートすることができます。例: ```js // 📁 main.js @@ -90,29 +89,18 @@ say.sayHi('John'); say.sayBye('John'); ``` -一見すると、"import everything" は非常にクールに思えます。が、そもそも、なぜインポートが必要なものを明示的にリストすると必要があるのでしょう? +一見すると、記述量も少なく、非常にクールに思えます。そもそも、なぜインポートが必要なものを明示的にリストする必要があるのでしょう? それにはいくつかの理由があります。 -1. 現代のビルドツール ([webpack](http://webpack.github.io) など) はモジュールをまとめ、読み込みを高速化するために最適化を行ったり、未使用なものを削除します。 +1. 何をインポートするかを明示的にリストすることで、より短い名前にできます: `say.sayHi()` の代わりに `sayHi()`。 +2. 明示的なインポートの一覧はコード構造の見通しをよりよくします。: 何がどこで使われているか。それはコードをサポートし、リファクタリングをより簡単にします。 - 我々のプロジェクトに多くの機能を持つサードパーティのライブラリ `lib.js` を追加したとしましょう。: - ```js - // 📁 lib.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` +```smart header="インポートし過ぎることを気にしないでください" +現代のビルドツール ([webpack](http://webpack.github.io) など) はモジュールをまとめ、読み込みを高速化するために最適化を行ったり、未使用なものを削除します。 - いま、我々のプロジェクトで実際に必要なのはそのうちの1つだけです。 - ```js - // 📁 main.js - import {sayHi} from './lib.js'; - ``` - ...その後、最適化処理は自動的にそれを検知し、バンドルされているコードから他の機能を完全に除去します。したがって、ビルドは小さくなります。それは "tree-shaking" と呼ばれています。 - -2. 何をインポートするかを明示的にリストすることは、より短い名前を与えます: `lib.sayHi()` の代わりに `sayHi()` -3. 明示的なインポートはコード構造の見通しをよりよくします。: 何がどこで使われているか。それはコードをサポートし、リファクタリングをより簡単にします。 +例えば、巨大なコードライブラリから `import * as library` を行い、いくつかのメソッドのみを使用する場合、未使用のものは最適化されたバンドルには [含まれません](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs)。 +``` ## import "as" @@ -148,30 +136,24 @@ export {sayHi as hi, sayBye as bye}; // 📁 main.js import * as say from './say.js'; -say.hi('John'); // Hello, John! -say.bye('John'); // Bye, John! +say.*!*hi*/!*('John'); // Hello, John! +say.*!*bye*/!*('John'); // Bye, John! ``` ## export default -これまでのところ、複数のものをインポート/エクスポートする方法を見てきました(必要に応じて "as" で別名で扱います)。 +実際には、主に 2 種類のモジュールがあります。 -実際には、モジュールは次のいずれかを含んでいます: -- ライブラリ、`lib.js` のような多く関数の集まり -- あるいは、`class User` のようなエンティティが `user.js` に記述されている場合。モジュール全体はこのクラスのみを持ちます。 +1. 上記の `say.js` のようなライブラリ、関数のパックを含むモジュール +2. 単一のエンティティを宣言するモジュール。例: モジュール `user.js` は `class User` のみをエクスポートします。 -ほとんどの場合、2番めのアプローチが好まれます。 +ほとんどの場合、すべての "もの" が自身のモジュールに存在するように、2 番目のアプローチが好まれます。 当然のことながら、モジュールシステムでは、すべてが独自のモジュールになるため、多くのファイルが必要となります。が、それはまったく問題ではありません。実際には、ファイルが良く名前付けされ、フォルダに構造化されていれば、コードのナビゲーションはとても簡単になります。 -合わせて、モジュールは、特別な `export default` 構文を提供し、"モジュール毎に1つのもの" のように見栄えを良くします。 +モジュールは、特別な `export default` ("デフォルトエクスポート") 構文を提供し、"モジュール毎に 1 つのもの" のように見栄えを良くします。 -これは以下の `export` と `import` 文を必要とします。: - -1. モジュールの "メインのエクスポート" の前に `export default` を置きます。 -2. 括弧なしで `import` を呼び出します。 - -例えば、`user.js` は `class User` をエクスポートします: +エクスポートするエンティティの前に `export default` を置きます: ```js // 📁 user.js @@ -182,7 +164,9 @@ export *!*default*/!* class User { // "default" を追加するだけ } ``` -...そして `main.js` はそれをインポートします: +ファイルごとに 1 つだけ `export default` が存在する場合があります。 + +...そして波括弧なしでそれをインポートします: ```js // 📁 main.js @@ -198,11 +182,9 @@ new User('John'); | `export class User {...}` | `export default class User {...}` | | `import {User} from ...` | `import User from ...`| -もちろん、1つのファイルには、1つしか "default" エクスポートはありません。 +技術的には、1 つのモジュールの中で、デフォルトと名前付きエクスポート両方をもたせることもできますが、実際には、通常は混在させません。モジュールは名前付きエクスポート、あるいはデフォルトいずれかを持ちます。 -1つのモジュールの中で、デフォルトと名前付きエクスポート両方をもたせることもできますが、実際には、通常は混在させません。モジュールは名前付きエクスポート、あるいはデフォルトいずれかを持ちます。 - -**もう1点注意すべきことは、名前付きエクスポートは名前を持たなければならない(当然ですが)のに対し、`export default` は匿名の場合があります。** +ファイルごとに最大で 1 つのデフォルトエクスポートがあり、エクスポートされたエンティティには名前がない場合があります。 例えば、これらはすべて有効なデフォルトエクスポートです: @@ -210,39 +192,45 @@ new User('John'); export default class { // クラス名なし constructor() { ... } } +``` -export default function(user) { // 関数名なし +```js +export default function (user) { // 関数名なし alert(`Hello, ${user}!`); } +``` +```js // 変数の作成なしで単一値のエクスポート export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; ``` これは問題ありません。なぜなら `export default` はファイル毎に1つのみだけだからです。そのため、`import` は何をインポートすべきか常に知っています。 -一方、名前付きエクスポートの名前を省略するとエラーになります。: + +`default` がない場合はエラーになります。: ```js export class { // Error! (非デフォルトエクスポートは名前が必要です) constructor() {} } -``` +``` -### "default" エイリアス +### "default" 名 -"default" という単語は、デフォルトエクスポートのための "エイリアス" の1つです。 +状況によっては、"default" というキーワードはデフォルトエクスポートを参照するために使用されます。 -例えば、すでに関数が宣言されている場合、それを `export default` する方法は次の通りです: +例えば、ある関数をその定義とは別にエクスポートする場合です: ```js function sayHi(user) { alert(`Hello, ${user}!`); } -export {sayHi as default}; // 関数の前に "export default" を追加する場合と同じです +// 関数の前に "export default" を追加する場合と同じです +export { sayHi as default }; ``` -あるいは、モジュール `user.js` が1つのメインの "デフォルト" のものと、いくつかの名前付きのものをエクスポートする場合(めったにありませんが起こりえます)、次のようになります: +あるいは、モジュール `user.js` が 1 つのメインの "デフォルト" のものと、いくつかの名前付きのものをエクスポートする場合(めったにありませんが起こりえます)、次のようになります: ```js // 📁 user.js @@ -257,7 +245,7 @@ export function sayHi(user) { } ``` -名前付きと一緒にデフォルトエクスポートをインポートする方法です: +これは名前付きと一緒にデフォルトエクスポートをインポートする方法です: ```js // 📁 main.js @@ -266,7 +254,7 @@ import {*!*default as User*/!*, sayHi} from './user.js'; new User('John'); ``` -あるいは、オブジェクトとして `*` をインポートする場合、`default` プロパティはまさにデフォルトエクスポートです: +そして、オブジェクトとして `*` ですべてをインポートする場合、`default` プロパティはまさにデフォルトエクスポートです: ```js // 📁 main.js @@ -276,26 +264,26 @@ let User = user.default; new User('John'); ``` - ### デフォルトエクスポートを使うべきですか? -デフォルトエクスポートを使用する際は注意が必要です。なぜなら、それらはメンテナンスがいくらか異なるためです。 - 名前付きエクスポートは明示的です。それらは正確にインポートするものを命名するので、そこから情報を得ることができます。これは良いことです。 また、名前付きエクスポートは、インポートするのに正確に正しい名前を使うことを強制します。 ```js import {User} from './user.js'; +// import {MyUser} は動作しません。名前は {User} でなければなりません。 ``` -デフォルトエクスポートの場合、独自の名前を作成する必要があります。: +...一方、デフォルトエクスポートの場合、インポート時に常に独自の名前を作成する必要があります。: ```js -import MyUser from './user.js'; // なにかをインポートします +import User from './user.js'; // 動作します +import MyUser from './user.js'; // 動作します +// なにでもインポートでき、それは動作します ``` -したがって、チームメンバは同じものに異なる名前を使用することができてしまうため、そこには誤用される余地があります。 +そのため、チームメンバは同じものに異なる名前を使用することができてしまうため、そこには誤用される余地があります。 通常、それを避け、コードの一貫性を保つため、インポートされた変数はファイル名に対応するべきであるという規則があります。例えば: @@ -306,27 +294,28 @@ import func from '/path/to/func.js'; ... ``` -別の解決策は、どこでも名前付きエクスポートを使用することです。たとえ単一のものだけがエクスポートされるとしても、`default` なしで名前付きでエクスポートします。 +それでも、チームによってはこれをデフォルトエクスポートの重大な欠点だと考えるかもしれません。この場合は常に名前付きエクスポートを使用することが好ましいです。たとえ単一のものだけがエクスポートされるとしても、`default` なしで名前付きでエクスポートします。 これは、再エクスポート(後述)を少しだけ簡単にもします。 ## 再エクスポート -"再エクスポート" 構文 `export ... from ...` を使うと、インポートをした直後にそれらを(場合により別の名前で)エクスポートすることができます。 +"再エクスポート" 構文 `export ... from ...` を使うと、インポートをした直後にそれらを(場合により別の名前で)エクスポートすることができます。: ```js -export {sayHi} from './say.js'; -export {default as User} from './user.js'; +export {sayHi} from './say.js'; // sayHi を再エクスポート + +export {default as User} from './user.js'; // default を再エクスポート ``` -要点は何で、なぜこれが必要なのでしょう?実用的なユースケースを見てみましょう。 +なぜこれが必要なのでしょう?実用的なユースケースを見てみましょう。 -"パッケージ" を書いていると想像してください。: 多くのモジュールを持ち、主に内部で必要とされ、いくつかの機能を外部にエクスポートされたフォルダ(NPMのようなツールはパッケージの公開と配布を可能にしますが、ここではそれは関係ありません。) +"パッケージ" を書いていると想像してください。: 多くのモジュールを含むフォルダで、一部の機能が外部にエクスポートされています(NPM のようなツールはこのようなパッケージの公開と配布を可能にしますが、それらを使用する必要はありません)。また、多くのモジュールは他のパッケージのモジュールで内部的に使用するための単なる "ヘルパー" です。 -ディレクトリ構造は次のようになります: +ファイル構造は次のようになります: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -337,40 +326,46 @@ auth/ ... ``` -単一のエントリポイントである "メインファイル" の `auth/index.js` を通してパッケージの機能を公開したいです。次にように: +単一のエントリポイント経由でパッケージの機能を公開したいです。 + +つまり、我々のパッケージを利用したい人は "メインファイル" `auth/index.js` からのみインポートします。 + +次のようになります: ```js import {login, logout} from 'auth/index.js' ``` +"メインファイル" `auth/index.js` はパッケージで提供したいすべての機能をエクスポートします。 + この考えは、部外者(我々のパッケージを使う開発者)は、その内部構造に干渉する必要はないということです。彼らは我々のパッケージフォルダの中のファイルを検索するべきではありません。我々は `auth/index.js` に必要なものだけをエクスポートし、残りの部分は詮索好きな目から隠れたままにします。 いま、実際にエクスポートされた機能はパッケージ内に散らかっているので、それらを集めて `auth/index.js` で "再エクスポート" します。: ```js // 📁 auth/index.js + +// login/logout をインポートし、すぐにエクスポートします import {login, logout} from './helpers.js'; export {login, logout}; +// User として default をインポートし、エクスポートします import User from './user.js'; export {User}; - -import Githib from './providers/github.js'; -export {Github}; ... ``` -"再エクスポート" はそれの短縮記法です: +これで、我々のパッケージの利用者は `import {login} from "auth/index.js"` ができます。 + +構文 `export ... from ...` はこのようなインポート - エクスポートの短縮記法です: ```js // 📁 auth/index.js +// login/logout の再エクスポート export {login, logout} from './helpers.js'; -// あるいは、すべてのヘルパーを再エクスポートするには、次のようにします: -// export * from './helpers.js'; +// User で default エクスポートを再エクスポート export {default as User} from './user.js'; - -export {default as Githib} from './providers/github.js'; ... ``` @@ -406,7 +401,7 @@ Import: - モジュールから名前付きエクスポート: - `import {x [as y], ...} from "mod"` -- デフォルトエクスポート: +- デフォルトエクスポート: - `import x from "mod"` - `import {default as x} from "mod"` - すべて: @@ -417,6 +412,7 @@ Import: import/export 文を他のコードの前後に置くことができ、どちらでも同じ結果になります。 なので、これも技術的には問題ありません: + ```js sayHi(); @@ -428,6 +424,7 @@ import {sayHi} from './say.js'; // ファイルの末尾で import **import/export 文は `{...}` の中では動作しないことに注意してください** このような条件付きのインポートは動作しません: + ```js if (something) { import {sayHi} from "./say.js"; // Error: import must be at top level diff --git a/1-js/13-modules/03-modules-dynamic-imports/article.md b/1-js/13-modules/03-modules-dynamic-imports/article.md index 5bb600d8cc..71dc3b67a3 100644 --- a/1-js/13-modules/03-modules-dynamic-imports/article.md +++ b/1-js/13-modules/03-modules-dynamic-imports/article.md @@ -1,11 +1,8 @@ - # Dynamic imports(ダイナミックインポート) -前のチャプターで説明したエクスポートとインポート文は "static(静的)" と呼ばれます。 - -それらは確かに静的であり、構文は非常に厳密です。 +前の章で説明したエクスポートとインポート文は "static(静的)" と呼ばれます。この構文は非常にシンプルかつ厳密です。 -静的な場合、まず `import` の任意のパラメータを動的に生成することはできません。 +まず `import` の任意のパラメータを動的に生成することはできません。 モジュールパスはプリミティブな文字列でなければならず、関数呼び出しもできません。これは動作しません。: @@ -25,15 +22,15 @@ if(...) { } ``` -これは、インポート/エクスポートはコード構造のバックボーンを提供することを目的としているためです。コード構造は分析することができ、モジュールを集め一緒にまとめることができ、未使用のエクスポートを除去する(tree-shaken)ことができるので、これは素晴らしいことです。これはすべてが固定されているがゆえに可能なことです。 +これは、`import/export` はコード構造のバックボーンを提供することを目的としているためです。コード構造は分析することができ、特別なツールを利用してモジュールを集め一つまとめることができ、未使用のエクスポートは除去されます(tree-shaken)。これはインポート/エクスポートがすべてが固定されているがゆえに可能なことです。 -しかし、どのようにしてモジュールを動的にオンデマンドでインポートするのでしょう? +では、どのようにしてモジュールを動的に、オンデマンドでインポートするのでしょう? -## import() 関数 +## import() 式 -`import(module)` 関数はどこからでも呼び出すことができます。これはモジュールオブジェクトになる promise を返します。 +`import(module)` 式は、モジュールを読み込み、モジュールがもつすべてのエクスポートを含むモジュールオブジェクトになる promise を返します。 -使用パターンは次のようになります: +コードの任意の場所で動的に利用できます。以下は例です: ```js run let modulePath = prompt("Module path?"); @@ -45,10 +42,57 @@ import(modulePath) あるいは、async function 内であれば `let module = await import(modulePath)` とすることができます。 -このようになります: +例えば、次のようなモジュール `say.js` があるとします: + +```js +// 📁 say.js +export function hi() { + alert(`Hello`); +} + +export function bye() { + alert(`Bye`); +} +``` + +...動的インポートは次のようにできます: + +```js +let {hi, bye} = await import('./say.js'); + +hi(); +bye(); +``` + +また、`say.js` が default export を持っている場合は次のようになります: + +```js +// 📁 say.js +export default function() { + alert("Module loaded (export default)!"); +} +``` + +...そこへアクセスするには、モジュールオブジェクトの `default` プロパティを使用します。: + +```js +let obj = await import('./say.js'); +let say = obj.default; +// あるいは、1行で: let {default: say} = await import('./say.js'); + +say(); +``` + +ここに完全な例があります: [codetabs src="say" current="index.html"] -したがって、ダイナミックインポートは非常に簡単に使用できます。 +```smart +ダイナミックインポートは通常のスクリプトで動作するので、`script type="module"` は必要ありません。 +``` + +```smart +`import()` は一見すると関数呼び出しに見えますが、たまたま括弧を使用している特別な構文です(`super()` と同様です)。 -また、ダイナミックインポートは通常のスクリプトで動作するので、`script type="module"` は必要ありません。 +したがって、変数に `import` をコピーしたり、`call/apply` を使用することはできません。関数ではないからです。 +``` diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index de4cc5c724..712b7811d1 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -2,7 +2,9 @@ `Proxy` オブジェクトは別のオブジェクトをラップし、プロパティやその他の読み取り/書き込みなどの操作をインターセプトします。必要に応じてそれらを独自に処理したり、オブジェクトが透過的にそれらを処理できるようにします。 -Proxy は多くのライブラリや一部のブラウザフレームワークで使われています。このチャプターでは、多くの実践的なアプリケーションを紹介します。 +Proxy は多くのライブラリや一部のブラウザフレームワークで使われています。この章では、多くの実践的なアプリケーションを紹介します。 + +## Proxy 構文: @@ -10,8 +12,8 @@ Proxy は多くのライブラリや一部のブラウザフレームワーク let proxy = new Proxy(target, handler) ``` -- `target` -- ラップするオブジェクトです。関数を含め何でもOKです。 -- `handler` -- プロキシ設定: 操作をインターセプトするメソッド "traps" をもつオブジェクトです。例: `get` トラップは `target` のプロパティの読み取り用、`set` トラップは、`target` へのプロパティ書き込み用、など。 +- `target` -- ラップするオブジェクトです。関数含め何でもOKです。 +- `handler` -- プロキシ設定: 操作をインターセプトするメソッドである "トラップ" をもつオブジェクトです。例: `get` トラップは `target` のプロパティの読み取り用、`set` トラップは、`target` へのプロパティ書き込み用、など。 `proxy` の操作では、`handler` に対応するトラップがある場合はそれが実行されます。それ以外の場合は、操作は `target` で実行されます。 @@ -29,19 +31,19 @@ alert(proxy.test); // 5, proxy からの読み取ることができます (2) for(let key in proxy) alert(key); // test, イテレーションも機能します (3) ``` -traps がないので、`proxy` 上のすべての操作は `target` に転送されます。 +トラップがないので、`proxy` 上のすべての操作は `target` に転送されます。 1. 書き込み操作 `proxy.test=` は `target` に値を設定します。 2. 読み込み操作 `proxy.test` は `target` からの値を返します。 3. `proxy` のイテレートは、`target` からの値を返します。 -ご覧の通り、traps がない場合は `proxy` は `target` に対する透過的なラッパーです。 +ご覧の通り、トラップがない場合は `proxy` は `target` に対する透過的なラッパーです。 ![](proxy.svg) `Proxy` は特別な "エキゾチックオブジェクト(exotic object)" です。`Proxy` は独自のプロパティは持っていません。空の `handler` の場合は、透過的に `target` へ操作を転送します。 -さらに機能を有効にするために、traps を追加しましょう。 +さらに機能を有効にするために、トラップを追加しましょう。 これによって、何がインターセプトできるでしょう? @@ -49,7 +51,7 @@ traps がないので、`proxy` 上のすべての操作は `target` に転送 プロキシのトラップはこれらのメソッドの呼び出しをインターセプトします。これらのメソッドは[Proxy specification](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots) 及び以下の表にリストされています。 -内部メソッドと trap(操作をインターセプトするために `new Proxy` の `handler` パラメータに追加するメソッド名)の対応表です。 +このテーブルに、すべての内部ソッドに対するトラップがあります: 操作をインターセプトするために `new Proxy` の `handler` パラメータに追加できるメソッド名です: | 内部メソッド | ハンドラメソッド | いつ発生するか | |-----------------|----------------|-------------| @@ -59,16 +61,16 @@ traps がないので、`proxy` 上のすべての操作は `target` に転送 | `[[Delete]]` | `deleteProperty` | `delete` 演算子 | | `[[Call]]` | `apply` | 関数呼び出し | | `[[Construct]]` | `construct` | `new` 演算子 | -| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | -| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | -| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) | -| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) | -| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) | -| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | -| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` | +| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | +| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | +| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) | +| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) | +| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) | +| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | +| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` | ```warn header="Invariants" -JavaScript にはいくつかの不変条件(内部メソッドと traps によって満たされるべき条件)があります。 +JavaScript にはいくつかの不変条件(内部メソッドと トラップによって満たされるべき条件)があります。 そのほとんどは戻り値に関してです: - `[[Set]]` は値が正常に書き込まれた場合には `true` を、そうでなければ `false` を返す必要があります。 @@ -80,8 +82,7 @@ JavaScript にはいくつかの不変条件(内部メソッドと traps によ traps はこれらの操作をインターセプトできますが、これらのルールには従う必要があります。 -不変条件は、言語機能の正しさと一貫した動作を保証するものです。完全な不変条件のリストは [仕様] -(https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots)にありますが、変なことをしない限りは違反することはないでしょう。 +不変条件は、言語機能の正しさと一貫した動作を保証するものです。完全な不変条件のリストは [仕様](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots)にありますが、変なことをしない限りは違反することはないでしょう。 ``` 実際の例でそれがどのように動作するのかを見てみましょう。 @@ -90,7 +91,7 @@ traps はこれらの操作をインターセプトできますが、これら 最も一般的なトラップ(traps)はプロパティの読み書きです。 -読み取りをインターセプトするには、`handler` に `get(target, property, receiver)` が必要です。 +読み取りをインターセプトするには、`handler` に `get(target, property, receiver)` が必要です。 これはプロパティが読み取られたとき、以下の引数で実行されます。: @@ -293,8 +294,7 @@ user = new Proxy(user, { alert( Object.keys(user) ); // ``` -なぜでしょう?理由は簡単です。: `Object.keys` は `enumerable` フラグを持つプロパティだけを返すからです。それを確かめるため、すべてのメソッドに対し内部メソッド `[[GetOwnProperty]]` を呼び出し, -[ディスクリプタ](info:property-descriptors) を取得します。すると、ここではプロパティがないので、そのディスクリプタは空であり、`enumerable` フラグがありません。そのため、スキップされます。 +なぜでしょう?理由は簡単です。: `Object.keys` は `enumerable` フラグを持つプロパティだけを返すからです。それを確かめるため、すべてのメソッドに対し内部メソッド `[[GetOwnProperty]]` を呼び出し,[ディスクリプタ](info:property-descriptors) を取得します。すると、ここではプロパティがないので、そのディスクリプタは空であり、`enumerable` フラグがありません。そのため、スキップされます。 `Object.keys` がプロパティを返すには、`enumerable` 付きでオブジェクトに存在するか、`[[GetOwnProperty]]`(トラップは `getOwnPropertyDescriptor`)の呼び出しをインターセプトし、`enumerable: true` を持つディスクリプタを返します。 @@ -436,6 +436,7 @@ user = { } ``` + `user.checkPassword()` の呼び出しはプロキシされた `user` を `this` (ドットの前のオブジェクトが `this` になります)として取得するため、`this._password` へのアクセスを試みると `get` トラップが機能(これはあらゆるプロパティ読み取りでトリガーされます)し、エラーをスローします。 そのため、`(*)` の通りオブジェクトメソッドのコンテキストを元のオブジェクトである `target` でバインドします。以降、その呼び出しでは `this` としてトラップのない `target` を使用します。 @@ -776,6 +777,7 @@ get(target, prop, receiver) { } ``` + `Reflect` 呼び出しはトラップとまったく同じ名前が付けられており、同じ引数を受け付けます。特別にそのように設計されました。 したがって、`return Reflect...` は安全かつ考えるまでもない分かりやすい手段で操作を転送することができます。 @@ -961,9 +963,13 @@ revoke(); alert(proxy.data); // Error ``` -`revoke()` 呼び出しは、プロキシからターゲットオブジェクトへのすべての内部参照を削除します。これにより繋がりがなくなります。ターゲットオブジェクトはその後ガベージコレクトできます。 +`revoke()` 呼び出しは、プロキシからターゲットオブジェクトへのすべての内部参照を削除します。これにより繋がりがなくなります。 -また、プロキシオブジェクトを簡単に見つけられるよう、`WeakMap` に `revoke` を保持することもできます。: +初期状態で、`revoke` は `proxy` とは別なので、現在のスコープに `revoke` を残したまま、`proxy` を渡すことが可能です。 + +`proxy.revoke = revoke` と設定することで、proxy に `revoke` メソッドをバインドすることもできます。 + +別の選択肢は、`WeakMap` を作成し、キーとして `proxy` を、値として対応する `revoke` をもたせることです。これで、簡単に proxy に対する `revoke` を見つけることができます。 ```js run *!* @@ -985,8 +991,6 @@ revoke(); alert(proxy.data); // Error (revoked) ``` -このようなアプローチの利点は `revoke` を持って回る必要がないことです。必要なときに `proxy` を使って map から取得できます。 - ここで `Map` の代わりに `WeakMap` を使用しているのは、ガベージコレクションをブロックしないようにするためです。proxy オブジェクトが "到達不可能" になった(e.g それを参照する変数がなくなった)場合、`WeakMap` を利用すると、不要になった `revoke` を一緒にメモリ上から削除することができます。 ## リファレンス diff --git a/1-js/99-js-misc/02-eval/article.md b/1-js/99-js-misc/02-eval/article.md index 0d959ccfe5..33aba46f6a 100644 --- a/1-js/99-js-misc/02-eval/article.md +++ b/1-js/99-js-misc/02-eval/article.md @@ -15,7 +15,7 @@ let code = 'alert("Hello")'; eval(code); // Hello ``` -コードの文字列は長かったり、改行や関数定義、変数を含んでいる可能性があります。 +コードの文字列は長かったり、改行や関数定義、変数を含んでいる可能性があります。 `eval` の結果は最後の文の結果です。 diff --git a/1-js/99-js-misc/03-currying-partials/1-ask-currying/task.md b/1-js/99-js-misc/03-currying-partials/1-ask-currying/task.md index 8149792ef1..e3ccbba43c 100644 --- a/1-js/99-js-misc/03-currying-partials/1-ask-currying/task.md +++ b/1-js/99-js-misc/03-currying-partials/1-ask-currying/task.md @@ -4,7 +4,7 @@ importance: 5 # ログイン用の部分的なアプリケーション -このタスクは 少しより複雑なバリアントです。 +このタスクは 少しより複雑なバリアントです。 `user` オブジェクトが修正されました。今、2つの関数 `loginOk/loginFail` の代わりに、単一の関数 `user.login(true/false)` があります。 @@ -31,4 +31,3 @@ askPassword(?, ?); // ? ``` 変更はハイライトされた箇所の修正だけにしてください。 - diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md index ca3fec89c6..38debcf329 100644 --- a/1-js/99-js-misc/03-currying-partials/article.md +++ b/1-js/99-js-misc/03-currying-partials/article.md @@ -3,220 +3,114 @@ libs: --- -# カリー化と部分適用 +# カリー化 -今まで、`this` をバインドすることについて話していました。 さあ、もう一歩を進めましょう。 +[カリー化](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96)は、関数を扱う際の上級テクニックです。これはJavaScriptだけでなく、他の言語でも使用されます。 -私たちは、`this` だけでなく、引数もバインドすることができます。それはめったにされませんが、便利なときがあります。 +カリー化は`f(a, b, c)`として呼び出せる関数を`f(a)(b)(c)`のように呼び出せるようにする、関数の変形のことを指します。 -[cut] +カリー化は関数を呼び出しません。ただ変形するだけです。 -`bind` の完全な構文です: +まず例を確認して、何について話しているかもっと理解して、それから実用的な応用例を見てみましょう。 -```js -let bound = func.bind(context, arg1, arg2, ...); -``` - -これは、コンテキスト( `this` として)と、関数の開始引数をバインドすることができます。 - -例えば、乗算関数 `mul(a, b)` を考えます: - -```js -function mul(a, b) { - return a * b; -} -``` - -これをベースとした関数 `double` を作るために、`bind` を使ってみましょう。: - -```js run -*!* -let double = mul.bind(null, 2); -*/!* - -alert( double(3) ); // = mul(2, 3) = 6 -alert( double(4) ); // = mul(2, 4) = 8 -alert( double(5) ); // = mul(2, 5) = 10 -``` - -`mul.bind(null, 2)` を呼び出すと、`mul` を呼び出す新しい関数 `double` が作成されます。それは、`null` がコンテキストとして、`2` が最初の引数として固定されます。さらに、その他の引数は "そのまま" 渡されます。 - -これは [関数への部分的な適用(partial function application)](https://en.wikipedia.org/wiki/Partial_application)と呼ばれます -- 既存の関数の一部のパラメータを変更することで新しい関数を作ります。 - -ここで、例では実際には `this` を使っていないことに注意してください。しかし `bind` はそれを必要とするため、`null` のような何かを指定する必要があります。 - -下のコードの関数 `triple` は値を3倍にします: - -```js run -*!* -let triple = mul.bind(null, 3); -*/!* - -alert( triple(3) ); // = mul(3, 3) = 9 -alert( triple(4) ); // = mul(3, 4) = 12 -alert( triple(5) ); // = mul(3, 5) = 15 -``` - -なぜ、通常部分的な関数を作るのでしょうか? - -ここでの我々のメリットは、分かりやすい名前(`double`, `triple`)で独立した関数を作れたことです。私たちはそれを使うことができ、毎回最初の引数を書く必要がありません。なぜなら、`bind` で固定されているからです。 - -別のケースでは、非常に汎用的な関数を持っており、利便性のために汎用性を減らしたい時に部分適用は役立ちます。 - -例えば、関数 `send(from, to, text)` を考えます。次に、`user` オブジェクトの内側で、その部分的なバリアントを使いたいかもしれません。: 現在のユーザから送信を行う `sendTo(to, text)` 関数など。 - - -## コンテキストなしの部分適用 - -仮に、いくつかの引数を修正したいが、`this` をバインドしない場合はどうなるでしょう? - -ネイティブの `bind` ではそれは許可されていません。単にコンテキストを省略し引数にジャンプすることは出来ません。 - -ただ、幸いにも引数だけをバインドする `partial` 関数は簡単に実装することが出来ます。 - -このようになります: +2引数を取る関数`f`をカリー化する、ヘルパー関数の`curry(f)`を作成します。言い換えると、2引数の`f(a, b)`に対して`curry(f)`は、関数を`f(a)(b)`のように呼び出せるように変形します。 ```js run *!* -function partial(func, ...argsBound) { - return function(...args) { // (*) - return func.call(this, ...argsBound, ...args); - } -} -*/!* - -// 使い方: -let user = { - firstName: "John", - say(time, phrase) { - alert(`[${time}] ${this.firstName}: ${phrase}!`); - } -}; - -// 最初の引数を固定して何かを表示する部分的なメソッドを追加する -user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); - -user.sayNow("Hello"); -// このようになります: -// [10:00] Hello, John! -``` - -`partial(func[, arg1, arg2...])` の呼び出しの結果は次のように `func` を呼び出すラッパー `(*)` です。: -- `this` はそれが取得したものと同じです(`user.sayNow` の場合 `user` です)。 -- 次に、`...argsBound` を与えます -- `partial` 呼び出しからの引数(`"10:00"`)です。 -- 次に、`...args` です -- ラッパーへ与えられた引数(`"Hello"`) です。 - -なのでスプレッド演算子を使えば簡単ですね。 - -また、lodashライブラリの [_.partial](https://lodash.com/docs#partial) 実装も用意されています。 - -## カリー化(Currying) - -"カリー化" と呼ばれる別のものと、上で言及された関数の部分適用を混同する人もいます。それらはここで言及しておくべき関数を扱う別の興味深いテクニックです。 - -[Currying](https://en.wikipedia.org/wiki/Currying) は `f(a, b, c)` と呼び出し可能なものを `f(a)(b)(c)` として呼び出しできるように変換します。 - -2変数関数に対するカリー化を行う関数 `curry` を作ってみましょう。つまり、`f(a, b)` を `f(a)(b)` に変換します。: - -```js run -*!* -function curry(func) { +function curry(f) { // curry(f)によって変形が施されます return function(a) { return function(b) { - return func(a, b); + return f(a, b); }; }; } */!* - -// 使い方 +// 使用法 function sum(a, b) { return a + b; } - -let carriedSum = curry(sum); - -alert( carriedSum(1)(2) ); // 3 +let curriedSum = curry(sum); +alert( curriedSum(1)(2) ); // 3 ``` -上でわかるように、実装はラッパーの連続です。 +ご覧の通り、実装は単純です:これはただの2つのラッパです。 -- `curry(func)` の結果はラッパー `function(a)` です。 -- `sum(1)` のように呼ばれるとき、引数はレキシカル環境に保存され、新しいラッパー `function(b)` が返却されます。 -- そして、最終的に `sum(1)(2)` は `2` で `function(b)` を呼び、それは元の複数引数を取る `sum` を呼びます。 +- `curry(func)`は`function(a)`のラッパです。 +- これが`curriedSum(1)`のように呼ばれると、引数はレキシカル環境に保存され、新しいラッパである`function(b)`が返されます。 +- そしてこのラッパが`2`を引数として呼ばれると、元の`sum`への呼び出しを返します。 -lodash の [_.curry](https://lodash.com/docs#curry) のようなカリー化のより高度な実装は、より洗練された処理を行います。それらの関数は全ての引数が提供された場合には関数が正常に呼び出されるようなラッパーを返し、*そうでない場合* には、部分適用を返します。 +例えばloadashライブラリの[_.curry](https://lodash.com/docs#curry)のような、もっと高度なカリー化の実装では、関数を通常通り呼んだり部分的に呼んだりすることが認められるようなラッパが返されます。 -```js -function curry(f) { - return function(..args) { - // もし args.length == f.length の場合(f と同じ引数がある場合), - // 呼び出しを f へ渡す - // それ以外は argsを最初の引数として固定する部分関数を返す - }; +```js run +function sum(a, b) { + return a + b; } +let curriedSum = _.curry(sum); // loadashライブラリの _.curry を使用します +alert( curriedSum(1, 2) ); // 3, 通常通り呼び出します +alert( curriedSum(1)(2) ); // 3, 部分的に呼び出します ``` -## カリー化? 何のために? +## カリー化?なんのために? -高度なカリー化を使用すると、簡単に関数を通常呼び出し可能にしつつ、部分適用をすることができます。このメリットを理解するために、価値のある実例を見る必要があります。 +利便性を理解するためには、価値のある実例が必要です。 -例えば、情報を整形して出力するロギング関数 `log(date, importance, message)` を持っているとします。実際のプロジェクトでは、このような関数には、ネットワーク経由での送信やフィルタリングなど、他にも多くの便利な機能があります。 +例えば、情報を整形して出力する`log(date, importance, message)`というロギング関数があります。実際のプロジェクトでは、そのような関数はネットワーク越しにログを送信するなどの多様で便利な機能を持っていますが、ここでは単純に`alert`を使用します。 -```js +```js run function log(date, importance, message) { alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); } ``` -では、カリー化してみましょう! +これをカリー化しましょう! ```js log = _.curry(log); ``` -この処理の後でも `log` は通常の方法で動きます: +そうすると、`log`は通常通り機能します: ```js -log(new Date(), "DEBUG", "some debug"); +log(new Date(), "DEBUG", "some debug"); // log(a, b, c) ``` -...しかしカリー化された形式でも呼び出すことができます: +……そしてカリー化後の形式でも機能します: ```js log(new Date())("DEBUG")("some debug"); // log(a)(b)(c) ``` -今日のログのための便利な関数を取得してみましょう: +すると、現在のログ用の便利な関数を簡単に作成できます: ```js -// todayLog は最初の引数が固定された log の一部になります -let todayLog = log(new Date()); +// logNowはlogの部分関数で、第1引数が固定されています +let logNow = log(new Date()); -// 使ってみます -todayLog("INFO", "message"); // [HH:mm] INFO message +// それを使用します +logNow("INFO", "message"); // [HH:mm] INFO message ``` -また、これで今日のデバッグメッセージのための便利関数ができます: +これで、`logNow`は第1引数が固定された`log`となりました。言い換えれば「部分適用された関数」あるいは短くすると「部分適用」となります。 -```js -let todayDebug = todayLog("DEBUG"); +さらに進んで、現在のデバッグログ用の便利な関数を作成することもできます: -todayDebug("message"); // [HH:mm] DEBUG message +```js +let debugNow = logNow("DEBUG"); +debugNow("message"); // [HH:mm] DEBUG message ``` -なので: -1. カリー化をしても何も失いませんでした。: `log` は以前のように呼び出し可能です。 -2. 色んなケースに応じて便利な部分適用した関数を生成する事ができました。 +つまり: +1. カリー化しても何も失いません:`log`を通常通り呼び出すこともできます。 +2. 今日のログ用のような部分適用された関数を簡単に生成することができます。 +## 高度なカリー化の実装 -## 高度なカリー実装 +もし詳細に触れたいのならば、こちらが上記でも使用できた、複数の引数を取る関数用の「高度な」カリー化の実装です。 -ここでは、上記で使用できる "高度な" カリー実装を示します。 +かなり短いです: -```js run +```js function curry(func) { - return function curried(...args) { if (args.length >= func.length) { return func.apply(this, args); @@ -226,74 +120,61 @@ function curry(func) { } } }; - } +``` +使用例: + +```js function sum(a, b, c) { return a + b + c; } - let curriedSum = curry(sum); - -// 通常通り呼ぶことも出来ます -alert( curriedSum(1, 2, 3) ); // 6 - -// curried(1)で部分を取得し、他の2つの引数で呼び出す -alert( curriedSum(1)(2,3) ); // 6 - -// 完全にカリー化された呼び出し -alert( curriedSum(1)(2)(3) ); // 6 +alert( curriedSum(1, 2, 3) ); // 6, 普通に呼び出すことも可能です +alert( curriedSum(1)(2,3) ); // 6, 最初の引数をカリー化しています +alert( curriedSum(1)(2)(3) ); // 6, 完全なカリー化です ``` -新しい `curry` は複雑に見えますが、実際には理解するのはとても簡単です。 +新しい`curry`は複雑なように見えますが、実際は簡単に理解できます。 -`curry(func)` の結果は、このように `curried` のラッパーです。: +`curry(func)`呼び出しの結果は、ラップされた以下のような`curried`関数です。 ```js -// func is the function to transform +// funcは変形対象の関数です function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { - return function pass(...args2) { // (2) + return function(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } }; ``` -これを実行すると、2つの分岐があります。: - -1. 渡された `args` の数と、元の関数で定義されている引数の数が同じ (`func.length`) かより多い場合、単にそれを呼び出し `func` に渡します。 -2. 部分適用を得る: そうでない場合は、`func` はまだ呼ばれません。代わりに別のラッパー `pass` が返却されます。これは `curried` を再度適用して新しい引数と一緒に前の引数を提供します。その後、新しい呼び出しでは、新しい部分適用(引数が不十分な場合)か、最終的に結果が得られます。 - -例えば、`sum(a, b, c)` のケースで何が起きるのかを見てみましょう。3つの引数があるので、`sum.length = 3` です。 +これを実行すると、2つの`if`の分岐に出会います: -`curried(1)(2)(3)` に対しては次のようになります: +1. もし渡された`args`の数が、元の関数で定義されている引数の数以上であれば(`func.length`)、単に`func.apply`を使用して関数を呼び出します。 +2. そうでなければ、部分適用します:`func`を単には呼び出しません。その代わりに、別のラッパが返されます。そのラッパは新しい引数とともに、以前与えられた引数を`curried`に再び適用します。 -1. 最初の呼び出し `curried(1)` はそのレキシカル環境に `1` を覚え、ラッパー `pass` を返します。 -2. ラッパー `pass` は `(2)` で呼び出されます: それは前の引数(`1`)を取り、渡された `(2)` と連結し `curried(1, 2)` を呼び出します。引数の数としては、以前 3 より少ないので、`curry` は `pass` を返します。 -3. ラッパー `pass` は再び `(3)` で呼ばれ、次は `pass(3)` が以前の引数 (`1`, `2`) を取り、`3` を追加して `curried(1, 2, 3)` を呼び出します。 -- ついに引数が `3` となったので、それらは元の関数に渡されます。 - -もしまだ不明瞭であれば、心の中、あるいは紙に呼び出しシーケンスをトレースしてみると良いです。 +それから繰り返しになりますが、`curried`を呼び出せば、新しい部分適用(引数の数が十分ではない場合)か、最終的には結果が得られます。 ```smart header="固定長の関数のみ" -カリー化では、関数が固定数の引数を持つ必要があります。 +カリー化では、関数の引数の数は固定されている必要があります。 + +`f(...args)`のような、残りのパラメータを使用するような関数は、このようにはカリー化できません。 ``` ```smart header="カリー化よりもさらに" -定義上、カリー化は `sum(a, b, c)` を `sum(a)(b)(c)` に変換するべきです。 +定義上、カリー化は sum(a, b, c) を sum(a)(b)(c) に変換するべきです。 しかし、JavaScriptでのカリー化のほとんどの実装は説明されているように高度であり、複数引数のバリアントでも関数が呼び出し可能となっています。 ``` -## サマリ +## サマリ -- 私たちが既存の関数のいくつかの引数を修正するとき、結果となる(汎用さは減る)関数は、*部分適用* と呼ばれます。部分適用を得るために `bind` を使うことができますが、他の方法でも可能です。 - 同じ引数を何度も繰り返し指定したくないとき、部分適用は便利です。それは、私たちが `send(from, to)` 関数を持っていて, - `from` が常に同じになるような場合です。部分適用を得て処理を続けることができます。 -- *カリー化* は `f(a,b,c)` を `f(a)(b)(c)` として呼び出し可能に変換します。JavaScriptの実装は、通常の形で呼び出し可能な関数を維持し、かつ引数が不足している場合には部分適用を返します。 +*カリー化*は`f(a,b,c)`を`f(a)(b)(c)`として呼び出し可能に変換します。JavaScriptの実装は、通常の形で呼び出し可能な関数を維持し、かつ引数が不足している場合には部分適用を返します。 - 簡単な部分適用がほしいときにカリー化は素晴らしいです。ロギングの例で見てきたように、カリー化後の汎用的な関数 `log(date, importance, message)` は、1つの引数 `log(date)` または2つの引数 `log(date, importance)` で呼び出された時には部分適用を返します。 +簡単な部分適用がほしいときにカリー化は素晴らしいです。ロギングの例で見てきたように、カリー化後の3引数を取る汎用的な関数`log(date, importance, message)`は、(`log(date)`のような)1つの引数または(`log(date, importance)`のような)2つの引数で呼び出された時には部分適用を返します。 diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index f558c83b8d..3bfab8aa40 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -2,7 +2,7 @@ 当初、JavaScript言語は web ブラウザのために作られました。それ以降、言語は進化し、多くの用途やプラットフォームをもつ言語になりました。 -プラットフォームは、ブラウザ、Webサーバ、あるいは別の *ホスト*、JavaScirpt が実行可能であれば "スマートな" コーヒーマシンかもしれません。これらはプラットフォーム固有の機能を提供します。JavaScript スペックではこれを *ホスト環境* と呼んでいます。 +プラットフォームは、ブラウザ、Webサーバ、あるいは別の *ホスト*、JavaScript が実行可能であれば "スマートな" コーヒーマシンかもしれません。これらはプラットフォーム固有の機能を提供します。JavaScript スペックではこれを *ホスト環境* と呼んでいます。 ホスト環境は言語のコアに加えて、独自のオブジェクトや機能を提供します。Webブラウザであれば Webページを制御する手段を、Node.js であればサーバサイドの機能などです。 diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index c05b77060c..a0c32ae971 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -22,7 +22,7 @@ document.body.style.background = 'red'; // 背景を赤に変更 setTimeout(() => document.body.style.background = '', 3000); // 戻します ``` -ここでは、`document.body` の背景色を変更するのに、`style.background` を使用していますが、以下のようにたに多くのプロパティがあります: +ここでは、`document.body` の背景色を変更するのに、`style.background` を使用していますが、以下のように他に多くのプロパティがあります: - `innerHTML` -- ノードの HTML コンテンツ - `offsetWidth` -- ノードの幅(ピクセル) @@ -242,7 +242,7 @@ DOMを見ることができます、要素をクリックしその詳細をみ ## コンソールでのインタラクション -DOM を調べるにつれて、DOMをに対して JavaScript を適用したいことがあります。例えば、ノードを取得して修正するコードを実行し、その結果を確認る、です。 ここでは、Elementsタブとコンソールの間を移動する tips をいくつか紹介します。 +DOM を調べるにつれて、DOM に対して JavaScript を適用したいことがあります。例えば、ノードを取得して修正するコードを実行し、その結果を確認する、です。 ここでは、Elementsタブとコンソールの間を移動する tips をいくつか紹介します。 まず最初に: @@ -259,7 +259,7 @@ DOM を調べるにつれて、DOMをに対して JavaScript を適用したい 逆もあります。DOMノードを参照している変数がある場合、`inspect(node)` コマンドを実行すると、Elements ペインで表示させることができます。 -もしくは単にそれをコンソールに出力し、"その場" で調べることができます。したの `document.body` のように: +もしくは単にそれをコンソールに出力し、"その場" で調べることができます。下の `document.body` のように: ![](domconsole1.png) @@ -279,4 +279,4 @@ HTML/XML ドキュメントはブラウザ内では DOM ツリーとして表現 ここでは、基本と、最もよく使われている重要なアクションについて説明しました。 Chrome開発者ツールに関する詳細なドキュメントは、 にあります。 ツールを学ぶ最も良い方法は、ここをクリックしてメニューを読むことです: ほとんどのオプションは明白です。 -DOMノードには、ノード間の移動やノードの変更、ページの遷移と言ったことを可能とするるプロパティとメソッドがあります。 次の章でそれらを見ていきましょう。 +DOMノードには、ノード間の移動やノードの変更、ページの遷移と言ったことを可能とするプロパティとメソッドがあります。 次の章でそれらを見ていきましょう。 diff --git a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md index 7adfca1260..3c951bcccc 100644 --- a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md +++ b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md @@ -6,7 +6,7 @@ importance: 5 対角線上のすべての表のセルを赤でペイントするコードを書いてください。 -`` からすべての体格 `
` を取得し、コードを使ってペイントする必要があります。: +`` からすべての対角 `
` を取得し、コードを使ってペイントする必要があります。: ```js // td はテーブルセルへの参照 diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index 0f35f57050..0757e1a8e2 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -19,7 +19,7 @@ DOM 上のすべての操作は `document` オブジェクトから始まりま ## トップ: documentElement と body -最上位のツリーノードは `documet` プロパティとして直接利用可能です: +最上位のツリーノードは `document` プロパティとして直接利用可能です: `` = `document.documentElement` : 最上位のドキュメントノードは `document.documentElement` で、`` タグの DOM ノードです。 @@ -68,7 +68,7 @@ DOM では、`null` 値は "存在しない" もしくは "このようなノー これから使う2つの用語があります。: - **子ノード (または子)** -- 直接の子要素です。つまり、与えられた要素にネストされています。例えば `` と `` は `` 要素の子です。 -- **子孫** -- 子要素、子要素など、指定された要素にネストされたすべての要素です。 +- **子孫** -- 子要素、さらにその子要素など、指定された要素にネストされたすべての要素です。 例えば、ここで `` は子 `
` と `
    ` (といくつかの空のテキストノード)を持ちます。: diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index d7ba4f0ed2..730fdea8e8 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -6,4 +6,4 @@ この問題はブラウザツールを使ってDOMを調べると簡単に答えることができます。`` の前に `"aaa"` があります。 -HHTML標準では、悪いHTMLを処理する方法を詳細に指定しています。このようなブラウザの動作は正しいです。 +HTML標準では、悪いHTMLを処理する方法を詳細に指定しています。このようなブラウザの動作は正しいです。 diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 7e424bbffa..3aebe1e860 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -518,9 +518,9 @@ ul.append(...getListContent()); // append + "..." operator = friends! 利点もあります。技術的には、ブラウザがまだ HTML を読んでいる(パースしている)間に `document.write` が呼び出され、何かを追加すると、ブラウザは HTML テキストに最初からそこにあったかのように処理します。 -これは *DOMの修正がないため* 驚くほど速く動作します。DOMがまだビルドされていない間、それは直接ページテキストに書き込み、ブラウザは生成時にそれをDOMを挿入します。 +これは *DOMの修正がないため* 驚くほど速く動作します。DOMがまだビルドされていない間、それは直接ページテキストに書き込み、ブラウザは生成時にそれをDOMに挿入します。 -なので、HTMLの動的に多くのテキストを追加する必要があり、またページをロードするフェーズであること、速度を考慮する必要がある場合には役立ちます。ですが、実際にはこれらの要件が一緒に来ることは殆どありません。通常、このメソッドを見るときは、単に古いスクリプトだからと言う理由です。 +なので、HTMLに多くのテキストを動的に追加する必要があり、またページをロードするフェーズであること、速度を考慮する必要がある場合には役立ちます。ですが、実際にはこれらの要件が一緒に来ることは殆どありません。通常、このメソッドを見るときは、単に古いスクリプトだからと言う理由です。 ## サマリ diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index df31f1f221..de4605c1cb 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -190,7 +190,7 @@ setTimeout(() => document.body.style.removeProperty('background'), 1000); // 1 ## 算出スタイル: getComputedStyle -スタイルを変更するのは簡単です。しかしどうやってそれを *参照* ますか? +スタイルを変更するのは簡単です。しかしどうやってそれを *参照* しますか? 例えば、要素のサイズ、マージン、色が知りたいです。どうやりますか? diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index be3962cd72..5562b68694 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -247,7 +247,7 @@ alert( getComputedStyle(elem).width ); // 要素の CSS幅を表示します また、もう1つ理由があります: スクロールバーです。スクロールバーがない場合に上手く動作するコードは、スクロールバーがあると不具合を引き起こすことがあります。なぜなら、スクロールバーは、ブラウザによってはコンテンツからスペースを取るためです。従って、コンテンツのために本当に利用可能な幅は CSS 幅よりも *小さい* です。そして、`clientWidth/clientHeight` はそれを考慮します。 -...しかし、`getComputedStyle(elem).width` を使った状況では異なります。一部のブラウザ(e.g. Chrome) ではスクロールバーを引いた実際の内部幅を返し、別のいくつか(e.g. Firefox)は -- CSS 幅を返します(スクロールバーを無視)。このようなクロスブラザの差異が、`getComputedStyle` の利用ではなく、むしろジオメトリプロパティに頼るべき理由です。 +...しかし、`getComputedStyle(elem).width` を使った状況では異なります。一部のブラウザ(e.g. Chrome) ではスクロールバーを引いた実際の内部幅を返し、別のいくつか(e.g. Firefox)は -- CSS 幅を返します(スクロールバーを無視)。このようなクロスブラウザの差異が、`getComputedStyle` の利用ではなく、むしろジオメトリプロパティに頼るべき理由です。 ```online diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index 100769d1dd..b4183cce34 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -178,7 +178,7 @@ elem.addEventListener(..., true) どのハンドラが動作するかを見るために、ドキュメント上の *すべての* 要素にクリックハンドラを設定しています。 -`

    ` をクリックするしたときの処理の流れは次の通りです: +`

    ` をクリックしたときの処理の流れは次の通りです: 1. `HTML` -> `BODY` -> `FORM` -> `DIV` (キャプチャリングフェーズ, 1つ目のリスナーです): 2. `P` (ターゲットフェーズ, キャプチャリングとバブリング2つのリスナをセットしているので、2度実行されます) diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index e94f86bddf..cd7310bc32 100644 --- a/2-ui/2-events/03-event-delegation/article.md +++ b/2-ui/2-events/03-event-delegation/article.md @@ -113,7 +113,7 @@ table.onclick = function(event) { 例えば、"Save" と "Load", "Search" などのボタンをもつメニューを作りたいとします。そしてメソッド `save`, `load`, `search`.... を持つオブジェクトがあります。 -最初の考えは、各ボタンに別々のハンドラを割り当てる事かもしれません。しかし、よりエレガントな方法があります。私たちはメニュー全体に対してハンドラを追加し、呼び出すメソッドを持っているに対して `date-action` 属性を追加します。: +最初の考えは、各ボタンに別々のハンドラを割り当てる事かもしれません。しかし、よりエレガントな方法があります。私たちはメニュー全体に対してハンドラを追加し、呼び出すメソッドがあるボタンに対して `date-action` 属性を追加します。: ```html @@ -260,11 +260,11 @@ One more counter: ```compare + 初期化の簡素化とメモリの節約: 多くのハンドラを追加する必要はありません。 -+ コードを減らす code: 要素を追加または削除するときに、ハンドラを追加/削除する必要はありません。 ++ より少ないコード: 要素を追加または削除するときに、ハンドラを追加/削除する必要はありません。 + DOM の変更: `innerHTML` などで要素を一括して追加/削除することができます ``` -移譲はには、もちろん制限があります: +移譲には、もちろん制限があります: ```compare - まず、イベントがバブリングする必要があります。バブリングしないイベントもあります。また低レベルのハンドラは `event.stopPropagation()` を使うべきではありません。 diff --git a/2-ui/2-events/05-dispatch-events/article.md b/2-ui/2-events/05-dispatch-events/article.md index b46eb7b97e..b5e9dde8a3 100644 --- a/2-ui/2-events/05-dispatch-events/article.md +++ b/2-ui/2-events/05-dispatch-events/article.md @@ -211,13 +211,13 @@ alert(event.clientX); // undefined, 未知のプロパティは無視されま ## イベント中のイベントは同期的です -通常、イベントは非同期に処理されます。つまり: ブラウザが `onclick` を処理しており、そのプロセスの中で新しいイベントが起きた場合、`onclick` が終わるまでそれは待ちます。 +通常、イベントは待ち行列に入れられてから処理されます。つまり: ブラウザが `onclick` を処理しており、そのプロセスの中で新しいイベントが起きた場合(例えばマウスが移動したなど)、その処理は待ち行列に入れられ、対応する `mousemove` のハンドラは `onclick` の処理が終了したあとに呼ばれます。 -例外は、あるイベントが別のイベントから開始された場合です。 +注目すべき例外は、あるイベントが別のイベントから開始された場合(例えば `dispatchEvent` の使用)です。そのようなイベントは直ちに処理されます: 新しいイベントハンドラが呼ばれ、それから現在のイベント処理が再開します。 -次に、制御はネストされたイベントハンドラに飛び、その後戻ってきます。 +例えば、以下のコードでは、 `menu-open` イベントは `onclick` の途中でトリガされます。 -例えば、ここではネストされた `menu-open` イベントは `onclick` の間、同期的に処理されます。: +これは、 `onclick` のハンドラの終了を待たずに直ちに処理されます: ```html run @@ -239,11 +239,15 @@ alert(event.clientX); // undefined, 未知のプロパティは無視されま ``` -ネストされたイベント `menu-open` はバブルアップし `document` で処理されることに注意してください。ネストされたイベントの伝搬は、外部のコード(`onclick`) に戻る前に完全に終了します。 +出力の順番は次のとおりです: 1 → nested → 2 -それは `dispatchEvent` についてだけでなく、他のケースも同様です。イベントハンドラ中の JavaScript は別のイベントにつながるメソッドを呼び出すことができます -- それらも同期的に処理されます。 +ネストされたイベント `menu-open` は `document` で捕捉されることに注意してください。ネストされたイベントの伝搬や処理は、外部のコード(`onclick`) に戻る前に完全に終了します。 -もしそれが気に入らなければ、`onclick` の末尾に `dispatchEvent` (または他のイベントトリガ呼び出し) を置くか、不便であれば `setTimeout(..., 0)` で囲みます。: +それは `dispatchEvent` についてだけでなく、他のケースも同様です。もしイベントハンドラが他のイベントを引き起こすメソッドを呼び出すと – これらも同期的に、ネストされた関数の要領で処理されます。 + +これが気に入らないとしましょう。 `menu-open` や他のネストされたイベントとは無関係に `onclick` を最初に完全に処理したいとします。 + +その場合、 `dispatchEvent` (あるいは他のイベントを引き起こす呼び出し)を `onclick` の最後に置くか、もっと良い方法として、それらをゼロ遅延の `setTimeout` でラップします: ```html run @@ -265,6 +269,10 @@ alert(event.clientX); // undefined, 未知のプロパティは無視されま ``` +これで現在のコードの実行後に、 `menu.onclick` を含め、 `dispatchEvent` を非同期に実行できます。つまりイベントハンドラが完全に切り離されました。 + +出力の順番は次の通りとなりました: 1 → 2 → nested + ## サマリ イベントを生成するためには、最初にイベントオブジェクトを作成する必要があります。 diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 4f978c436b..886028db19 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -277,22 +277,22 @@ function onMouseMove(event) { 今、プロセス全体で、変数 `currentDroppable` の中に現在の "ドロップターゲット" があり、強調表示やその他のことをするのに使うことができます。 -## Summary +## サマリ -We considered a basic `Drag'n'Drop` algorithm. +私達は、基本的なドラッグアンドドロップのアルゴリズムについて考察しました。 -The key components: +重要な要素: -1. Events flow: `ball.mousedown` -> `document.mousemove` -> `ball.mouseup` (cancel native `ondragstart`). -2. At the drag start -- remember the initial shift of the pointer relative to the element: `shiftX/shiftY` and keep it during the dragging. -3. Detect droppable elements under the pointer using `document.elementFromPoint`. +1. イベントの流れ: `ball.mousedown` -> `document.mousemove` -> `ball.mouseup`(ネイティブの `onstart` はキャンセル) +2. ドラッグの開始時 -- 要素に対するポインタの相対的なずれを記憶すること: `shiftX/shiftY` そしてドラッグ中保持しておくこと +3. ポインタの下にあるドロップ可能な要素を `document.elementFromPoint` を用いて検出すること -We can lay a lot on this foundation. +私達は、この機能の上に、様々な物を置くことができます。 -- On `mouseup` we can finalize the drop: change data, move elements around. -- We can highlight the elements we're flying over. -- We can limit dragging by a certain area or direction. -- We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements. -- And so on. +- `mouseup` の段階で、ドロップを終了させることができます: データの変更、要素の移動 +- 今動かしている要素をハイライト表示できます。 +- ドラッグを特定の場所や方向に制限することができます +- `mousedown/up` において、イベントの委譲を行うことができます。 `event.target` を確認する広範囲のイベントハンドラが、何百もの要素のドラッグアンドドロップを管理することができます。 +- などなど。 -There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to described above, so it should be easy to understand them now. Or roll our own, because you already know how to handle the process, and it may be more flexible than to adapt something else. +これらを基にアーキテクチャを構築するフレームワークが存在します: `DragZone` 、 `Droppable` 、 `Draggable` や他のクラスなど。これらのほとんどは上記で説明したものと類似した事を行うため、今となっては簡単に理解できることでしょう。あるいは自作します。なぜなら既に、どのように処理を行えばよいかを知っているし、こちらの方が、何か他のものを採用するよりも柔軟性が高いでしょうから。 diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/solution.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/solution.md index e4aeedc102..e8d4f26682 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/solution.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/solution.md @@ -1,6 +1,6 @@ `mouse.onclick` を使用してクリックを処理し、ネズミを `position:fixed` で移動可能にし、その後 `mouse.onkeydown` で矢印キーを処理します。 -唯一の落とし穴は `keydown` はフォーカスのある要素でのみトリガするということです。そのため、要素に `tabindex` を追加する必要があります。HTML を変更することは禁止しているので、そのために `mouse.tebIndex` プロパティを使います。 +唯一の落とし穴は `keydown` はフォーカスのある要素でのみトリガするということです。そのため、要素に `tabindex` を追加する必要があります。HTML を変更することは禁止しているので、そのために `mouse.tabIndex` プロパティを使います。 P.S. `mouse.onclick` を `mouse.onfocus` に置き換えることもできます。 diff --git a/2-ui/5-loading/02-script-async-defer/article.md b/2-ui/5-loading/02-script-async-defer/article.md index 4a102fc42a..e78d9489e8 100644 --- a/2-ui/5-loading/02-script-async-defer/article.md +++ b/2-ui/5-loading/02-script-async-defer/article.md @@ -123,7 +123,7 @@ ``` - ページのコンテンツはすぐに表示されます: `async` はブロックしません。 -- `DOMContentLoaded` は `asyn` の前後の両方で発生する可能性があり、ここでは保証されません。 +- `DOMContentLoaded` は `async` の前後の両方で発生する可能性があり、ここでは保証されません。 - より小さいスクリプト `small.js` が2番目にありますが、おそらく `long.js` の前に読み込まれるので、`small.js` が最初に実行されます。ですが、キャッシュされている場合は、`long.js` が最初に読み込まれ、最初に実行されることもあります。つまり、非同期スクリプトは "ロードファースト" 順で実行します。 非同期スクリプトは独立したサードパーティのスクリプトをページに組み込む際に便利です。:カウンター、広告など。それらは我々のスクリプトには依存しないので、我々のスクリプトを待つべきではありません。: diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md index bebaada002..6c2c711199 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -6,22 +6,19 @@ libs: # 選択(Selection) と 範囲(Range) -このチャプターではドキュメントでの選択と、`` などのフォームフィールドでの選択について説明します。 +この章ではドキュメントでの選択と、`` などのフォームフィールドでの選択について説明します。 JavaScript を利用して選択状態を取得したり、全体あるいは一部分の選択/選択解除、ドキュメントから選択した部分を削除、タグへのラップなどを行うことができます。 -末尾の "サマリ" セクションでレシピが使用できます。が、チャプター全体を読むことでより多くのことを知ることができます。基礎となる `Range` と `Selection` オブジェクトは簡単に把握できるので、必要なことをするためのレシピは必要ありません。 +末尾の "サマリ" セクションにレシピがあり、これで現時点で必要なことはカバーされているかもしれません。ただ、章全体を読むことでより多くのことを知ることができます。 +基礎となる `Range` と `Selection` オブジェクトの把握は難しくはないので、必要なことをするためのレシピは必要ありません。 ## 範囲(Range) -選択の基本的な概念は [範囲(Range)](https://dom.spec.whatwg.org/#ranges) です。: 基本的には "境界点"(範囲の開始と終了) のペアです。 +選択の基本的な概念は [範囲(Range)](https://dom.spec.whatwg.org/#ranges) で、基本的には "境界点"(範囲の開始と終了) のペアです。 -各点は、始点からの相対オフセットをもつ親DOMノードを表します。親ノードが要素ノードの場合、オフセットは子の番号であり、テキストノードの場合はテキスト内での位置です。以下、例を示します。 - -何かを選択しましょう。 - -まず、range を作成します(コンストラクタにパラメータはありません): +`Range` オブジェクトはパラメータなしで作成できます: ```js let range = new Range(); @@ -29,6 +26,38 @@ let range = new Range(); 次に、`range.setStart(node, offset)` と `range.setEnd(node, offset)` を使用して選択の境界を設定します。 +ご想像のとおり、さらに選択をしていくために `Range` オブジェクトを使用しますが、最初にそのようなオブジェクトをいくつか作成しましょう。 + +### テキストを部分的に選択 + +興味深いことは、両方のメソッドの最初の引数 `node` はテキストノード、あるいは要素ノードで、2つ目の引数の意味はその種類によります。 + +**`node` がテキストノードの場合、`offset` はそのテキストでの位置になります。** + +例えば、要素 `

    Hello

    ` がある場合、次のようにして文字 "ll" を含む範囲を作成できます。: + +```html run +

    Hello

    + +``` + +ここでは `

    ` の最初の子(テキストノード)を取り、その中のテキスト位置を指定しています。: + +![](range-hello-1.svg) + +### 要素ノードの選択 + +**一方、`node` が要素ノードであれば、`offset` は子供の番号になります。** + +これは、テキスト内のどこかで停止するのではなく、ノード全体を含む範囲を作成する場合に便利です。 + 例として、この HTML の一部を考えます: ```html @@ -73,10 +102,21 @@ let selectPDomtree = { drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320); -`"Example: italic"` を選択しましょう。これは `

    ` の先頭から2つの子です(テキストノードのカウント): +`"Example: italic"` の範囲を作成しましょう。 + +ご覧の通り、これはインデックス `0` と `1` を持つ `

    ` の先頭から2つの子です: ![](range-example-p-0-1.svg) +- 開始位置は親の `node` として `

    ` を、オフセットは `0` を持ちます。 + + なので、`range.setStart(p, 0)` と設定できます。 +- 終了位置も親ノードとして `

    ` を持ちますが、オフセットとしては `2` になります(ここまで、という範囲を指定しますが、`offset` の含みません)。 + + そのため、 `range.setEnd(p, 2)` と設定できます。 + +これはデモです。実行するとテキストが選択されるのが分かります: + ```html run

    Example: italic and bold

    @@ -96,9 +136,6 @@ drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320); ``` -- `range.setStart(p, 0)` -- `

    ` の 0番目の子を始点に設定します(テキストノード `"Example: "` です)。 -- `range.setEnd(p, 2)` -- `

    ` の 2番目の子まで(2番目自体は含まない)広げます(2番目はテキストノード `" and "` ですが、それ自体は含まれないので、最後の選択されたノードは `` です。 - これはより柔軟な例で多くのパターンを試せます。: ```html run autorun @@ -126,15 +163,17 @@ From – To ` の最初の子の位置 2 から開始("Example: " の最初の2文字を除くすべて) @@ -156,7 +195,13 @@ From – To alert(document.getSelection()) + ## Selection プロパティ -range と同様、選択には始点と終点があり、それぞれ "anchor(アンカー))"、"focus(フォーカス))" と呼ばれます。 +前述したように、理論上、選択には複数の範囲が含まれる場合があります。これらの範囲オブジェクトは、次のメソッドを使用して取得できます。 + +- `getRangeAt(i)` -- `0` から始まる i 番目の範囲を取得します。Firefox を除くすべてのブラウザでは、`0` のみが使用されます。 + +また、多くの場合、利便性が向上するプロパティも存在します。 + +範囲(range)と同様、選択には始点と終点があり、それぞれ "anchor(アンカー)"、"focus(フォーカス)" と呼ばれます。 主な selection プロパティは次のものです: @@ -290,32 +352,35 @@ range と同様、選択には始点と終点があり、それぞれ "anchor( - `isCollapsed` -- selection が未選択(空の範囲) あるいは存在しない場合 `true` になります。 - `rangeCount` -- selection に含まれる range の数です。 -````smart header="ドキュメント内で Selection の終点が始点の前にくることがあります" -ユーザエージェントによって、コンテンツを選択する多くの方法があります: マウス、ホットキー、モバイルでのタップなど。 +````smart header="選択(selection) 終了/開始 vs 範囲(range)" + +選択(selection)の アンカー/フォーカス は `Range` の 開始/終了 と比較して重要な違いがあります。 + +ご存知の通り、`Range` オブジェクトは常に 終了の前に開始があります。 + +選択の場合、常にそうであるわけではありません。 -マウスなど、そのうちのいくつかは同じ選択を "左から右" と "右から左" の2方向で作成できます。 +マウスで何かを選択することは、「左から右」または「右から左」の両方向で行うことができます。 -もしドキュメント内の選択の始点(anchor)が終点(focus)の前にある場合、この選択は "正" 方向と呼ばれます。 +つまり、マウス ボタンを押してドキュメント内を前に移動すると、その終了点 (フォーカス) は開始点 (アンカー) の後に来ます。 E.g. ユーザがマウスで選択を開始し、"Example" から "italic" まで操作した場合: ![](selection-direction-forward.svg) -そうでない場合、もし "italic" の終わりから "Example" に進む場合、選択は "後方" に向けられ、その focus は anchor の前になります。: +...しかし、同じ選択を逆方向に行うこともできます。"italic" から "Example" まで開始すると (逆方向)、その終了 (フォーカス) が開始 (アンカー) の前になります。 ![](selection-direction-backward.svg) - -これは常に正方向を向く `Range` オブジェクトとは異なります。range の始点を終点の後に置くことはできません。 ```` ## Selection イベント 選択範囲を追跡するためのイベントがあります: -- `elem.onselectstart` -- `elem` で選択が開始されたとき。e.g. ユーザがボタンを押しながらマウスを動かし始めたとき。 - - デフォルトアクションを防いだ場合、選択は開始されません。 +- `elem.onselectstart` -- `elem`(またはその内部)で選択が *開始* されたとき。例えば、ユーザがボタンを押しながらマウスを動かし始めたとき。 + - デフォルトアクションを防いだ場合、選択は開始されません。したがって、この要素から選択を開始することは不可能になりますが、要素は引き続き選択可能です。訪問者は他の場所から選択を開始するだけで済みます。 - `document.onselectionchange` -- 選択範囲が変更されたとき。 - - 注意: おのハンドラは `document` に対してのみ設定可能です。 + - 注意: このハンドラは `document` に対してのみ設定可能です。 ### 選択範囲の追跡デモ @@ -327,19 +392,23 @@ E.g. ユーザがマウスで選択を開始し、"Example" から "italic" ま From – To ``` -### 選択範囲の取得デモ +### 選択(selection)のコピーデモ -選択範囲全体を取得するには: -- テキストとして: `document.getSelection().toString()` を呼ぶだけです。 -- DOM ノードとして: 基底となる範囲を取得し、それらの `cloneContents()` を呼び出します(Firefox のマルチ選択をサポートしてない場合は最初の1つの range に対してのみ)。 +選択されたコンテンツをコピーする、2つのアプローチがあります: + +1. `document.getSelection().toString()` を使用してテキストとして取得できます。 +2. DOM ノードとして: 基底となる範囲を取得し、それらの `cloneContents()` を呼び出します(Firefox のマルチ選択をサポートしてない場合は最初の1つの range に対してのみ)。 そして、これはテキストとDOM ノード両方で選択範囲を取得するデモです: @@ -474,6 +543,7 @@ From – To - `onselect` はなにかが選択されたときに発生しますが、選択が除去されたときには発生しません。 - `document.onselectionchange` イベントは、[仕様](https://w3c.github.io/selection-api/#dfn-selectionchange)によると `document` の選択や範囲とは関係ないため、フォームコントロール内の選択では発生すべきではありません。一部のブラウザにはイベントを生成しますが、それに頼るべきではありません。 + ### 例: カーソルの移動 選択範囲を設定する `selectionStart` と `selectionEnd` を変更することができます。 diff --git a/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg new file mode 100644 index 0000000000..2951607a2d --- /dev/null +++ b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg @@ -0,0 +1 @@ +<p>Hello</p>p.firstChild \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index 29f305de59..19e09766b6 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -3,7 +3,7 @@ ブラウザの JavaScript 実行フローは、Node.js 同様 *event loop* に基づいています。 -event loop の動作を理解することは最適化ためには重要であり、適切なアーキテクチャにとっても重要である場合があります。 +event loop の動作を理解することは最適化のためには重要であり、適切なアーキテクチャにとっても重要である場合があります。 このチャプターでは、最初にそれがどのように動作するかについて理論的な詳細を説明し、次にその知識の実践的な使用例を見ていきます。 diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index c372856a83..1d16e32afb 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -9,45 +9,36 @@ window.open('http://javascript.info/') ... すると、指定された URL で新しいウィンドウが開きます。ほとんどのモダンブラウザは、別ウィンドウではなく新しいタブとして開くよう設定されています。 -## ポップアップブロック +ポップアップはとても古くから存在します。当初の考えは、メインのウィンドウを閉じることなく別のコンテンツを表示することでした。現時点では、それをするための他の方法があります: [fetch](info:fetch) を使うことでコンテンツを動的に読み込むことができ、それを動的に生成された `

    ` の中で表示することができます。ですから、ポップアップは私達が普段使用するものではありません。 -ポップアップはとても古くから存在します。当初の考えは、メインのウィンドウを閉じることなく別のコンテンツを表示することでした。現時点では、それをするための他の方法があります: JavaScript はサーバへリクエストを送ることができるので、ポップアップはめったに使われません。しかし、それでも依然として便利なときはあります。 +さらにポップアップは、複数のウィンドウを同時には表示しないモバイルデバイスでは手際を要します。 -過去、悪意のあるサイトはポップアップを大いに乱用しました。悪意のあるページは広告を含むウィンドウを何度も開く事ができました。そのため、現在多くのブラウザはポップアップをブロックし、ユーザを守ろうとしています。 +それでも、ポップアップがいまだに使われるタスクが存在します。例えば OAuth 認証(Google や Facebook などへのログイン)。なぜなら: -**ほとんどのブラウザは、`onclick` などユーザがトリガーしたイベントハンドラ外から呼ばれた場合には、ポップアップをブロックします。** +1. ポップアップは独立した JavaScript 環境を持つウィンドウです。ですから、第三者の信頼されていないサイトから開くポップアップは安全です。 +2. ポップアップを開くことは非常に簡単です。 +3. ポップアップはナビゲート(URLの変更)可能で、ポップアップを開いたウィンドウにメッセージを送ることができます。 -これについて考える場合、少し注意が必要です。もしコードが直接 `onclick` 内にあればそれは簡単です。しかし、ポップアップは `setTimeout` で開くでしょうか? +## ポップアップブロック -このコードを試してみましょう: +過去、悪意のあるサイトはポップアップを大いに乱用しました。悪意のあるページは広告を含むウィンドウを何度も開く事ができました。そのため、現在多くのブラウザはポップアップをブロックし、ユーザを守ろうとしています。 -```js run -// 3秒後に開く -setTimeout(() => window.open('http://google.com'), 3000); -``` +**ほとんどのブラウザは、`onclick` などユーザがトリガーしたイベントハンドラ外から呼ばれた場合には、ポップアップをブロックします。** -Chrome ではポップアップは開きますが、Firefox ではブロックされます。 +これについて考える場合、少し注意が必要です。もしコードが直接 `onclick` 内にあればそれは簡単です。しかし、ポップアップは `setTimeout` で開くでしょうか? -...そして、これは Firefox でも機能します。: +例えば: +```js +// ポップアップはブロックされます +window.open('https://javascript.info'); -```js run -// 1秒後に開く -setTimeout(() => window.open('http://google.com'), 1000); +// ポップアップは許可されます +button.onclick = () => { + window.open('https://javascript.info'); +}; ``` -違いは、Firefox は 2000ms 以下のタイムアウトは許容することです。しかし、その後は "信頼" がなくなり、"ユーザ操作の範囲外" であると想定します。そのため、最初のはブロックされ、2つ目は開きました。 - -## モダンな使用方法 - -現在、JavaScriptを使用してデータをロードし、ページ上に表示する方法は数多くあります。しかし、ポップアップがベストな選択肢である状況はまだあります。 - -例えば、多くのお店は相談にのる方法としてオンラインチャットを使います。訪問者がボタンを押すと、`window.open` が実行され、チャットをするポップアップが開きます。 - -なぜ、ここではページ内ではなくポップアップが良いのでしょう? - -1. ポップアップは独立した JavaScript 環境を持つ別ウィンドウです。なので、チャットサービスはメインの店舗サイトのスクリプトと統合する必要がありません。 -2. ポップアップはサイトに追加するのが非常に簡単で、オーバーヘッドがほとんどありません。スクリプトの追加は不要で小さいボタンだけです。 -3. ポップアップは利用者がページを離れても持続させることができます。例えば、相談で利用者に新しい "スーパークーラー" のページを訪れるようにアドバイスします。利用者はメインウィンドウでそのページへ行きますが、チャットは無くなりません。 +このように、ユーザは望まないポップアップからある程度は守られていますが、その機能は完全には無効にされていません。 ## window.open @@ -76,9 +67,9 @@ params - `scrollbars` (yes/no) -- 新しいウィンドウのスクロールバーを無効にします。非推奨です。 -あまりサポートされていないブラウザ固有の機能も数多くありますが、通常は使用されていません。例については、window.open in MDN を確認してみてください。 +あまりサポートされていないブラウザ固有の機能も数多くありますが、通常は使用されていません。例については、MDN の window.open を確認してみてください。 -## 例: a minimalistic window +## 例: 最小限のウィンドウ ブラウザがどの機能の無効化を許容するか、最小セットの機能でウィンドウを開いてみましょう。: @@ -91,7 +82,7 @@ open('/', 'test', params); ここでは、ほとんどの "ウィンドウの機能 は無効にされ、ウィンドウは画面外に配置されています。実行して実際に何が起きるのかを見てください。ほとんどのブラウザはゼロ値の `width/height` や画面外の `left/top` といったおかしなものを "直します"。例えば、Chrome はフルスクリーンになるよう、画面幅/高さでウィンドウを開きます。 -通常の配置オプションで `width`, `height`, `left`, `top` 座標を追加追加しましょう。: +通常の配置オプションと妥当な `width`, `height`, `left`, `top` 座標を追加しましょう。: ```js run let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, @@ -105,20 +96,30 @@ open('/', 'test', params); 設定が省略された際のルール: - `open` 呼び出しで 3つ目の引数がない場合、もしくはそれが空の場合、デフォルトのウィンドウパラメータが使われます。 -- params の文字列はあるが、一部の機能の yes/no が省略されている場合、省略された機能はブラウザで許可されていれば無効になります。そのため、params を指定した場合には、明示的に必要なすべての機能に yes を設定してください。 +- params の文字列はあるが、一部の機能の yes/no が省略されている場合、省略された機能はブラウザで許可されていれば無効になります。そのため、params を指定する場合には、明示的に必要なすべての機能に yes を設定してください。 - params に `left/top` がない場合、ブラウザは最後に開いたウィンドウの近くに新しいウィンドウを開こうとします。 - `width/height` がない場合、新しいウィンドウは最後に開いたウィンドウと同じサイズになります。 ## ポップアップにアクセスする -`open` 呼び出しは、新しいウィンドウへの参照を返します。それはプロパティを操作したり、位置を替えたりと言ったことをするのに利用できます。 +`open` 呼び出しは、新しいウィンドウへの参照を返します。それはプロパティを操作したり、位置を変えたりといったことをするのに利用できます。 + +この例では、ポップアップの内容を JavaScript で生成します: + +```js +let newWin = window.open("about:blank", "hello", "width=200,height=200"); + +newWin.document.write("Hello, world!"); +``` -下の例では、新しいウィンドウのコンテンツはロード後に変更されます。 +またこちらでは、コンテンツをロード後に変更します: ```js run let newWindow = open('/', 'example', 'width=300,height=300') newWindow.focus(); +alert(newWindow.location.href); // (*) about:blank, 読み込みはまだ始まっていません + newWindow.onload = function() { let html = `
    Welcome!
    `; *!* @@ -127,63 +128,132 @@ newWindow.onload = function() { }; ``` -外部の `document` コンテンツは、同じオリジン(同じ protocol://domain:port) からのウィンドウに対してのみアクセス可能であることに注意してください。 +注意してください: `window.open` の直後は、新しいウィンドウはまだ読み込まれていません。これは `(*)` の行にある `alert` で示されています。ですから、変更するために `onload` を待っています。 `newWin.document` 用に `DOMContentLoaded` ハンドラを使うこともできます。 -別のサイトの URL を持つウィンドウの場合、`newWindow.location=...` による割り当てでロケーションを変更することができますが、そのロケーションやコンテンツにアクセスすることはできます。これはユーザの安全のためであり、悪意のあるページが `http://gmail.com` のポップアップを開いてもデータを読む事ができないようにするためです。後ほど詳しく話します。 +```warn header="同一生成元ポリシー" +ウィンドウは、同じ元(同じプロトコル://ドメイン:ポート)から生成された場合のみ、互いのコンテンツに自由にアクセスすることができます。 + +そうでなければ、例えばメインウィンドウが `site.com` から生成され、ポップアップが `gmail.com` から生成された場合、ユーザの安全性のため不可能となります。詳しくは、 の章を確認してください。 +``` ## 開いた元(opener)のウィンドウにアクセスする -ポップアップは "opener" ウィンドウにもアクセスすることができます。その中で、それを開いたウィンドウにアクセスするには `window.opener` を使います。ポップアップ以外のすべてのウィンドウの場合、それは `null` です。 +ポップアップは `window.opener` 参照を用いることでも "opener" ウィンドウにアクセスできます。ポップアップ以外のすべてのウィンドウの場合、それは `null` です。 + +以下のコードを実行すると、 opener ウィンドウ(現在のウィンドウ)の内容が "Test" に置き換わります: + +```js run +let newWin = window.open("about:blank", "hello", "width=200,height=200"); + +newWin.document.write( + "