yano
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2022年2月12日 ActionMailer, whenever, scope #### ActionMailer - Railsに組み込まれているメール送信機能です。 コマンドで主要ファイルを作成することが出来ます。 ``` rails g mailer UserMailer ``` #### whenever Railsで定期的に実行したいタスクを設定することが出来るGemです。 ## スコープとメソッドの違い - 前提としてクエリメソッドとは、データベースに問い合わせするクエリを発行してくれるメソッドです。 - 簡単に言うと、スコープとは複数のクエリメソッドをまとめて、複雑なデータをDBから取得したい時などに使われます。 ## スコープの定義方法 ```ruby class モデル名 < ApplicationRecord scope :スコープの名前, -> { 条件式 } # ->ラムダリテラルはlambdaとも書けます。 scope :スコープの名前,lambda { 条件式 } end ``` ```ruby #scopeメソッド    scope(name, body, &block) ``` [scope(name, body, &block)](https://edgeapi.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-scope) 以下は**binding.pry**で止めて、scopeメソッドの中身を検証した結果です。 ```ruby 159: # We are able to call the methods like this: 160: # 161: # Article.published.featured.latest_article 162: # Article.featured.titles 163: def scope(name, body, &block) => 164: unless body.respond_to?(:call) 165: raise ArgumentError, "The scope body needs to be callable." 166: end 167: 168: if dangerous_class_method?(name) 169: raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ [1] pry(Article)> name => :published_at_yesterday [2] pry(Article)> body => #<Proc:0x0000000127da5b88@/Users/name/workspace/advanced/4916_name/app/models/article.rb:86 (lambda)> [3] pry(Article)> &block SyntaxError: unexpected &, expecting end-of-input &block ^ [3] pry(Article)> block => nil ``` 上記から分かることは、第一引数のnameにスコープ名、第二引数のbodyにprocオブジェクトが入っていることが分かります。第三引数&blockにはnilが入っていました。 第三引数に値を渡すには以下のコード例になります。 ```ruby class Shirt < ActiveRecord::Base scope :red, -> { where(color: 'red') } do def dom_id 'red_shirts' end end end ``` do~endそのものが&blockに渡ります。 ### &blockの&とは… ブロックを引数として明示的に受け取る スコープのブロックは第2引数の`body`に渡り第3引数は`nil`でしたが第3引数について触れます。&を仮引数の前につけるとブロックをメソッドの実引数として明示的に受け取ることができます。また、そのブロックを実行する場合はcallメソッドを使います。 **スコープを定義する際に使われる`->`って何?** lambdaリテラルのことです。ここでは、スコープ名という関数を使いたいので、lambdaリテラルを使って、スコープメソッドを定義しているというイメージです。 **引数の持たせ方のコード例** ラムダリテラルを使用した場合は引数をブロックの外で()で仮引数を定義できます。lambdaを記述する書き方ではブロック内にブロック引数で仮引数を定義する必要があります。 ```ruby -> (a) { a + b } lambda { |a| a + b } ``` ラムダリテラルを使用した場合は引数の()を省略できます。 ```ruby -> a { a + b } ``` ## lambdaとは? lambdaとは、->構文またはlambdaメソッドで作られたProcオブジェクトです。Procオブジェクトとは、ブロックをオブジェクトとして扱えるようにしたオブジェクトです。オブジェクトとして扱えるので、変数に入れて別のメソッドに渡すことができます。 ブロックをオブジェクトとして扱うために、procオブジェクトにする=> procオブジェクトを作ります。 ## 1.day.ago 昨日の日付を取得できます。 ``` [1] pry(main)> 1.day.ago => Thu, 10 Feb 2022 23:39:38.257665000 UTC +00:00 [2] pry(main)> 1.day.ago.class => ActiveSupport::TimeWithZone ``` ``` [2] pry(main)> 1.week.ago.beginning_of_day..Time.zone.now.end_of_day => Sat, 05 Feb 2022 00:00:00 JST +09:00..Sat, 12 Feb 2022 23:59:59 JST +09:00 先週の土曜日の0時〜今日(土曜日)の終わり23時59分 ``` ## all_day Railsの**ActiveSupport**に定義されているメソッドで、**日付を範囲**で取得することができます。Rangeクラスになります。**ActiveSupport**とは、Railsの拡張機能のことです**ActiveSupport**を使用することで様々なpresent?やblank?などのメソッドを使うことが出来るようになります。 今日の日付で、00:00:00から23:59:59までを取得範囲とします。all_dayの前に取得したい日付を指定するとその日付の00:00:00から23:59:59までを取得します。 ``` [11] pry(main)> Time.now.all_day => 2022-02-12 00:00:00 +0900..2022-02-12 23:59:59 +0900 ``` |実際の取得例|取得したい日付オブジェクト + all_day| |--|--| |今日| Time.zone.now.all_day| |昨日|1.day.ago.all_day| |1週間前|1.week.ago.all_day| |1か月前|1.month.ago.all_day| ## begining_of_dayとend_of_day begining_of_dayを使うことで、時間部分をその日の始まりに設定した日付を取得できます。end_of_dayは時間部分をその日の終わりに設定した日付を取得できます。 ```ruby [5] pry(main)> 1.day.ago.beginning_of_day => Fri, 11 Feb 2022 00:00:00 JST +09:00 [6] pry(main)> 1.day.ago.end_of_day => Fri, 11 Feb 2022 23:59:59 JST +09:00 [7] pry(main)> 1.day.ago.beginning_of_day..1.day.ago.end_of_day => Fri, 11 Feb 2022 00:00:00 JST +09:00..Fri, 11 Feb 2022 23:59:59 JST +09:00 ``` [Active Support](https://railsguides.jp/active_support_core_extensions.html)とは > Active SupportはRuby on Railsのコンポーネントであり、Ruby言語の拡張やユーティリティを提供します。 > Active Supportは言語レベルで基本部分を底上げして豊かなものにし、Railsアプリケーションの開発とRuby on Railsそれ自体の開発に役立てるべく作られています。 以下はActiveSupportによって拡張されたメソッドの例です。 Rubyでは使えません。 - blank? - present? - empty? - nil? ## TimeとTimeWithZoneどちらを使うべきかについて [TimeとTimeWithZoneの違いについて](https://ken3ypa.hatenablog.com/entry/2019/05/22/070907) まず両者の明確な違いを記します。 **Timeクラス** Rubyのクラスです。 **TimeWithZone** RailsのActive Supportの拡張クラスです。Rubyでは、使用できません。 > Railsの実装を見ていると日時関連の処理は TimeWithZone を積極的に使おうとしているように思います。 TimeWithZone はタイムゾーンをRuby標準のTimeクラスよりも器用に扱えるので、国際的なwebアプリケーションをターゲットにするのであれば、TimeWithZoneクラスを積極的に使うのは確かに理にかなっています。 なので、我々も 極力 TimeWithZone を使うようにした方が良い 、と考えることができます。 [RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い](https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c) 最終的にどちらを使えばいいの?という結論につきましては、上記にもあるようにRailsを使用する場合は、Timeクラスではなく、TimeWithZoneを使用するのをオススメします。 ## 参考 [Railsでよく利用する、Scopeの使い方。](https://qiita.com/ngron/items/14a39ce62c9d30bf3ac3) [Railsのモデルのscopeを理解しよう](https://qiita.com/ozin/items/24d1b220a002004a6351) [【Rails】 モデルのスコープ機能(scope)の使い方を1から理解する](https://pikawaka.com/rails/scope) [知らずに損した】ActiveSupportの期間指定メソッドall_day, all_week, all_month, all_year](https://qiita.com/terufumi1122/items/3aa21c20eeacbce33b93) [Active Support コア拡張機能](https://railsguides.jp/active_support_core_extensions.html) [TimeとTimeWithZoneの違いについて](https://ken3ypa.hatenablog.com/entry/2019/05/22/070907) [RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い](https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c) [module function Kernel.#lambda](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/lambda.html) [[Rails]モデルのscopeメソッド](https://zenn.dev/yusuke_docha/articles/ca0637ccc8d01f) [] # 2022年2月15日 非同期処理:コールバック/Promise/Async - Promise.resolve (JavaScript Primer) ## Promise.resolve Promise.resolveメソッドはFulfilledの状態となったPromiseインスタンスを作成します。 ```javascript const fulfilledPromise = Promise.resolve(); ``` Promise.resolveメソッドは以下のコードの糖衣構文([シンタックスシュガー](https://wa3.i-3-i.info/word15703.html))です。 ```javascript // const fulfilledPromise = Promise.resolve(); と同じ意味 const fulfilledPromise = new Promise((resolve) => { resolve(); }); ``` 上記はresolveを実行するだけの関数です。 Promise.resolveメソッドは引数にresolveされる値を渡すこともできます。 ```javascript // `resolve(42)`された`Promise`インスタンスを作成する const fulfilledPromise = Promise.resolve(42); fulfilledPromise.then(value => { console.log(value); // => 42 }); ``` new演算子を使用した場合とPromise.resolveメソッドを使った場合をコード例として書きました。 ```javascript const fulfilledPromise = Promise.resolve(42); fulfilledPromise.then(value => { console.log(value); // => 42 }); const fulfilledPromise2 = new Promise((resolve) => { resolve("yano"); }); fulfilledPromise2.then(v => { console.log(v); // => yano }); ``` 両者は同じですが、メソッドの挙動を理解するために書きました。 > Promise.resolveメソッドで作成したFulfilledの状態となったPromiseインスタンスに対してもthenメソッドでコールバック関数を登録できます。 状態が変化済みのPromiseインスタンスにthenメソッドで登録したコールバック関数は、常に非同期なタイミングで実行されます。 "常に非同期的なタイミング"という点がピンと来なかったので検証しました。 基本的には同期処理が終わった後に非同期処理であるPromiseの処理が呼び出されるようです。 ▼以下の出力結果は1→2→3→4です。 ```javascript const promise = Promise.resolve(); promise.then(() => { console.log("3. コールバック関数が実行されました"); }); console.log("1. 同期的な処理が実行されました"); promise.then(() => { console.log("4. コールバック関数が実行されました"); }); console.log("2. 同期的な処理が実行されました"); ``` 同期処理は、上から順に一つの処理が終わるのを待ってから次の処理が実行されていきます。 対して、非同期処理とは、上から順に処理が実行されるのは同じですが、一つの処理が終了するのを待たずに、次の処理を実行します。 メインスレッドではJavaScriptのコードの実行とレンダリングを行います。 JavaScriptではメインスレッドで一部の例外を除き非同期処理が並行処理として扱われます。 並行処理とは、処理を一定の単位ごとに分けて処理を切り替えながら実行することです。 今回でいうPromiseやsetTimeOutのような非同期処理はメインスレッドの並びから離れて次の処理に譲ります。 別の言い方にすると、PromiseやsetTimeOutなどの非同期処理は実行されずにタスクキューという領域で一時的に管理され、同期処理が終わった後に非同期処理がメインスレッドで実行されます。 ## Promise.reject Promise.rejectメソッドもresolve()と同じ要領です。 Rejectedの状態となったPromiseインスタンスを作成します。 ```javascript const rejectedPromise = Promise.reject(new Error("エラー")); ``` 上記の構文は以下の糖衣構文です。 ```javascript const rejectedPromise = new Promise((resolve, reject) => { reject(new Error("エラー")); }); ``` Promise.rejectメソッドで作成したRejected状態のPromiseインスタンスに対してもthenやcatchメソッドでコールバック関数を登録できます。Rejected状態へ変化済みのPromiseインスタンスに登録したコールバック関数は、常に非同期なタイミングで実行されます。これはFulfilledの場合と同様です。 ```javascript Promise.reject(new Error("エラー")).catch(() => { console.log("2. コールバック関数が実行されました"); }); console.log("1. 同期的な処理が実行されました"); ``` ## 参考 [Promise.resolve](https://jsprimer.net/basic/async/#promise-resolve) # 2022年2月17日 非同期処理:コールバック/Promise/Async - Promiseチェーン (JavaScript Primer) ## Promiseチェーン > Promiseによる統一的な処理方法は複数の非同期処理を扱う場合に特に効力を発揮します。 > 非同期処理が終わったら次の非同期処理というように、複数の非同期処理を順番に扱いたい場合もあります。 > Promiseではこのような複数の非同期処理からなる一連の非同期処理を簡単に書く方法が用意されています。 thenやcatchメソッドは常に新しいPromiseインスタンスを作成して返すという仕様があります。 つまり、メソッドチェーンと同じくPromiseもチェーンすることができます。 以下はthenメソッドの返り値がPromiseインスタンスのため、1つ目のthenが返された後に次のthenの処理を実行しているという例です。 ```javascript Promise.resolve() // thenメソッドは新しい`Promise`インスタンスを返す .then(() => { console.log(1); }) .then(() => { console.log(2); }); ``` > このPromiseチェーンは、次のコードのように毎回新しい変数に入れて処理をつなげるのと結果的には同じ意味となります。 厳格等価演算子を使うと`false`を返すので新しいインスタンスを作成していることがわかります。 ```javascript // Promiseチェーンを変数に入れた場合 const firstPromise = Promise.resolve(); const secondPromise = firstPromise.then(() => { console.log(1); }); const thirdPromise = secondPromise.then(() => { console.log(2); }); // それぞれ新しいPromiseインスタンスが作成される console.log(firstPromise === secondPromise); // => false console.log(secondPromise === thirdPromise); // => false ``` さらに具体的なPromiseチェーンの例です。 > asyncTask関数はランダムでFulfilledまたはRejected状態のPromiseインスタンスを返します。 この関数が返すPromiseインスタンスに対して、thenメソッドで成功時の処理を書いています。 thenメソッドの返り値は新しいPromiseインスタンスであるため、続けてcatchメソッドで失敗時の処理を書けます。 ```javascript // ランダムでFulfilledまたはRejectedの`Promise`インスタンスを返す関数 function asyncTask() { return Math.random() > 0.5 ? Promise.resolve("成功") : Promise.reject(new Error("失敗")); } // asyncTask関数は新しい`Promise`インスタンスを返す asyncTask() // thenメソッドは新しい`Promise`インスタンスを返す .then(function onFulfilled(value) {  console.log(value); // => "成功" }) // catchメソッドは新しい`Promise`インスタンスを返す .catch(function onRejected(error) { console.log(error.message); // => "失敗" }); ``` `Math.random` は0以上1未満の範囲で浮動小数点の擬似乱数を返します。値を(0 は含むが、 1 は含まない)を返します。 最初の三項演算子で0.5と`Math.random`で返した値を比べたときの真偽値で条件分岐をしています。 先述したように、`Promiseチェーン`はそれぞれ独立したインスタンスを作成するため、今回の場合に条件分岐で`true`のときと`false`のときで別のインスタンスを作成させることで呼び出すメソッドが決まります。 > asyncTask関数が成功(resolve)した場合はthenメソッドで登録した成功時の処理だけが呼び出され、catchメソッドで登録した失敗時の処理は呼び出されません。 一方、asyncTask関数が失敗(reject)した場合はthenメソッドで登録した成功時の処理は呼び出されずに、catchメソッドで登録した失敗時の処理だけが呼び出されます。 例外に対してthenやcatchをチェーンした場合、最も近くにある失敗時の処理が呼び出されます。 以下は1つ目のthenで例外を返しているため、rejectedな状態のPromiseインスタンスを返しており、2つ目のthenは無視されて3つ目のcatchの処理が呼び出されている例です。 ```javascript Promise.resolve().then(() => { // 例外が発生すると、thenメソッドはRejectedなPromiseを返す throw new Error("例外"); }).then(() => { // このthenのコールバック関数は呼び出されません }).catch(error => { console.log(error.message); // => "例外" }); ``` > Promiseチェーンで失敗をcatchメソッドなどで一度キャッチすると、次に呼ばれるのは成功時の処理です。 これは、thenやcatchメソッドは**Fulfilled状態のPromiseインスタンスを作成して返す**ためです。 そのため、一度キャッチするとそこからは元のthenで登録した処理が呼ばれるPromiseチェーンに戻ります。 ```javascript Promise.reject(new Error("エラー")).catch(error => { console.log(error); // Error: エラー }).then(() => { console.log("thenのコールバック関数が呼び出される"); }); ``` 上記の説明を読んで本当に`fulfilled`を返すのか気になったので検証しました。 ```javascript Promise.reject(new Error("エラー")).catch(error => { console.log(error); // Error: エラー }); [[Prototype]]: Promise [[PromiseState]]: "fulfilled" [[PromiseResult]]: undefined ``` 内部プロパティの[[PromiseState]]は`fulfilled`になっていました。 ## まとめ - Promiseはメソッドチェーンと同じく、処理の返り値を新しいPromiseインスタンスを返して次のthenやcatchの処理へ繋ぐことが出来ます。 - 返り値の状態(rejectedやfulfilled)がチェーンしているメソッド(catchやthen)にマッチしない場合は無視されて次のチェーンしているメソッドに移行します。 - catchはエラーを受け取るが、受け取った後に返す`PromiseState`内部プロパティは`fulfilled`です。 (catchの中でさらにエラーを投げた場合は別です。) ## 参考 [Promiseチェーン](https://jsprimer.net/basic/async/#promise-chain) # 2022年2月17日 非同期処理:コールバック/Promise/Async - Promiseチェーンで値を返す (JavaScript Primer) ## Promiseチェーンで値を返す thenメソッドなどのコールバック関数は次のチェーンに値を渡すこともできます。 以下はreturnして次のthenメソッドの引数に値を渡している例です。 ```javascript Promise.resolve(1).then((value) => { console.log(value); // => 1 return value * 2; }).then(value => { console.log(value); // => 2 return value * 2; }).then(value => { console.log(value); // => 4 }).then(value => { console.log(value); // => undefined }); ``` 値をreturnしないのは undefined を返すのと同じです。 ## コールバック関数でPromiseインスタンスを返す Promise.rejectで[[PromiseState]]を`rejected`の状態でインスタンス化したものを`return`しているので、thenは無視されてcatchに処理が移ります。 これまでは`fulfielled`を返すと学びましたが、このように`rejected` で返すことで、その後のPromiseチェーンでthenではなくcatchの処理をさせる事ができます。 ```javascript Promise.resolve().then(function onFulfilledA() { return Promise.reject(new Error("失敗")); }).then(function onFulfilledB() { console.log("onFulfilledBは呼び出されません"); }).catch(function onRejected(error) { console.log(error.message); // => "失敗" }).then(function onFulfilledC() { console.log("onFulfilledCは呼び出されます"); }); ``` 以下のように、catchの処理の中でrejectをreturnさせて、エラー処理を次のチェーンへと連続させることも出来ます。 ```javascript function main() { return Promise.reject(new Error("エラー")); } // mainはRejectedなPromiseを返す main().catch(error => { // mainで発生したエラーのログを出力する console.log(error); // Promiseチェーンはそのままエラーを継続させる return Promise.reject(error); }).then(() => { // 前のcatchでRejectedなPromiseが返されたため、この行は実行されません }).catch(error => { console.log("メインの処理が失敗した"); }); ``` ## [ES2018]Promiseチェーンの最後に処理を書く > Promiseのfinallyメソッドは成功時、失敗時どちらの場合でも呼び出されるコールバック関数を登録できます。 try...catch...finally構文のfinally節と同様の役割を持つメソッドです。 以下はresolve()とreject()のどちらかをランダムに発生させ、thenかcatchを行った後にfinallyの処理を呼び出しています。 ```javascript const promise = Math.random() < 0.5 ? Promise.resolve() : Promise.reject(); promise.then(() => { console.log("Promise#then"); }).catch((error) => { console.log("Promise#catch"); }).finally(() => { // 成功、失敗どちらの場合でも呼び出される console.log("Promise#finally"); }); ``` > 次のコードでは、リソースを取得してthenで成功時の処理、catchで失敗時の処理を登録しています。 また、リソースを取得中かどうかを判定するためのフラグをisLoadingという変数で管理しています。 成功失敗どちらにもかかわらず、取得が終わったらisLoadingはfalseにします。 thenとcatchの両方でisLoadingへfalseを代入できますが、finallyメソッドを使うことで代入を一箇所にまとめられます。 `let isLoading = true;` のフラグを処理が終わったら`false`にしたいわけですが、finallyメソッドを使わないとthenとcatchそれぞれに書かなくてはいけないところを、必ず最後に実行されるfanallyメソッドを使う事で一箇所にまとめられています。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } // リソースを取得中かどうかのフラグ let isLoading = true; dummyFetch("/resource/A").then(response => { console.log(response); }).catch(error => { console.error(error); }).finally(() => { isLoading = false; console.log("Promise#finally"); }); ``` ## まとめ - Promiseチェーンではコールバックで返した値を次のコールバックの引数として渡せます - catch内でPromise.rejectメソッド使って[[PromiseState]]を`rejected`で返せばその後のPromiseチェーンのthenを無視させcatchの処理を行えます - finallyメソッドは[[PromiseState]]が`fulfilled` `rejected`どちらでも処理を実行します - ## 参考 [Promiseチェーン](https://jsprimer.net/basic/async/#promise-chain-value # 2022年2月19日 非同期処理:コールバック/Promise/Async - Promiseチェーンで逐次処理 (JavaScript Primer) ## Promiseチェーンで逐次処理 > Promiseチェーンで非同期処理の流れを書く大きなメリットは、非同期処理のさまざまなパターンに対応できることです。 > ここでは、典型的な例として複数の非同期処理を順番に処理していく逐次処理を考えていきましょう。 Promiseで逐次的な処理といっても難しいことはなく、単純にthenで非同期処理をつないでいくだけです。 以下はdummyFetchを実行してif文がtrueとなりthenメソッドで配列にresponse.bodyを追加した後に、dummyFetchを再度実行している例です。 (逐次処理) Resource AとResource Bを順番に取得しています。 それぞれ取得したリソースを変数resultsに追加し、すべて取得し終わったらコンソールに出力します。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } const results = []; // Resource Aを取得する dummyFetch("/resource/A").then(response => { results.push(response.body); // Resource Bを取得する return dummyFetch("/resource/B"); }).then(response => { results.push(response.body); }).then(() => { console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"] }); ``` しかし、Resource AとBどちらを先に取得しても問題ない場合は、Promise.allメソッドを使って複数のPromiseを1つのPromiseとしてまとめられます。 ## Promise.allで複数のPromiseをまとめる > Promise.allを使うことで複数のPromiseを使った非同期処理をひとつのPromiseとして扱えます。 > Promise.allメソッドは Promiseインスタンスの配列を受け取り、新しいPromiseインスタンスを返します。 その配列のすべてのPromiseインスタンスがFulfilledとなった場合は、返り値のPromiseインスタンスもFulfilledとなります。 一方で、ひとつでもRejectedとなった場合は、返り値のPromiseインスタンスもRejectedとなります。 **Promise.allメソッド** - インスタンス化して配列を返します。 - 配列の返り値が全てFulfilledの場合のみFulfilledで返します。 - 配列の返り値が一つでもRejectedの場合はRejectedを返します。 ```javascript // `timeoutMs`ミリ秒後にresolveする function delay(timeoutMs) { return new Promise((resolve) => { setTimeout(() => { resolve(timeoutMs); }, timeoutMs); }); } const promise1 = delay(1); const promise2 = delay(2); const promise3 = delay(3); Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); // => [1, 2, 3] }); ``` > 先ほどのPromiseチェーンでリソースを取得する例では、Resource Aを取得し終わってからResource Bを取得というように逐次的でした。 しかし、Resource AとBどちらを先に取得しても問題ない場合は、Promise.allメソッドを使って複数のPromiseを1つのPromiseとしてまとめられます。 メリットとして、 Resource AとBを同時に取得すればより早い時間で処理が完了します。 > 次のコードでは、Resource AとBを同時に取得開始しています。 両方のリソースの取得が完了すると、thenのコールバック関数にはAとBの結果が配列として渡されます。 - 順番が関係のない場合は一度にPromise.allメソッドで複数のPromiseを一つにまとめられます。 - より早く処理が終わります。 - 同時に取得するとthenのコールバック関数の引数に返り値が分割代入されます。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } const fetchedPromise = Promise.all([ dummyFetch("/resource/A"), dummyFetch("/resource/B") ]); // fetchedPromiseの結果をDestructuringでresponseA, responseBに代入している fetchedPromise.then(([responseA, responseB]) => { console.log(responseA.body); // => "Response body of /resource/A" console.log(responseB.body); // => "Response body of /resource/B" }); ``` 先述したように渡したPromiseがひとつでもRejectedとなった場合は、失敗時の処理が呼び出される事の検証結果です。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } const fetchedPromise = Promise.all([ dummyFetch("/resource/A"), dummyFetch("/not_found/B") // Bは存在しないため失敗する ]); fetchedPromise.then(([responseA, responseB]) => { // この行は実行されません }).catch(error => { console.error(error); // Error: NOT FOUND }); ``` Promise.Allの配列インスタンスの - PromiseState内部プロパティがRejectedで返ってきているのでthenメソッドが無視されcatchの処理に移行しています。 ## 復習 #### 分割代入 ある特定の情報だけを参照して、変数に格納したい場合、分割代入を使います。 影響範囲を少なくするため、変数に格納して使用します。 **配列の場合** 右辺の配列の各要素を、左辺の配列の各要素に代入します。 ```javascript const array = [1, 2]; // aには`array`の0番目の値、bには1番目の値が代入されます(分割代入)。 const [a, b] = array; console.log(a); // => 1 console.log(b); // => 2 ``` ## まとめ - Promise.allを使う事で複数の非同期処理をまとめる事ができます。 - 複数の非同期処理を逐次的に行う場合、順番を意識しないのであればPromise.Allを使って同時に行うことで、処理を効率化することができる。 ## 参考 [Promiseチェーンで逐次処理](https://jsprimer.net/basic/async/#promise-sequential) # 2022年2月21日 非同期処理:コールバック/Promise/Async - Promise.race (JavaScript Primer) Promise.allメソッドは複数のPromiseが全て完了するまで待つ処理でした。 一方で複数のPromiseを受け取りますが、Promiseが1つでも完了した(Settled状態となった)時点で次の処理を実行する "Promise.race"を勉強しました。 ## Promise.race > Promise.raceメソッドはPromiseインスタンスの配列を受け取り、新しいPromiseインスタンスを返します。 この新しいPromiseインスタンスは、配列の中で一番最初にSettled状態となったPromiseインスタンスと同じ状態になります。 配列の中で一番最初にSettledとなったPromiseがFulfilledの場合は、新しいPromiseインスタンスもFulfilledになる 配列の中で一番最初にSettledとなったPromiseがRejectedの場合は、新しいPromiseインスタンスも Rejectedになる つまり、複数のPromiseによる非同期処理を同時に実行して競争(race)させて、一番最初に完了したPromiseインスタンスに対する次の処理を呼び出します。 ```javascript // `timeoutMs`ミリ秒後にresolveする function delay(timeoutMs) { return new Promise((resolve) => { setTimeout(() => { resolve(timeoutMs); }, timeoutMs); }); } // 1つでもresolveまたはrejectした時点で次の処理を呼び出す const racePromise = Promise.race([ delay(1), delay(32), delay(64), delay(128) ]); racePromise.then(value => { // もっとも早く完了するのは1ミリ秒後 console.log(value); // => 1 }); ``` #### 上記のコードのまとめ - 引数の中で最初にSettledとなったPromiseインスタンスを返します。 - Promise.raseのPromiseStateが決まった時点でSettledは不変なものになります。 - Promise.raceの最初の処理結果が出た後も、残りの非同期処理は行われますが、返り値には影響しません。 - Promise.raseの返り値は"Promiseインスタンスの1"となります。 ## Promise.allの返り値にPromiseResult内部プロパティに値が返ってきているのを検証から発見しました。 ここでは、PromiseResult内部プロパティに`Array(2)`が返ってきていて中身も参照した画像を載せます。 ![](https://i.imgur.com/4XHCoLu.png) 続いて、Promise.raseだと一番最初に実行された引数の値が入ります。 上記のコード例での検証結果も載せます。 ![](https://i.imgur.com/T7fpvq7.png) Promise.raseの最初の引数の返り値は`dalay(1)`の結果なのでPromiseResult内部プロパティには `1`が入っています。 ## Promise.raceの実用例 非同期処理のタイムアウトが実装できます。 以下は一定時間経過しても処理が終わっていない場合、エラーを返すという処理です。 ```javascript= function timeout(timeoutMs) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error(`Timeout: ${timeoutMs}ミリ秒経過`)); }, timeoutMs); }); } function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } Promise.race([ dummyFetch("/resource/data"), timeout(500), ]).then(response => { console.log(response.body); // => "Response body of /resource/data" }).catch(error => { console.log(error.message); // => "Timeout: 500ミリ秒経過" }); ``` #### 上記のコードのまとめ - Promise.race()は配列内の2つのPromiseインスタンスの処理を同時に実行し先に返ってきた返り値が処理結果になります。 - dummyFetchは0〜1秒未満のランダムなタイミングで実行しfulfilled状態のオブジェクトを返します。 - timeoutは500ミリ秒経過したら例外を発生させrejected状態のインスタンスを返します。 - dummyFetchのランダムの実行時間が500ミリ秒未満ならthenメソッドが実行され以上ならcatchメソッドが実行されます。 ## [ES2017] Async Function Promiseは構文ではなくただのオブジェクトであるため、それらをメソッドチェーンとして実現しないといけないといった制限があります。 この制限を解決するために導入されたのがAsync Functionです。 Async Functionは、Promiseインスタンスを返す関数を定義する構文です。 ```javascript= function doAsync() { return Promise.resolve("値"); } ``` 上記のコードの糖衣構文が以下です。 ```javascript= async function doAsync() { return "値"; } ``` ## Async Functionの定義 ```javascript // 関数宣言のAsync Function版 async function fn1() {} // 関数式のAsync Function版 const fn2 = async function() {}; // Arrow FunctionのAsync Function版 const fn3 = async() => {}; // メソッドの短縮記法のAsync Function版 const obj = { async method() {} }; ``` 関数の定義に**asyncキーワードをつけること**で定義できます。 - Promiseインスタンスを返します。 - `await`式が利用できます。 `await`式というPromiseの非同期処理が完了するまで待つ構文が利用できます。 非同期処理を同期処理のように扱えるため、Promiseチェーンで実現していた処理の流れを読みやすく書けます。 [MDN await](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await) ## 参考 [Promise.race](https://jsprimer.net/basic/async/#promise-race) # 2022年2月22日 非同期処理:コールバック/Promise/Async - Async FunctionはPromiseを返す (JavaScript Primer) ## Async FunctionはPromiseを返す Async Functionとして定義した関数が返す値は次のケースがあります。 - 値をreturnした場合、Fulfilled状態のPromiseインスタンスの値を返す - Promiseをreturnした場合、返り値のPromiseをその返す - 例外が発生した場合は、エラーを持つrejectedなPromiseを返す ## await式 - 右辺のPromiseインスタンスがFulfilledまたはRejectedになるまでその場で非同期処理の完了を待ちます。 - Promiseインスタンスの状態が変わると、次の行の処理を再開します。 **使用できる場所** - Async Functionの関数の直下 - ECMAScriptモジュールの直下 **awaitがついた場合の実行のタイミング** - 右辺の処理のみを待ちます。 - 右辺の処理を待っている間はそれ以降の処理は実行されません。 ```javascript async function asyncMain() { await Promiseインスタンス; // Promiseインスタンスの状態が変わったら処理を再開する } ``` ```javascript async function doAsync() { 非同期処理 } async function asyncMain() { // doAsyncの非同期処理が完了するまでまつ await doAsync(); console.log("この行は非同期処理が完了後に実行される"); } ``` - await doAsyncの記述があるので `doAsync()`の処理が終えるまで待ちます。 - doAsync()の処理が完了されたら次の行のconsole.log();が実行されます。 ```javascript async function asyncMain() { const value = await Promise.resolve(42); console.log(value); // => 42 } asyncMain(); // Promiseインスタンスを返す ``` - awaitの右辺(Promiseインスタンス)の評価結果を値として返します。 - thenメソッドを使わなくても評価結果を返すのでそのまま変数に代入できます。 - 関数の結果はPromiseインスタンス"42"を返します。 ```javascript async function asyncMain() { // `await`式で評価した右辺のPromiseがRejectedとなったため、例外がthrowされる const value = await Promise.reject(new Error("エラーメッセージ")); // await式で例外が発生したため、この行は実行されません } // Async Functionは自動的に例外をキャッチできる asyncMain().catch(error => { console.log(error.message); // => "エラーメッセージ" }); ``` - awaitが右辺を評価するため、その場でPromise.rejectメソッドがエラーをthrowします。 - awaitがない場合はただ定義がされただけで何も返さないためcatchが無視されます。 ## try...catch構文でawait式を使用する ```javascript async function asyncMain() { // await式のエラーはtry...catchできる try { // `await`式で評価した右辺のPromiseがRejectedとなったため、例外がthrowされる const value = await Promise.reject(new Error("エラーメッセージ")); // await式で例外が発生したため、この行は実行されません } catch (error) { console.log(error.message); // => "エラーメッセージ" } } // asyncMainはResolvedなPromiseを返す asyncMain().catch(error => { // すでにtry...catchされているため、この行は実行されません }); ``` - asyncMain()内の処理は全て非同期的に実行されます。 - awaitの右辺が`fulfilled`か`rejected`か評価をしてから次の行を実行するので同期処理のようにわかりやすく書けます。 - awaitを書くことでPromise.rejectの評価結果を得られるので非同期処理内でtry...catchが行えています。 - 同期処理のtry...catch構文と同じようにエラーが発生した時点で例外を投げることも可能になります。 ## 参考 [Async FunctionはPromiseを返す](https://jsprimer.net/basic/async/#:~:text=%E3%81%8C%E5%88%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B-,Async%20Function%E3%81%AFPromise%E3%82%92%E8%BF%94%E3%81%99,-Async%20Function%E3%81%A8%E3%81%97%E3%81%A6) # 2022年2月24日 非同期処理:コールバック/Promise/Async - Promiseチェーンをawait式で表現する (JavaScript Primer) ## Promiseチェーンをawait式で表現する #### **まずはawaitを使わない逐次的な処理から見てみます。** 以下はPromiseチェーンで複数の非同期処理を逐次的に行っている例です。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } // リソースAとリソースBを順番に取得する function fetchAB() { const results = []; return dummyFetch("/resource/A").then(response => { results.push(response.body); return dummyFetch("/resource/B"); }).then(response => { results.push(response.body); return results; }); } // リソースを取得して出力する fetchAB().then((results) => { console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"] }); ``` #### 上記のコードのポイント - fetchAB()は逐次的な処理を行った結果を配列のresultsにpushしていき、最終的にそれを返します。 - thenメソッドでPromiseチェーンしています。 - 最初のdummyFetch()ではfulfilled状態のPromiseインスタンスを返して配列にvalueを追加した上でさらにdummyFetch()を実行しています。 - 最初のthenメソッドの中で`return dummyFetch("/resoutce/B");`を行うことで`fetchAB()`として一つの関数にまとめることができています。 - `response.body`はプロパティをドット記法で記述する事でvalueを取得しています。 **await式を使った場合** ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } // リソースAとリソースBを順番に取得する async function fetchAB() { const results = []; const responseA = await dummyFetch("/resource/A"); results.push(responseA.body); const responseB = await dummyFetch("/resource/B"); results.push(responseB.body); return results; } // リソースを取得して出力する fetchAB().then((results) => { console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"] }); ``` - 先程との相違点として、fetchABをAsync Functionとawait式で書かれています。 - fetchAB()内の処理は全て非同期的に処理が行われます。 - dummyFectch()はPromiseインスタンスを返します。 - await式の右辺のdummyFetch()の評価結果(Fulfilled,Rejected)を得るまで次の行は実行されません。 - await式はasyncを宣言していないと`syntax error`になります。 - await式はAsync Functionの関数の直下とECMAScriptモジュールの直下でしか使用できません。 **await式をfunction宣言の中で使用したときのエラー内容** > SyntaxError: await is only valid in async functions and the top level bodies of modules Async Functionとawait式で書くことで同期的な流れと同じように処理を行うことが出来るため、ネストがなく綺麗なコードになります。 ## Async Functionと反復処理 他の構文との組み合わせとして、for文の例が紹介されていました。 繰り返し処理の中でawait式を使ってリソースの取得を待っています。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } // 複数のリソースを順番に取得する async function fetchResources(resources) { const results = []; for (let i = 0; i < resources.length; i++) { const resource = resources[i]; // ループ内で非同期処理の完了を待っている const response = await dummyFetch(resource); results.push(response.body); } // 反復処理がすべて終わったら結果を返す(返り値となるPromiseを`results`でresolveする) return results; } // 取得したいリソースのパス配列 const resources = [ "/resource/A", "/resource/B" ]; // リソースを取得して出力する fetchResources(resources).then((results) => { console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"] }); ``` - fetchResourcesに渡したい配列を定義した上で実行しています。 - ループ内でawaitを使うとdummyFetch()の非同期処理結果を待つことが出来ます。 - ループ処理が完了したら文字列の要素が入ったresultsの配列が返されます。(Asyncで宣言しているためPromiseインスタンスとなります) ## [復習]for文 for文は繰り返す範囲を指定した反復処理を書くことができます。 ```javascript for (初期化式; 条件式; 増分式) { 実行する文; } ``` for文の実行の流れは次のようになります。 - 初期化式 で変数の宣言 - 条件式 の評価結果がtrueなら次のステップへ、falseなら終了 - 実行する文 を実行 - 増分式 で変数を更新 - ステップ2へ戻る ## 参考 [Promiseチェーンをawait式で表現する](https://jsprimer.net/basic/async/#promise-chain-to-async-function) # 2022年2月25日 非同期処理:コールバック/Promise/Async - Promise APIとAsync Functionを組み合わせる (JavaScript Primer) 昨日の[朝会](https://morning-6.hatenablog.com/entry/2022/02/24/101506?_ga=2.76488924.892086942.1645514215-1676638408.1634000917)ではawait式でリソースを順番に1つずつ取得していましたが、Promise.allを使ってまとめて取得する例から見ていきます 。 ## Promise APIとAsync Functionを組み合わせる ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } // 複数のリソースをまとめて取得する async function fetchAllResources(resources) { // リソースを同時に取得する const promises = resources.map(resource => { return dummyFetch(resource); }); // すべてのリソースが取得できるまで待つ // Promise.allは [ResponseA, ResponseB] のように結果が配列となる const responses = await Promise.all(promises); // 取得した結果からレスポンスのボディだけを取り出す return responses.map(response => { return response.body; }); } const resources = [ "/resource/A", "/resource/B" ]; // リソースを取得して出力する fetchAllResources(resources).then((results) => { console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"] }); ``` #### 上記のコードのポイント - Promise.allメソッドは引数で渡した全ての要素をPromiseインスタンス化し配列で返します。(PromiseState内部プロパティは一つでもrejectedならrejected、全てがfulfieldならfulfiieldになります。) - mapメソッドは配列を受け取り、仮引数に要素を一つずつ渡して渡してブロック内の処理を繰り返し実行します。 - mapメソッドは非破壊的メソッドであるため新しい配列を生成した後も元の配列の値も元の変数内に残っています。 - mapメソッドのコールバック関数は引数が一つなので引数に使用する()を省略できます。(primerでは省略していませんでしたがここではあえて省略しました。) - s ## thisが問題となる2つのパターン #### 問題1: thisを含むメソッドを変数に代入した場合 thisを参照するメソッドを、変数に代入して関数として呼び出すと、ベースオブジェクトの所属先が変わっているためundefinedになってしまう。 ```javascript "use strict"; const person = { fullName: "Brendan Eich", sayName: function() { return this.fullName; } }; console.log(person.sayName()); // => "Brendan Eich" const say = person.sayName; say(); // => TypeError: Cannot read property 'fullName' of undefined ``` - thisを使ったメソッドは変数に代入するべきではありません。 - どうしても代入する場合はcall apply bindメソッドを使って対応すると良いです。 #### 問題2: コールバック関数とthis - functionキーワードを使用してしまうと`undefined`になります。 - Arrow Functionで書けばスコープチェーンの仕組みと同様に外側のスコープを探索するので問題なく使えます。 (Arrow Functionはthisを持たない) ## 復習 #### スコープチェーンの仕組み - 今いるスコープを探索してなかったら外側のスコープに探索しにいく仕組みの事です。 #### 静的スコープ - 定義された位置でスコープが決まります。 - 呼び出し元によって参照先が変わりません。 - ある変数がどの値を参照するかは静的に決まります。 #### メモリ管理の仕組み - 参照されなくなったデータはガベージコレクションにより解放されます。 ## 参考 [](https://jsprimer.net/basic/async/#relationship-promise-async-function) # 2022年2月26日 非同期処理:コールバック/Promise/Async - Promise APIとAsync Functionを組み合わせる (JavaScript Primer) await式はAsync Functionの直下でのみ利用可能 ```javascript // asyncではない関数では`await`式は利用できない function main(){ // SyntaxError: await is only valid in async functions await Promise.resolve(); } ``` - await式が利用できるのは Async function直下もしくはECMスクリプトモジュール直下です。 - await式を利用する場合はasync宣言をしないとSyntaxErrorになります。 > Async Function内でawait式を使って処理を待っている間も、関数の外側では通常どおり処理が進みます。 次のコードを実行してみると、Async Function内でawaitしても、Async Function外の処理は停止していないことがわかります。 ```javascript async function asyncMain() { // 中でawaitしても、Async Functionの外側の処理まで止まるわけではない await new Promise((resolve) => { setTimeout(resolve, 16); }); } console.log("1. asyncMain関数を呼び出します"); // Async Functionは外から見れば単なるPromiseを返す関数 asyncMain().then(() => { console.log("3. asyncMain関数が完了しました"); }); // Async Functionの外側の処理はそのまま進む console.log("2. asyncMain関数外では、次の行が同期的に呼び出される"); ``` - Async Functionでawait式を使った場合、外側の処理にawaitが影響することはありません。 for文で繰り返し処理をしてきましたが、ここでは`Array#forEach`  に変更しています。この場合に先述したAsync Functionの外側の処理にawaitが影響しない事で望む挙動が得られなくなってきます。 ```javascript async function fetchResources(resources) { const results = []; // Syntax Errorとなる例 resources.forEach(function(resource) { // Async Functionではないスコープで`await`式を利用しているためSyntax Errorとなる const response = await dummyFetch(resource); results.push(response.body); }); return results; } ``` - Async Function内でforEachなどでコールバック関数を書く場合、コールバック関数に対してもasync宣言を行わないとawait式を使った場合にSyntaxErrorとなります。 - コールバック関数内のブロックのawaitはコールバック関数の外側を参照しないためです。 forEachメソッドのコールバック関数をAsync Functionにした場合の挙動をみていきます。 syntax errorにはなりませんが望んだ挙動は得られません。 以下、コード例です。問題点がわかるように`console.log` で出力する順番をみていきます。 ```javascript function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } // リソースを順番に取得する async function fetchResources(resources) { const results = []; console.log("1. fetchResourcesを開始"); resources.forEach(async function(resource) { console.log(`2. ${resource}の取得開始`); const response = await dummyFetch(resource); // `dummyFetch`が完了するのは、`fetchResources`関数が返したPromiseが解決された後 console.log(`5. ${resource}の取得完了`); results.push(response.body); }); console.log("3. fetchResourcesを終了"); return results; } const resources = ["/resource/A", "/resource/B"]; // リソースを取得して出力する fetchResources(resources).then((results) => { console.log("4. fetchResourcesの結果を取得"); console.log(results); // => [] }); ``` - fetchResources(resources)は非同期処理が行われています。 - 非同期処理内ではコールバック関数がAsync Functionでawait式が使われていますが外側には反映されません。 - awaitの処理を待っている間に非同期処理が進み、先に結果をreturnします。 - Fulfilled状態の[]が返り値となりsettledとしてthenメソッドが実行され、4つ目のconsole.log();とresultsが出力されます。 - resultsが出力された後にawaitの5つ目のconsole.log();が出力されます。 - forEachのコールバック関数をArrowFunctionに変えても同様の結果になります。 ## まとめ > この問題を解決する方法として、最初のfetchResources関数のように、コールバック関数を使わずにすむforループとawait式を組み合わせる方法があります。 また、fetchAllResources関数のように、複数の非同期処理を1つのPromiseにまとめることでループ中にawait式を使わないようにする方法があります。 - await式を利用する場合はasync宣言をしないとSyntaxErrorになります。 - await式は定義されたスコープのAsyncFunction外には影響しません。 - AsyncFunction内でコールバック関数を使う場合は注意しましょう。 ## 参考 [await式はAsync Functionの直下でのみ利用可能](https://jsprimer.net/basic/async/#relationship-promise-async-function) # ## [ES2022] Module直下でのawait式 - ブラウザではscriptタグで囲えば"Script"として実行され、\<script type="module">と書けば"Module"として実行されます。 - "Module"としてJavaScriptを実行した時のみ、トップレベル(もっとも外側のスコープ)においてはAsync Functionなしでawait式が利用できます。 以下はmodule直下であることを前提とするとawaitが仕様でき、正しくコードが実行出来ます。 ```javascript console.log("実行開始"); const startTime = Date.now(); // awaitを使って1秒待つ await new Promise(resolve => setTimeout(resolve, 1000)); console.log(`実行終了: ${Date.now() - startTime}ms 経過しました`); ``` - Acync Function内ではないがawait式が利用できています。 - await式は右辺のPromiseの評価結果を返してから次の行が実行されます。 - await式のお陰でconsole.logの内容が期待した内容で得られています。 ##### 実行コンテキストの確認 console.log(this);とすると分かりやすいです。 実行コンテキストがmoduleの場合・・・ undefinedが出力され、 実行コンテキストがscriptの場合・・・ [object Window]が出力される。 - Moduleではトップレベルにおいてawait式が利用できることは、Top-Level awaitと呼ばれます。 ## [復習]セミコロンについて #### セミコロンが必要なパターン - 式の末尾 - 関数式の末尾(ブロックで終わるとしてもセミコロンをつける) - 文の末尾 例) ```javascript console.log(); // これは式です const read = function() { }; // 関数式の末尾です。ブロックで終わりますが;を付けます。 return 〜; // 文です ``` #### セミコロンが不要なパターン - ブロックで終わる文 以下の2点を押さえておきましょう。 - ブロック文の後には不要。 - 関数式の後のブロックには付ける。 > JavaScriptには、特殊なルールに基づき、セミコロンがない文も行末に自動でセミコロンが挿入されるという仕組みがあります。 しかし、この仕組みは構文を正しく解析できない場合に、セミコロンを足すという挙動を持っています。 これにより、意図しない挙動を生むことがあります。そのため、必ず文の末尾にはセミコロンを書くようにします。 ## まとめ この章では、非同期処理に関するコールバック関数、Promise、Async Functionについて学びました。 - 非同期処理はその処理が終わるのを待つ前に次の処理を評価すること - 非同期処理であってもメインスレッドで実行されることがある - エラーファーストコールバックは、非同期処理での例外を扱うルールの1つ - Promiseは、ES2015で導入された非同期処理を扱うビルトインオブジェクト - Async Functionは、ES2017で導入された非同期処理を扱う構文 - Async FunctionはPromiseの上に作られた構文であるため、Promiseと組み合わせて利用する PromiseやAsync Functionの応用パターンについては「JavaScript Promiseの本」も参照してください。 #### 章の終わりに認識をずらっと書いてみました。 - 非同期処理は上から順番に実行されるが処理が終えるのを待つ事なく次の行が実行されます。 - 同期処理はコールスタックで処理をしてコールスタックが空になったらイベントループが非同期処理を格納しているタスクキューに処理を実行するように伝える流れなので、同期処理が終わってから非同期処理が実行されます。 - Web Worker APIなどを使わなかったら非同期処理もメインスレッドで実行されます。 - エラーファーストコールバックは非同期処理を呼び出す際に第一引数に値、第二引数にコールバック関数を書きます。 - コールバック関数の第一引数にはエラー時の処理、第二引数には成功時の処理を書きます。非同期関数内でこれらを条件式で呼び出すことで非同期処理内で発生した例外を外側に伝えることができます。 - 非同期処理中に発生したエラーを外側でキャッチする方法の第一人者がエラーファーストコールバックでした。 - エラーファーストコールバックをわかりやすくするためにPromiseが導入されました。 - Async Functionとawaitを使う事でPromiseを同期処理のようにわかりやすく記述できます。 ## 参考 [[ES2022] Module直下でのawait式](https://jsprimer.net/basic/async/#top-level-await-in-module) # 2022年3月1日 [ES2015] Map/Set - Map (JavaScript Primer) JavaScriptには配列の他にマップ型のコレクションであるMapと、セット型のコレクションであるSetというデータの集まりを扱うコレクションがあります。 ## Map マップ型のコレクションを扱うためのビルトインオブジェクトです。 キーと値の組み合わせからなります。 #### マップの作成と初期化 他のビルトインオブジェクトと同じようにMapオブジェクトもnewでインスタンス化することが出来ます。 sizeプロパティはマップのサイズを返します。 ```javascript const map = new Map() console.log(map.size); //=>0 ``` 初期化時にコンストラクタに値を渡す場合はエントリーの配列を渡します。 **エントリーとは** 1つのキーと値の組み合わせを[キー, 値]という形式の配列で表現したものです。 **引数にはエントリーの配列が渡せます** ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); // 2つのエントリーで初期化されている console.log(map.size); // => 2 ``` - エントリー自体が配列でリテラルで囲われており、そのエントリーを配列として引数に渡すので配列リテラルは2重で書く必要があります。 エントリーを配列リテラルで囲わずに引数として渡すとTypeErrorが発生しました。 ```javascript const map = new Map(["key1", "value1"]); TypeError: Iterator value key1 is not an entry object ``` エントリーがどこに渡されているのかを検証ツールで確認しました。 Entries内部プロパティがありその中にありました。ここにnew演算子でインスタンス化した際に渡した引数が入っています。 ![](https://i.imgur.com/C3ybVWA.png) ## 要素の追加と取り出し **Mapが持つメソッド** - setメソッド・・・2つの要素(キーとバリュー)を引数に渡すことで新しくmapに要素を追加出来ます。 - getメソッド・・・引数に既存のキーを渡すことでvalueにあたる値を取り出せます。 - hasメソッド・・・引数にkeyを渡すことで、そのkeyに紐づく値を持っているかを確認し、真偽値を返します。 ```javascript const map = new Map(); // 新しい要素の追加 map.set("key", "value1"); console.log(map.size); // => 1 console.log(map.get("key")); // => "value1" // 要素の上書き map.set("key", "value2"); console.log(map.get("key")); // => "value2" // キーの存在確認 console.log(map.has("key")); // => true console.log(map.has("foo")); // => false ``` ObjectとMapの違い - Mapのキーは任意の型で指定できます。 - Objectのキーは文字列かシンボルの型でしか指定できません。 > ObjectもMapも、キー付けされたデータの集まりですが、違いもあります。 それは、Objectの場合、キーを文字列またはシンボルの型のみでしか指定できませんでしたが、Mapの場合は、キーを任意の型で取ることができるようになった点です。 インスタンス化をしたMapオブジェクトにsetメソッドを使って要素を追加した状態を検証しました。 ![](https://i.imgur.com/kdDpiKB.png) - Entries内部プロパティにsetで追加した要素(keyとvaluのセット)が入っていました。 #### deleteメソッド clearメソッド ```javascript const map = new Map(); map.set("key1", "value1"); map.set("key2", "value2"); console.log(map.size); // => 2 map.delete("key1"); console.log(map.size); // => 1 map.clear(); console.log(map.size); // => 0 ``` - map.delete("キー名")の記述で要素を削除する事ができます。 - map.clear()の記述で要素を全て削除する事ができます。 ## メモ - ArrayやObject、Mapなどのデータの集まりをコレクションといいます。 - Mapではキー文字列やシンボル型以外にも指定できます。 - エントリーとは、1つのキーと値の組み合わせを[キー, 値]という形式の配列で表現したものです。 - Mapインスタンスをnew演算子で作成する際に渡す引数にはエントリーを配列リテラルで囲う必要があります。[[]]このように2重になるイメージです。 ## 復習 - new演算子には関数呼び出しと同じように引数を渡すことができます。 - new演算子の引数はクラスのconstructorメソッド(コンストラクタ関数)の仮引数に渡されます。 - コンストラクタの中ではインスタンスオブジェクト(this)が新しく作成されています。(初期化処理) ```javascript const array = [1, 2, 3]; array.forEach((currentValue, index, array) => { console.log(currentValue, index, array); }); // コンソールの出力 // 1, 0, [1, 2, 3] // 2, 1, [1, 2, 3] // 3, 2, [1, 2, 3] ``` ## 参考 [[ES2015] Map/Set](https://jsprimer.net/basic/map-and-set/#map-and-set) [Map](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map) # 2022年3月3日 [ES2015] Map/Set - Map (JavaScript Primer) マップの反復処理 マップが持つ"要素を列挙メソッド"として、forEach、keys、values、entriesがあります。 今回はforEach、keysについて学んでいきます。 ## forEachメソッド - コールバック関数には値、キー、マップの3つが渡されます。 - 配列のforEachではインデックスが渡されるところがMapではキーが渡されます。 - 配列はインデックス・Mapではキーで要素を特定するためです。 ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const results = []; map.forEach((value, key) => { results.push(`${key}:${value}`); }); console.log(results); // => ["key1:value1","key2:value2"] ``` ### MapのforEachで渡す3つの引数を出力しました ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const results = []; map.forEach((value, key, map) => { console.log(value); console.log(key); console.log(map); results.push(`${key}:${value}`); }); // 出力結果 value1 key1 Map(2) { 'key1' => 'value1', 'key2' => 'value2' } value2 key2 Map(2) { 'key1' => 'value1', 'key2' => 'value2' } ``` - 第一引数にはvalueが渡っています。 - 第二引数にはkeyが渡っています。 - 第三引数にはmapが渡っています。 ## keysメソッド > keysメソッドはマップが持つすべての要素のキーを挿入順に並べたIteratorオブジェクトを返します。 同様に、valuesメソッドはマップが持つすべての要素の値を挿入順に並べたIteratorオブジェクトを返します。 これらの返り値はIteratorオブジェクトであって配列ではありません。 そのため、次の例のようにfor...of文で反復処理を行ったり、Array.fromメソッドに渡して配列に変換して使ったりします。 - keysメソッドはマップが持つすべての要素のキーを挿入順に並べたイテレータを返します。 - valuesメソッドはマップがもつすべての要素の値を挿入順に並べたイテレータを返します。 - 返り値はイテレータであって配列ではありません。 - for...of文で反復処理をしたり、Array.fromで配列に変換したりできます。 ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const keys = []; // keysメソッドの返り値(Iterator)を反復処理する for (const key of map.keys()) { keys.push(key); } console.log(keys); // => ["key1","key2"] // keysメソッドの返り値(Iterator)から配列を作成する const keysArray = Array.from(map.keys()); console.log(keysArray); // => ["key1","key2"] ``` - Array.from() メソッドは、配列のようなオブジェクトや反復可能オブジェクトから、浅くコピーされた新しい Array インスタンスを生成します。 - for...of文は配列に対して使います。ループ毎に配列の各要素を取り出します。(for...in文はインデックス番号で取り出します) - keysとkeysArrayは過程は違いますが同じ出力結果です。 #### map.keysメソッドを出力 ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); map.keys() ``` 出力結果: [object Map Iterator] (中身がmapの要素が列挙された状態のIteratorが返っています) map.values()としても結果は出力結果は同じです。(中身がvalueの要素が列挙された状態のIteratorが返っています) ## 参考 [マップの反復処理](https://jsprimer.net/basic/map-and-set/#map-iteration) # 2022年3月4日 [ES2015] Map/Set - Map (JavaScript Primer) マップの反復処理 ## entriesメソッド ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const entries = []; for (const [key, value] of map.entries()) { entries.push(`${key}:${value}`); } console.log(entries); // => ["key1:value1","key2:value2"] ``` > entriesメソッドはマップが持つすべての要素をエントリーとして挿入順に並べたIteratorオブジェクトを返します。 先述のとおりエントリーは[キー, 値]のような配列です。 そのため、配列の分割代入を使うとエントリーからキーと値を簡単に取り出せます。 - Mapが持つすべての要素をエントリーとして挿入順に並べたイテレーターオブジェクトを返します。 - エントリーは[キー, 値]です。(配列リテラルを含めてエントリーです) - 配列の分割代入を使うとエントリーからキーと値を取り出せます。 #### マップを直接for...of文で反復処理してもentriesメソッドと同じ結果を得られます。 ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const results = []; for (const [key, value] of map) { results.push(`${key}:${value}`); } console.log(results);// => ["key1:value1","key2:value2"] ``` - Mapがイテレータを持つ値なのでfor...of文を使う事ができます。 ## entriesメソッドを使ったときとMapに直接反復処理を使ったときの違いについて 結果は同じですが違いに関して検証してみました。 **map.entries()の場合** ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const entries = []; for (const [key, value] of map.entries()) { console.log(map.entries()); // => [object Map Iterator] [object Map Iterator] entries.push(`${key}:${value}`); } ``` **mapに対して直接for...of文を使った場合** ```javascript const map = new Map([["key1", "value1"], ["key2", "value2"]]); const results = []; for (const [key, value] of map) { console.log(map); // => [object Map] [object Map] results.push(`${key}:${value}`); } ``` - map.entriesとmapをconsole.logして出力しています。 - 返り値がイテレータオブジェクトかMapオブジェクトから取り出した値かの違いがあります。 - どちらの場合も配列をリテラルを用いた分割代入ができるので結果は同じになります。 ## 参考 [マップの反復処理](https://jsprimer.net/basic/map-and-set/#map-iteration) # 2022年3月4日 [ES2015] Map/Set - マップとしてのObjectとMap (JavaScript Primer) ES2015でMapが導入されるまで、JavaScriptにおいてマップ型を実現するためにObjectが利用されてきました。 ただし、"マップとしてのObject"にはいくつかの問題があります。 - Objectのprototypeオブジェクトから継承されたプロパティによって、意図しないマッピングを生じる危険性がある - プロパティとしてデータを持つため、キーとして使えるのは文字列かSymbolに限られる [マッピングとは](https://wa3.i-3-i.info/word1347.html) →2つの要素を関連付けること 以下はMapオブジェクトのような動きをオブジェクトで表現しています。 prototypeオブジェクトから継承したプロパティと衝突することもわかります。 ```javascript const map = {}; // マップがキーを持つことを確認する function has(key) { return typeof map[key] !== "undefined"; } console.log(has("foo")); // => false // Objectのプロパティが存在する console.log(has("constructor")); // => true ``` - typeofを使う事でプロパティがなかった場合に`undefined` を返します。 - hasメソッドにプロパティ名を渡すとそのプロパティが存在するかを真偽値で返します。 - 存在しないプロパティ名を渡すとundefinedを返します。 - オブジェクトのプロパティにブラケット記法でアクセスする場合ブラケット記法は文字列リテラルで囲んで渡す必要があります。 衝突に関してはprototypeオブジェクトを継承しないオブジェクトをObject.create(null)で(初期化)作成することで回避出来ます。 上記のprototype内部プロパティに定義されているプロパティと衝突する問題を回避し、上記のようなよく使うであろうメソッドをはじめから定義したMapをES2015からビルトインオブジェクトとして導入されました。 その他のMapを導入したことのメリットが3つあります。 - マップのサイズを簡単に知ることができる - マップが持つ要素を簡単に列挙できる - オブジェクトをキーにすると参照ごとに違うマッピングができる これらのメリットの例としてショッピングカートのコードがありました。 ```javascript // ショッピングカートを表現するクラス class ShoppingCart { constructor() { // 商品とその数を持つマップ this.items = new Map(); } // カートに商品を追加する addItem(item) { // `item`がない場合は`undefined`を返すため、Nullish coalescing演算子(`??`)を使いデフォルト値として`0`を設定する const count = this.items.get(item) ?? 0; this.items.set(item, count + 1); } // カート内の合計金額を返す getTotalPrice() { return Array.from(this.items).reduce((total, [item, count]) => { return total + item.price * count; }, 0); } // カートの中身を文字列にして返す toString() { return Array.from(this.items).map(([item, count]) => { return `${item.name}:${count}`; }).join(","); } } const shoppingCart = new ShoppingCart(); // 商品一覧 const shopItems = [ { name: "みかん", price: 100 }, { name: "リンゴ", price: 200 }, ]; // カートに商品を追加する shoppingCart.addItem(shopItems[0]); shoppingCart.addItem(shopItems[0]); shoppingCart.addItem(shopItems[1]); // 合計金額を表示する console.log(shoppingCart.getTotalPrice()); // => 400 // カートの中身を表示する console.log(shoppingCart.toString()); // => "みかん:2,リンゴ:1" ``` **前提** - ショッピングカートを表現するShoppingCartクラスを定義しています。 - コンストラクタ関数内でMapをインスタンス化しthis.itemsに代入します。 - const shoppingCart変数に代入のところで、ShoppingCartをインスタンス化した時にコンストラクタ関数が一度だけ実行されます。 - this.itemsはMapの持つメソッドが使えます。 - shopItems変数に配列で2つのオブジェクトを持たせています。 **addItemメソッド** - 実引数はshopItems変数のインデックス番号で要素を指定しています。 - Nullish coalescing演算子(??)は左辺が`null`か`undefined`のときに右辺を返します。 - this.items.get(item)はインスタンス化されたMapのエントリーに対してgetメソッドに引数のキーを指定してバリューを取り出します。 - 1度目の実行時にcount変数に代入されるのは、エントリーに何もない状態なので`undefined`になるため右辺の0になります。 - this.items.set(item, count + 1)ではsetメソッドで空のMapのエントリーに要素が追加されます。 - 2度目の実行時はcount変数に1度目の結果のエントリーのキーをgetメソッドで指定できるため、バリューのcountの1が代入されます。 - 2度目のsetメソッド実行時はキーが1度目と同じためバリューが更新されます。 - 3度目はshopItemsのインデックス番号1の要素で実行されます。 **getTotalPriceメソッド** - 前提としてaddItemメソッドの結果値がthis.itemsのエントリーにあります。 - addItemメソッドの結果値を元に合計金額を返すメソッドです。 - Array.fromの返り値this.itemsを配列化したもので以下ように出力されます。 ```javascript [ [ { name: 'みかん', price: 100 }, 2 ], [ { name: 'リンゴ', price: 200 }, 1 ] ] ``` - reduceメソッドで第一引数にコールバック、第二引数に初期値の0を設定し実行しています。 - コールバック関数には第一引数に初期値の0が入り、第二引数にエントリ内のオブジェクト(上のコード参照)とエントリ内の個数が順番に渡されます。 - オブジェクトのpriceにアクセスし、total+個数と掛け算した結果がtotalに代入され、繰り返します。 - 繰り返し処理の結果として400が返ります。(みかん100円が2個とりんご200円が1個) **toSrtingメソッド** - ショッピングカート内を文字列で表現するメソッドです。 - Array.fromの返り値は先程と同じく以下のようになります。 ```javascript [ [ { name: 'みかん', price: 100 }, 2 ], [ { name: 'リンゴ', price: 200 }, 1 ] ] ``` - mapのコールバック関数には第一引数にエントリ内のオブジェクト(上のコード参照)と第二引数にエントリー内の数値(個数)が順番に渡されます。 - テンプレートリテラルを使いオブジェクトのnameにアクセスし、カートの商品名とcountである個数を出力します。 - joinメソッドは配列を連結し、間にjoinの引数に指定した文字を挿入します。 - 結果的に"みかん:2,リンゴ:1"が返ります。 ## 参考 [マップとしてのObjectとMap ](https://jsprimer.net/basic/map-and-set/#object-and-map) # 2022年3月7日 [ES2015] Map/Set - マップとしてのObjectとMap (JavaScript Primer) マップとしてのObjectの利点 ## マップとしてのObjectの利点 > Objectをマップとして使うときに起きる多くの問題は、Mapオブジェクトを使うことで解決しますが、 常にMapがObjectの代わりになるわけではありません。 マップとしてのObjectには次のような利点があります。 - 多くの場合はMapオブジェクトを使うことで解決します。 - MapがObjectの代わりにならないことがあります。 ### マップとしてのObjectには次のような利点 - リテラル表現があるため作成しやすい - 規定のJSON表現があるため、JSON.stringify関数を使ってJSONに変換するのが簡単である - ネイティブAPI・外部ライブラリを問わず、多くの関数がマップとしてObjectを渡される設計になっている > 次の例では、ログインフォームのsubmitイベントを受け取ったあと、サーバーにPOSTリクエストを送信しています。 サーバーにJSON文字列を送るために、JSON.stringify関数を使います。 そのため、Objectのマップを作ってフォームの入力内容を持たせています。 このような簡易なマップにおいては、Objectを使うほうが適切でしょう。 MapではなくObjectを使用した方が良い例 ```javascript // URLとObjectのマップを受け取ってPOSTリクエストを送る関数 function sendPOSTRequest(url, data) { // XMLHttpRequestを使ってPOSTリクエストを送る const httpRequest = new XMLHttpRequest(); httpRequest.setRequestHeader("Content-Type", "application/json"); httpRequest.send(JSON.stringify(data)); httpRequest.open("POST", url); } // formのsubmitイベントを受け取る関数 function onLoginFormSubmit(event) { const form = event.target; const data = { userName: form.elements.userName, password: form.elements.password, }; sendPOSTRequest("/api/login", data); } ``` サーバーにJSON文字列を送る際に、オブジェクトでマッピングしたdataを引数にJSON.stringify()を実行します。 XMLHttpRequestはJavaScriptを使って、ブラウザとサーバー間で非同期通信でデータの送受信を可能にするオブジェクトです。 このオブジェクトは様々なメソッドを保有しており、それらのメソッドを使うことで、サーバーとのやり取りを行うことができます。 #### XMLHttpRequest.setRequestHeader() HTTP リクエストヘッダーの値を設定します。 - 第一引数に値を設定するヘッダーの名前 - 第二引数にヘッダー本体として設定する値 #### XMLHttpRequest.send() send() メソッドは、リクエストをサーバーに送信します。 - 引数にリクエストの本文を指定(ここでいうとJSON) #### XMLHttpRequest.open() 新しく作成されたリクエストを初期化したり、既存のリクエストを再初期化したりします。 - 第一引数にHTTPリクエストメソッド - 第二引数にリクエストを送信するURL ## ポイント - [マッピングとは](https://wa3.i-3-i.info/word1347.html) 2つの要素を関連付けること - MapにはMapリテラルがない - 今回のコードをMapで表現する場合、少なくともMapオブジェクトをNewする必要があり、複雑なコードになってしまう。 今回のJS primerのコード例ではMapを使わずにオブジェクトでMapを表現した方が良い例でしたが、Mapで書くとどうなってしまうのかと思考し、書いてみました。 マップとしてのObjectの書き方 ```javascript const data = { userName: form.elements.userName, password: form.elements.password, }; console.log(JSON.stringify(data)); ``` Map ```javascript const mapData = new Map ([["userNameKey", { userName: form.elements.userName }], ["passwordKey", { password: form.elements.password }]]); console.log(mapData); console.log(mapData.get("userNameKey")); const data = mapData.get("userNameKey"); console.log(JSON.stringify(data)); ``` - Mapにはリテラルがないのでnew演算子でインスタンス化しています。 - エントリーの値をオブジェクトにし、getメソッドで取り出しています。 - 取り出した値にJSON.stringfyメソッドを使ってJSON形式にしています。 - 今回の例だと、Mapを使わずにマップとしてのObjectを使用した方がわかりすいです。 ## 末尾のカンマ オブジェクトを代入する際にプロパティに末尾のカンマの必要性などが気になったため調べたところ、MDNに以下の記述がありました。 > 末尾のカンマ (「最後のカンマ」と呼ばれることもあります) は、JavaScript のコードに新しい要素や引数、プロパティを追加するときに役立ちます。新しいプロパティを追加するとき、最終行ですでに末尾のカンマを使用していれば、最終行を修正することなく新しい行を追加できます。これによって、バージョン管理の差分がより洗練され、コード編集の煩雑さを軽減できます。 ## 参考 [マップとしてのObjectとMap](https://jsprimer.net/basic/map-and-set/#object-and-map) # 2022年3月8日 [ES2015] Map/Set - WeakMap (JavaScript Primer) WeakMap ## WeakMap WeakMapは、Mapと同じくマップを扱うためのビルトインオブジェクトです。 Mapと違う点は、キーを弱い参照(Weak Reference)で持つことです。 **弱い参照とは** ガベージコレクションによるオブジェクトの解放を妨げない特殊な性質です。 メモリ管理の仕組みで学んだ、変数の値の結果を参照するものがあればガベージコレクションから解放されないという特性が、weakMapではあえて働かないという事です。 - キーに紐づいた値も削除されます。 - メモリリークを起こしません。 **メモリリークとは** メモリリークとは、コンピュータで実行中のプログラムが確保したメモリ領域の解放を忘れたまま放置してしまうこと。 動作の不具合を招くバグ(欠陥)の一種 - 花見での場所取りをイメージするとわかりやすいです。 - 誰か1人が敷きシートを複数枚持っており、場所取りをしました。 - その人は、その敷きシートを敷いてまま別の場所で敷きシートをして花見をしています。 - 最初の敷きシート場所は使っていないのに他の方が使えませんし、みんなの使える場所が減ってしまいました。 > 次のコードでは、最初にobjには{}を設定し、WeakMapではそのobjをキーにして値("value")を設定しています。 次にobjに別の値(ここではnull)を代入すると、objが元々参照していた{}という値はどこからも参照されなくなります。 このときWeakMapは{}への弱い参照を持っていますが、弱い参照はGCを妨げないため、{}は不要になった値としてGCによりメモリから解放されます。 > 同時に、WeakMapは解放されたオブジェクト({})をキーにしてひもづいていた値("value")を破棄できます。 ただし、どのタイミングで実際にメモリから解放するかは、JavaScriptエンジンの実装に依存します。 ```javascript const map = new WeakMap(); // キーとなるオブジェクト let obj = {}; // {} への参照をキーに値をセットする map.set(obj, "value"); // {} への参照を破棄する obj = null; // GCが発生するタイミングでWeakMapから値が破棄される ``` - {}はnullが再代入された時点でWeakMapから弱い参照を持っているが、メモリから解放されます。 - {}に紐づいていた値("value")も破棄されます。 - 実際のメモリから解放されるタイミングはJavaScriptエンジンの実装に依存します。 **WeakMapはイテレータの性質を持つオブジェクトではありません。** - keyを列挙するkeysメソッドやsizeプロパティは存在しません。 - エントリーを複数持てません。 - keysメソッドを出力するとイテレーターオブジェクトなので、イテラブルなオブジェクトでないWeakMapに存在するはずがありません。 **要素を複数追加しようとするとTypeErrorが発生しました。** ```javascript const wMap = new WeakMap(); wMap.set("key1","value1"); wMap.set("key2","value2"); TypeError: Invalid value used as weak map key ``` ## WeakMapを使う主なケース クラスにプライベートの値を格納します。 > this (クラスインスタンス) を WeakMap のキーにすることで、インスタンスの外からはアクセスできない値を保持できます。 また、クラスインスタンスが参照されなくなったときには自動的に解放されます。 > WeakMapの主な使い方のひとつは、クラスにプライベートの値を格納することです。 this (クラスインスタンス) を WeakMap のキーにすることで、インスタンスの外からはアクセスできない値を保持できます。 また、クラスインスタンスが参照されなくなったときには自動的に解放されます。 > 次のコードでは、オブジェクトが発火するイベントのリスナー関数(イベントリスナー)を WeakMap で管理しています。 イベントリスナーとは、イベントが発生したときに呼び出される関数のことです。 このマップをMapで実装してしまうと、明示的に削除されるまでイベントリスナーはメモリ上に残り続けます。 ここでWeakMapを使うと、addListener メソッドに渡されたlistenerは EventEmitter インスタンスが参照されなくなった際、自動的に解放されます。 ```javascript // イベントリスナーを管理するマップ const listenersMap = new WeakMap(); class EventEmitter { addListener(listener) { // this にひもづいたリスナーの配列を取得する const listeners = listenersMap.get(this) ?? []; const newListeners = listeners.concat(listener); // this をキーに新しい配列をセットする listenersMap.set(this, newListeners); } } // 上記クラスの実行例 let eventEmitter = new EventEmitter(); // イベントリスナーを追加する eventEmitter.addListener(() => { console.log("イベントが発火しました"); }); // eventEmitterへの参照がなくなったことで自動的にイベントリスナーが解放される eventEmitter = null; ``` - addListenerの引数としてリスナー関数を渡します。 - weakMapではなくMapで定義すると強い参照になるため、nullが再代入されて出力されなくなったイベントリスナーがメモリ上に残ってしまいます。 - エントリーのキーにthisをセットしているため、インスタンスの外からエントリーの値にアクセスすることはできないためプライベートな値を格納できています。 - concatは配列の末尾に配列の要素を結合するメソッドです。 - listenersMap.set()でWeakMapのエントリーのキーにthis、valueにイベントリスナーを追加した配列をエントリーに追加し、イベントが実行されるたびにWeakMapのvalue要素が更新される実装になっています。 - setでthisを使ってインスタンス変数をキーにして参照している点がポイントです。 - eventEmiterに別の値を再代入することでweakMapでは弱い参照になるため自動的にイベントリスナーが解放されます。 ## WeakMapを使う主なケース2 また、あるオブジェクトから計算した結果を一時的に保存する用途でもよく使われます。 次の例ではHTML要素の高さを計算した結果を保存して、2回目以降に同じ計算をしないようにしています。 ```javascript const cache = new WeakMap(); function getHeight(element) { if (cache.has(element)) { return cache.get(element); } const height = element.getBoundingClientRect().height; // elementオブジェクトに対して高さをひもづけて保存している cache.set(element, height); return height; } ``` - このコードは何度実行しても結果値は同じです。 - weakMapを使うことで実行されるたびにメモリから破棄されていくのでメモリリークが起こりません。 - 2回目以降はif文がtrueとなるためif文の中身が実行され1度目の結果値が返ります。 #### Element.getBoundingClientRect() イメージしにかったため[Qiita記事](https://qiita.com/akkiii/items/de0b6d79ac4e216a15f0#getboundingclientrect%E3%81%AE%E6%83%85%E5%A0%B1%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B)のコードを拝借しました。 要素(Element)に対してgetBoundingClientRect()を使用するとその要素の高さや座標などの情報が返されます。 ```javascript let image = (<HTMLAnchorElement>document.getElementById("image")); let image_domRect = image.getBoundingClientRect(); console.log(image_domRect) ``` 以下のような返り値が得られます。 ```javascript DOMRect {x: 677.484375, y: 269, width: 143.015625, height: 15, top: 269, …} bottom: 284 height: 15 left: 677.484375 right: 820.5 top: 269 width: 143.015625 x: 677.484375 y: 269 __proto__: DOMRect ``` ## [コラム] キーの等価性とNaN Mapに値をセットする際のキーにはあらゆるオブジェクトが使えます。 このときのマップが特定のキーをすでに持っているか、つまり挿入と上書きの判定は基本的に===演算子と同じです。 ただし、キーがNaNの扱いだけが例外的に違います。Mapにおけるキーの比較では、NaN同士は常に等価であるとみなされます。 この挙動はSame-value-zeroアルゴリズムと呼ばれます。 次のコードでは、NaN同士の===の比較結果がfalseになるのに対して、MapのキーではNaN同士の比較結果が一致していることがわかります。 ```javascript const map = new Map(); map.set(NaN, "value"); // NaNは===で比較した場合は常にfalse console.log(NaN === NaN); // => false // MapはNaN同士を比較できる console.log(map.has(NaN)); // => true console.log(map.get(NaN)); // => "value" ``` - Mapの場合keyにアクセスする際は暗黙的に厳密等価演算子で判定されています。 - Mapを使わずにNaNを厳密等価演算子で比較するとfalseになります。 - NaNはMapのキーにして比較するとtrueを返すので各メソッドが使用可能です。 **復習** 再認識するため本ブログの過去記事からNaNのまとめを持ってきました。 ![](https://i.imgur.com/Ba8MgHD.png) - 以上の知識を踏まえて今回のコラムを読むとしっかり理解できました。 ## 参考 [WeakMap](https://jsprimer.net/basic/map-and-set/#weakmap) [2021年10月12日 JavaScript (JavaScript Primer) データ型とリテラル2](https://morning-6.hatenablog.com/entry/2021/10/12/100856) # 2022年3月10日 [ES2015] Map/Set - Set - 値の追加と取り出し (JavaScript Primer) ## 復習 #### WeakMapの認識が甘かった箇所 - Mapと違いキーには参照型のオブジェクトのに使用できるためプリミティブ型だとエラーになります。 #### ラッパーオブジェクト - プリミティブ型に対してプロパティを呼び出すタイミングで働きます。 ## Set Setはセット型のコレクションを扱うためのビルトインオブジェクトです。 セットは重複する値がないことが保証されたコレクションのことを言います。 > Setは追加した値を列挙できるので、値が重複しないことを保証する配列のようなものとしてよく使われます。 値の重複がなく配列のインデックスで要素にアクセスできないものというイメージです。 **配列とSetの違い - new演算子でインスタンスを生成することができます。 ```javascript const set = new Set(); console.log(set.size); // => 0 ``` 文字列もイテラブルのためセットとして扱うことも出来ます。 以下の例では"o"が重複しているためsizeが4として保存されています。 ```javascript const set = new Set("yanoooo"); console.log(set.size); // => 4 console.log(set); // => [object Set] ←中身は 'y','a','n','o'となっていてpaizeでは出力されます。 ``` set自体は以下のように状態を持っていることがわかります。 ![image](https://user-images.githubusercontent.com/78721963/157565374-e1dfef8e-28d8-4aad-873a-c8f53f8ab968.png) ## はじめから用意されているイテラブルなオブジェクト - Array - String - iterator - ジェネレータ - Arguments - TypedArray - Map - Set ## 参考 [Set](https://jsprimer.net/basic/map-and-set/#set) # 2022年3月11日 [ES2015] Map/Set - Set (JavaScript Primer) 値の追加と取り出し ## 値の追加と取り出し - 作成したセットに値を追加するにはaddメソッドを使います。 - 重複する値は持たないので、既存の値にaddメソッドを使っても無視されます。 - セットが特定の値を持っているかの真偽値を得るのにhasメソッドがあります。   ```javascirpt const set = new Set(); // 値の追加 set.add("a"); console.log(set.size); // => 1 // 重複する値は追加されない set.add("a"); console.log(set.size); // => 1 // 値の存在確認 console.log(set.has("a")); // => true console.log(set.has("b")); // => false ``` ## Setの値を削除する - deleteメソッドに渡された値がセットから削除されます。 - 全ての値を削除するためのclearメソッドがあります。 ```javascript const set = new Set(); set.add("yano"); set.add("yano2"); console.log(set.size); // => 2 set.delete("yano"); console.log(set.size); // => 1 set.clear(); console.log(set.size); // => 0 ``` ## セットの反復処理 セットが持つ値を反復処理する例です。 - setが持つ要素を一つずつresultsに追加しています。 ```javascript const set = new Set(["やの", "やの2"]); const results = []; set.forEach((value) => { results.push(value); }); console.log(results); // => ["やの","やの2"] ``` ## セット自身がIteratorオブジェクトのため必要ありませんがMapとの類似性のためにkeys、values、entriesメソッドがあります。(使われることはほぼないでしょう) ```javascript const set = new Set(["a", "b"]); // keysで列挙 const keysResults = []; for (const value of set.keys()) { keysResults.push(value); } console.log(keysResults); // => ["a","b"] // entriesで列挙 const entryResults = []; for (const entry of set.entries()) { // entryは[値, 値]という配列 entryResults.push(entry); } console.log(entryResults); // => [["a","a"], ["b", "b"]] ``` - セットはキーを持っていませんがkeysメソッドは存在します。 - keysメソッドとvaluesメソッドはエイリアスでSetが持つ全ての値を持つIteratorオブジェクトを返します。 - entriesメソッドはSetにキーとバリューという概念がないにも関わらずentriesメソッドを使うとキーとバリューとして同じ値が出力されます。 Setオブジェクト自身もiterableなオブジェクトのため、反復処理することができます。 ```javascript const yano = new Set(["touhu", "nattou", "kimuti"]); const reizouko = []; for (const meshi of yano) { reizouko.push(meshi); } console.log(reizouko); // => [ 'touhu', 'nattou', 'kimuti' ] ``` - Setがiterableなオブジェクトのためfor...of文をsetに直接使っています。 - これができるため、先述した3つのメソッドは必要ありません。 ## WeakSet 特徴 - オブジェクトしか渡せません。 - iterableではないので追加した値を反復処理できません。 - WeakSetは値の追加と削除、存在確認以外のことができません。 - 同じ要素を省いて保存するため、データの一意性を確認することに特化したセットです。 ```javascript const set = new WeakSet(); const yuhan = { meshi: "nabe" }; set.add(yuhan); console.log(set); // => WeakSet { <items unknown> } ``` 結果は出力されませんでしたが、検証ツールでentries内部プロパティの中身を見たら値がありました。 ![](https://i.imgur.com/5Hl2pQH.png) - WeakSetの用途は循環参照を検出する際に使われるようです。 ## まとめ この章ではMapとSetについて学びました。 - Mapはキーと値の組み合わせからなるコレクションを扱うビルトインオブジェクト - Mapのキーはプロトタイプオブジェクトのプロパティと名前が衝突しないため意図しないマッピングを避けられる - WeakMapはキーを弱い参照で持つMapと同様のビルトインオブジェクト - Setは重複する値がないことを保証した順序を持たないコレクションを扱うビルトインオブジェクト - WeakSetは値を弱い参照で持つSetと同様のビルトインオブジェクト ## 参考 [Set - 値の追加と取り出し(JavaScript Primer)](https://jsprimer.net/basic/map-and-set/#set-read-and-write) # 2022年3月12日 [番外編]技術的な雑談でリテラシーを高めた ## API・ソフトウェアについて Application Programming Interfaceの略です。 - ソフトウェアの機能を共有できる仕組みのこと。 - ソフトウェアとは1・・・コンピュータのうち蹴っ飛ばせない部分 - ソフトウェアとは2・・・ソフトやファイルなど人間様の手では触れない部分 [参照](https://wa3.i-3-i.info/word131.html) よく使われているものだとGoogle Mapが有名です。 フォームなどで郵便番号を入れると自動で入力されるなんてことができるのは大体APIから取得しています。 APIは通常Webに公開されていて、誰でも無料で使うことが可能。「WebAPI」と呼ばれたりもします。 一般的にJSONという形式で結果を返します。 RailsでAPIを実装する場合、RailsのAPIモードを使用します。JSONを返すAPI機能を作成し、フロントとやり取りを行います。 ## SaaSとは みんな大好き勝又○太さんの「Web系エンジニアになろう」には以下のように書かれていました。 >利用者のパソコンやサーバに直接ソフトウェアをインストールするのではなく、インターネット経由でソフトウェアの機能を提供するサービスです。 GithubやGmail等もSaaSに含まれます つまり、インターネット経由でソフトウェアの機能を提供するサービスのことをSaaSと言います。 以下に、Saasの代表的な例を紹介します。 - Microsoft 365・・・ビジネス向け総合パッケージ - Salesforce・・・SFA(営業支援システム)・CRM(顧客管理システム) - ServiceNow・・・複数システムのデータを集約し、統合的な運用管理の実現するITSM(ITサービスマネジメント)ツール - freee、マネーフォワード・・・クラウド会計ソフト - Zoom・・・オンライン会議ツール - Slack、Chatwork・・・ビジネスチャットツール - マネーフォワード IT管理クラウド・・・複数のSaaSを一元管理するSaaSマネジメントプラットフォーム - Gmail・・・Webメール - Canva・・・オンライングラフィックデザインツール ## 技術質問の記事から曖昧な箇所を学習しました ### PHPとRubyの違い **結論** - 言語の違いがある。したがって構文が全く異なる。どちらも動的型付け言語のサーバーサイドのプログラミング言語である。 - 目的の違いがある。PHPはWordPressでのでWeb制作にもよく用いられ、ホームページ制作を最適化する。Rubyはストレスなくプログラミングを楽しむため。 - 当然だがフレームワークの違いがある。PHPは「CakePHP」、「Phalcon」、「Laravel」があり、Rubyは基本的には「Ruby on Rails」一択。 ### Railsが支持される理由 他の言語のフレームワークは設定ファイルに事細かにアプリケーションの定義を記載していくものが多いのに対し Railsが画期的だったのは「同じことを繰り返さない」「設定より規約」といった思想のもとに開発されたフレームワークという点。 保守性や開発効率を考えると、影響範囲の特定などにコストをとられずに済む。 予め決めておいた規約のとおりに、コーディングすることで設定する項目を劇的に減らすことに成功した。 ## クラス継承とプロトタイプ継承の違い プロトタイプ継承を行う方法 - クラスを継承してプロトタイプすべてを継承する - __proto__でプロトタイプのプロパティを指定して継承する #### クラスを継承してプロトタイプ継承をする方法 子クラスのインスタンスから親クラスのプロトタイプメソッドもプロトタイプチェーンの仕組みによって呼び出せます。 extendsによって継承した場合、子クラスのプロトタイプオブジェクトのPrototype内部プロパティには親クラスのプロトタイプオブジェクトが設定されます。 このコードでは、Child.prototypeオブジェクトのPrototype内部プロパティにはParent.prototypeが設定されます。 ```javascript class Parent { method() { console.log("Parent#method"); } } // `Parent`を継承した`Child`を定義 class Child extends Parent { // methodの定義はない } const instance = new Child(); instance.method(); // "Parent#method" ``` - extendsを使ってプロトタイプを継承しているからChildにメソッドの定義はなくてもParentのメソッドが使用できています。 #### __proto__を使ってプロトタイプ継承をする 変数の中で継承しているパターン ```javascript let yano = { meshi: "kimuti" }; let yui = { __proto__: yano }; console.log(yui.meshi); ``` 変数の外で定義しているパターン ```javascript let yui = {}; let yano = { meshi: "kimuti" }; let yuno = { meshi: "Tom yum goong", skill: "React" }; yui.__proto__ = yano; yano.__proto__ = yuno; console.log(yui.meshi); // => kimuti console.log(yui.skill); // => React ``` - \__proto__を使用してプロトタイプ継承をしています。 - プロトタイプチェーンの仕組みが働いているのでyuiにないプロパティをyanoに探しに行くという挙動が生まれています。 ![](https://i.imgur.com/zmT52tB.png) 検証ツールで変数yuiの中身を見てみました。 Prototype内部プロパティに継承されたオブジェクトがあるのがわかります。 ### RubyとJSで同じclassという単語を使用していますが、概念の面では、以下のような違いがあります。 ![](https://i.imgur.com/lHbINJ9.png) **Ruby** - Rubyでは**クラスベース**のオブジェクト指向が使われていますが、JavaScriptでは**プロトタイプベース**のオブジェクト指向を使用しています。 - Rubyの世界でクラスとは、クラスという金型を利用して、鯛焼きというインスタンスを量産できるオブジェクトのことです。ベースとなるクラスの属性を持ったインスタンスを量産できるので便利です。最初から鯛焼きの形を作るところから始める必要がなくなります。 - Rubyの世界では全てのものをオブジェクトとして、扱います。それはクラスも然りです。 - Rubyのクラス(自作のクラスも含む)は実はClassクラスのインスタンスです。 - オブジェクトの階層は、上に上に行くほど深い階層になっているイメージです。これは、JavaScriptと逆の方向になります。 ![](https://i.imgur.com/rdCjaLn.png) ![](https://i.imgur.com/vQnHnwR.png) **JavaScript** - クラスを使わず、同じ機能や性質を持ったオブジェクトを量産するためにJavaScriptでは**プロトタイプ**を採用しています。 - JavaScriptでは、自分が持っていない特性を別のオブジェクトにお願いするイメージです。そのお願いをする別のオブジェクトを**プロトタイプ**と言います。 - オブジェクトの階層は、下に下に深掘りしていくイメージです。下に穴を掘るほどオブジェクトの元のオブジェクトに繋がっていきます。 ## 参考 [SaaSとは――同じクラウドサービスでもPaaS、IaaSとは何が違うのか](https://shiftasia.com/ja/column/saas%E3%81%A8%E3%81%AF/) [最強オブジェクト指向言語 JavaScript 再入門!](https://www.slideshare.net/yuka2py/javascript-23768378) # 2022年3月14日 JSON (JavaScript Primer) ## JSON - JavaScript Object Notationの略です。 - JSONは、データ送信に用いる文字列フォーマットです。 - JavaScriptのオブジェクトリテラルをベースに作られた軽量なデータフォーマットです。 - Notation(ノーテーション)は表記という意味です。 - オブジェクトリテラルのキーをダブルクォートで囲う必要があります。 - JavaScriptのオブジェクトリテラル、配列リテラル、各種プリミティブ型の値を組み合わせたものです。 - 人間にとって読み書きが容易でマシンにとってパースや生成が容易です。 - 多くのプログラミング言語にJSONを扱う機能があります。 以下がJSONの例です。 ```javascript { "object": { "number": 1, "string": "js-primer", "boolean": true, "null": null, "array": [1, 2, 3] } } ``` ## JSON文字列をオブジェクトに変換する ```javascript const json = '{ "id": 1, "name": "yano" }'; const obj = JSON.parse(json); console.log(obj.name); // => "yano" ``` JSON.parseメソッドに配列のJSONを渡すことで配列を返しています。 ```javascript const json = "[1, 2, 3]"; console.log(JSON.parse(json)); // => [1, 2, 3] ``` オブジェクトリテラルの中をダブルクォーテーションを使用した場合、オブジェクトリテラルを囲うのはシングルクォーテーションにしないとエラーになります。 もし外側もダブルクォーテーションで囲うなら以下の例のようにエスケープしなければいけません。 ```javascript // エスケープした記述例 const json = "{\"id\": 1, \"name\": \"js-primer\" }"; // シングルクォートで解決 const json = '{ "id": 1, "name": "js-primer" }'; ``` このような記述を避けるためにシングルクォーテーションを使用すると良いです。 #### JSON.parseメソッドは基本的にtry...catch構文を使用する 実際のアプリケーションJSONの主な用途として、外部のプログラムとデータを交換する用途がほとんどです。 外部から送信されたデータが正しいJSONフォーマットあるとは限りません。 そのため、例外をキャッチする処理を用意しておくことが好ましいでしょう。 ```javascript const userInput = "not json value"; try { const json = JSON.parse(userInput); } catch (error) { console.log("パースできませんでした"); } ``` ## オブジェクトをJSON文字列に変換する - JSON.stringifyメソッドは第一引数に与えられたオブジェクトをJSON形式の文字列に変換して返す関数です。 - parseメソッドの逆の挙動をします。 ```javascript const obj = { id: 1, name: "js-primer", bio: null }; console.log(JSON.stringify(obj)); // => '{"id":1,"name":"js-primer","bio":null}' ``` JavaScriptのオブジェクトをJSON形式の文字列に変換する例です。 valueがnullのプロパティを除外してJSONを返しています。 ```javascript const obj = { id: 1, name: "js-primer", bio: null }; const replacer = (key, value) => { if (value === null) { return undefined; } return value; }; console.log(JSON.stringify(obj, replacer)); // => '{"id":1,"name":"js-primer"}' ``` - 関数を渡した場合は引数にプロパティのキーと値が渡され、その返り値によって文字列に変換される際の挙動をコントロールできます。 - replacer関数でnullを除外する操作をした上でのobjをJSON形式にしたものが返り値になります。 - 第二引数の関数であるreplacerでは繰り返し処理が行われます。 #### JSON.stringifyメソッドの第二引数を配列として渡す場合 ホワイトリストとして扱われます。 以下の例では配列の要素に、JSONとして渡したいオブジェクトのキーを指定しています。 ```javascript const obj = { id: 1, name: "js-primer", bio: null }; const replacer = ["id", "name"]; console.log(JSON.stringify(obj, replacer)); // => '{"id":1,"name":"js-primer"}' ``` #### JSON.stringifyメソッドの第三引数(space引数) 変換後のJSON形式の文字列を読みやすくフォーマットする際のインデントを設定できます。 インデントを数値で指定した例 ```javascript const obj = { id: 1, name: "js-primer" }; // replacer引数を使わない場合はnullを渡して省略するのが一般的です console.log(JSON.stringify(obj, null, 2)); /* { "id": 1, "name": "js-primer" } */ ``` タブ文字でインデントした例 ```javascript const obj = { id: 1, name: "js-primer" }; console.log(JSON.stringify(obj, null, "\t")); /* { "id": 1, "name": "js-primer" } */ ``` ## 参考 [JSON (JavaScript Primer)](https://jsprimer.net/basic/json/#json) # 2022年3月15日 JSON (JavaScript Primer)JSONにシリアライズできないオブジェクト ## JSONにシリアライズできないオブジェクト JSON.stringifyメソッドはJSONで表現可能な値だけをシリアライズします。 (※シリアライズとは、複数の要素を一列に並べる操作や処理のこと。) JSONに変換可能な値として紹介されている、文字列、数値、真偽値、配列、オブジェクトを出力しました。 ```javascript console.log(JSON.stringify({ x: "yano" })); console.log(JSON.stringify({ x: 10 })); console.log(JSON.stringify({ x: true })); console.log(JSON.stringify({ x: ["yano", 11] })); console.log(JSON.stringify({ x: { reizouko: "touhu" } })); ``` ▶{"x":"yano"} ▶{"x":10} ▶{"x":true} ▶{"x":["yano",11]} ▶{"x":{"reizouko":"touhu"}} |シリアライズ前の値|シリアライズ後の値| |---|---| |文字列・数値・真偽値|対応する値| |null|null| |配列|配列| |オブジェクト|オブジェクト| |関数|変換されない(配列のときはnull)| |undefined|変換されない(配列のときはnull)| |Symbol|変換されない(配列のときはnull)| |RegExp|{}| |Map,Set|{}| ```javascript // 値が関数のプロパティ console.log(JSON.stringify({ x: function() {} })); // => '{}' // 値がSymbolのプロパティ console.log(JSON.stringify({ x: Symbol("") })); // => '{}' // 値がundefinedのプロパティ console.log(JSON.stringify({ x: undefined })); // => '{}' // 配列の場合 console.log(JSON.stringify({ x: [10, function() {}] })); // => '{"x":[10,null]}' // キーがSymbolのプロパティ JSON.stringify({ [Symbol("foo")]: "foo" }); // => '{}' // 値がRegExpのプロパティ console.log(JSON.stringify({ x: /foo/ })); // => '{"x":{}}' // 値がMapのプロパティ const map = new Map(); map.set("foo", "foo"); console.log(JSON.stringify({ x: map })); // => '{"x":{}}' ``` - 配列の中にJSONに変換できないものが入っていたら`null` になります。 - 正規表現とMap,Setは列挙可能なプロパティを持たないため`{}` に変換されます。 ## JSON.stringifyメソッドがシリアライズに失敗する場合もあります 良くある例として循環参照しているオブジェクトに対してシリアライズしようとしたときに例外が投げられるケースです。 例のように、あるオブジェクトのプロパティを再帰的にたどって自分自身が見つかるような場合はシリアライズが不可能となります。 ```javascript const obj = { foo: "foo" }; obj.self = obj; try { JSON.stringify(obj); } catch (error) { console.error(error); // => "TypeError: Converting circular structure to JSON" } ``` このようなときのために例外処理をしておきましょう。 ## toJSONメソッドを使ったシリアライズ オブジェクトがtoJSONメソッドを持っている場合、JSON.stringifyメソッドは既定の文字列変換ではなくtoJSONメソッドの返り値を使います。 引数に直接渡さなくてもプロパティとしてtoJSONがある場合もtoJSONが返り値になります。 ```javascript const obj = { foo: "foo", toJSON() { return "bar"; } }; console.log(obj); // => { foo: 'foo', toJSON: [Function: toJSON] } console.log(JSON.stringify(obj)); // => '"bar"' console.log(JSON.stringify({ x: obj })); // => '{"x":"bar"}' ``` toJSONメソッドは自作のクラスを特殊な形式でシリアライズする目的などに使われます。 ## この章のまとめ この章では、JSONについて学びました。 - JSONはJavaScriptのオブジェクトリテラルをベースに作られた軽量なデータフォーマット - JSONオブジェクトを使ったシリアライズとデシリアライズ - JSON形式にシリアライズできないオブジェクトもある - JSON.stringifyはシリアライズ対象のtoJSONメソッドを利用する ## 参考 [JSONにシリアライズできないオブジェクト (JavaScript Primer)](https://jsprimer.net/basic/json/#not-serialization-object) ## 復習 # 2022年3月16日 Date (JavaScript Primer) ## 復習 #### オプショナルチェーン演算子とは 以下の二つのコードが同義です。 ```javascript obj?.value obj != null ? obj.value : undefined ``` - レシーバが`null` `undefined` のときにエラーではなく`undefined` を返します。 - 三項演算子で書くよりもリファクタリングができます。 ## Dateオブジェクト ECMAScriptで定義されたビルトインオブジェクトです。 - Dateにおける「時刻」は、UTC(協定世界時)の1970年1月1日0時0分0秒を基準とした相対的なミリ秒として保持されます。 - Dateオブジェクトのインスタンスはそれぞれがひとつの時刻値を持ち、その時刻値を元に日付や時・分などを扱うメソッドを提供します。 JSTとは 日本時間として普通に使っている時間 UTCとは 今の世界で標準時として使っている時間 (日本時間から9時間引いた時間) ## インスタンスの作成 Dateオブジェクトのインスタンスは、常にnew演算子を使って作成します。 2種類のインスタンス化パターンがあります。 #### 現在の時刻をインスタンス化 newするときにコンストラクタ引数を何も渡さない場合は、現在の時刻を表すインスタンスが作成されます。 ```javascript const now = new Date(); console.log(Date.now()); // => 1647384533248 // 時刻値を取得する console.log(now.getTime()); // => 1647384533248 // 時刻をISO 8601形式の文字列で表示する console.log(now.toISOString()); // =>2022-03-15T22:49:11.841Z ``` - getTimeメソッドはインスタンスが持つ時刻値を返します。 - [Date.now()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/now)はUTC (協定世界時) での 1970 年 1 月 1 日 0 時 0 分 0 秒 から現在までの経過時間をミリ秒単位で返します。(インスタンスではなくclassのメソッドです) - toISOStringメソッドは、時刻をUTCにおけるISO 8601形式の文字列に変換できます。 ISO 8601とは国際規格となっている文字列の形式で、2006-01-02T15:04:05.999+09:00のように時刻を表現します。(わかりやすいためよく使われます) #### 任意の時刻をインスタンス化 インスタンス生成時にコンストラクタ引数を渡すことで、任意の時刻を指定する3つの方法があります。 1. 時刻値を渡すもの 2. 時刻を示す文字列を渡すもの 3. 時刻の部分(年・月・日など)をそれぞれ数値で渡すも #### 1. 時刻値を渡すもの > コンストラクタ関数にミリ秒を表す数値型の引数を渡したときに適用されます。 渡した数値をUTCの1970年1月1日0時0分0秒を基準とした時刻値として扱います。 この方法は実行環境による挙動の違いが起きないので安全です。 また、時刻値を直接指定するので、他の2つの方法と違ってタイムゾーンを考慮する必要がありません。 ```javascript const now = Date.now(); console.log(now); // => 1647385122932 const now2 = new Date(now); console.log(now2); // => 2022-03-15T22:58:42.933Z const now3 = new Date(Date.now()); console.log(now3); // => 2022-03-15T22:58:42.933Z ``` - 実行タイミングによって数値は変わります。 - new Dateのコンストラクタ引数にDate.now()メソッドでミリ秒を取得し渡しています。 #### 2.ISO形式の文字列を引数に渡す ```javascript // UTCにおける"2006年1月2日15時04分05秒999"を表すISO 8601形式の文字列 const inUTC = new Date("2006-01-02T15:04:05.999Z"); console.log(inUTC.toISOString()); // => "2006-01-02T15:04:05.999Z" // 上記の例とは異なり、UTCであることを表す'Z'がついていないことに注意 // Asia/Tokyo(+09:00)で実行すると、UTCにおける表記は9時間前の06時04分05秒になる const inLocal = new Date("2006-01-02T15:04:05.999"); console.log(inLocal.toISOString()); // "2006-01-02T06:04:05.999Z" (Asia/Tokyoの場合) ``` - ZをつけることでUTCのタイムゾーンを指定しているので、つけないと実行環境に依存します。 #### 3. 時刻を次のように、年・月・日などの部分ごとの数値で指定する方法です。 ```javascript new Date(year, month, day, hour, minutes, seconds, milliseconds); ``` - 月は0〜11になる点がポイントです。 - 日付だけは1がデフォルトで渡されます。 - タイムゾーンを指定することが出来ないため、常にローカルのタイムゾーンの時刻とみなされ結果が実行環境に依存します。 この方法でUTCを指定するためには[Date.UTCメソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC)を使えば良いです。 ```javascript const ms = Date.UTC(2006, 0, 2, 15, 4, 5, 999); // 時刻値を渡すコンストラクタと併用する const date2 = new Date(ms); console.log(date2.toISOString()); // => "2006-01-02T15:04:05.999Z" ``` 不正な引数を渡した場合、以下のようにNaNが返されます。 (どのオーバーロードにも当てはまらない引数や、時刻としてパースできない文字列を渡した場合) オーバーロードとは・・・「引数や戻り値が異なるが名称が同一のメソッドを複数定義する」というオブジェクト指向プログラミングのテクニックである。 ```javascript const invalid = new Date(""); console.log(invalid.getTime()); // => NaN console.log(invalid.toString()); // => "Invalid Date" ``` ## Dateのインスタンスメソッド > Dateオブジェクトのインスタンスは多くのメソッドを持っていますが、 ほとんどはgetHoursとsetHoursのような、時刻の各部分を取得・更新するためのメソッドです。 > 次の例は、日付を決まった形式の文字列に変換しています。 getMonthメソッドやsetMonthメソッドのように月を数値で扱うメソッドは、0から11の数値で指定することに注意しましょう。あるDateのインスタンスの時刻が何月かを表示するには、getMonthメソッドの返り値に1を足す必要があります。 ```javascript // YYYY/MM/DD形式の文字列に変換する関数 function formatDate(date) { const yyyy = String(date.getFullYear()); console.log(typeof yyyy); // => string // Stringの`padStart`メソッド(ES2017)で2桁になるように0埋めする const mm = String(date.getMonth() + 1).padStart(2, "0"); console.log(mm); // => 01 const dd = String(date.getDate()).padStart(2, "0"); return `${yyyy}/${mm}/${dd}`; } const date = new Date("2006-01-02T15:04:05.999"); console.log(formatDate(date)); // => "2006/01/02" ``` - [String()コンストラクタ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/String)で引数の値を文字列化しています。 - [String.prototype.padStart()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/String)で第一引数で2桁を指定して2桁に満たないものは0で穴埋めしています。 #### getTimezoneOfsetメソッド 実行環境のタイムゾーンのUTCからのオフセット値を分単位の数値で返します。 オフセットとは・・・位置を基準点からの距離で表した値です。 ここではUTCと9時間差がありますのでそれを分単位にするため、返り値が540になります。それを○時間という単位で表すため60で割っています。 ```javascript // getTimezoneOffsetはインスタンスメソッドなので、インスタンスが必要 const now = new Date(); // 時間単位にしたタイムゾーンオフセット const timezoneOffsetInHours = now.getTimezoneOffset() / 60; // UTCの現在の時間を計算できる console.log(`Hours in UTC: ${now.getHours() + timezoneOffsetInHours}`); ``` ## 現実のユースケースとDate > ここまでDateオブジェクトとインスタンスメソッドについて述べましたが、 多くのユースケースにおいては機能が不十分です。 たとえば次のような場合に、Dateでは直感的に記述できません。 - 任意の書式の文字列から時刻に変換するメソッドがない - 「時刻を1時間進める」のように時刻を前後にずらす操作を提供するメソッドがない - 任意のタイムゾーンにおける時刻を計算するメソッドがない - YYYY/MM/DDのようなフォーマットに基づいた文字列への変換を提供するメソッドがない > そのため、JavaScriptにおける日付・時刻の処理は、標準のDateではなくライブラリを使うことが一般的になっています。 代表的なライブラリとしては、moment.jsやjs-joda、date-fnsなどがあります。 - Date自体は実際に使うとなると変換できなかったり操作できないのでライブラリを用いると良いでしょう。 以下、ライブラリを用いた例です。 ```javascript // moment.jsで現在時刻のmomentオブジェクトを作る const now = moment(); // addメソッドで10分進める const future = now.add(10, "minutes"); // formatメソッドで任意の書式の文字列に変換する console.log(future.format("YYYY/MM/DD")); ``` ## まとめ この章では、Dateオブジェクトについて学びました。 - Dateオブジェクトのインスタンスはある特定の時刻を表すビルトインオブジェクト - Dateにおける「時刻」は、UTC(協定世界時)の1970年1月1日0時0分0秒を基準とした相対的なミリ秒として保持されている - Dateコンストラクタで任意の時間を表すDateインスタンスを作成できる - Dateインスタンスメソッドにはさまざまなものがあるが、現実のユースケースでは機能が不十分になりやすい - ビルトインオブジェクトのDateのみではなく、ライブラリも合わせて利用するのが一般的 ## 参考 [Date (JSprimer)](https://jsprimer.net/basic/date/#date) [Date.now()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/now) [String()コンストラクタ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/String) [String.prototype.padStart()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/String) # 2022年3月17日 Math (JavaScript Primer) ## Mathオブジェクト JavaScriptで数学的な定数と関数を提供するビルトインオブジェクトです。 インスタンスは作成しないです。 ```javascript new Math(); ``` 結果: TypeError: Math is not a constructor ```javascript const rad90 = Math.PI * 90 / 180; const sin90 = Math.sin(rad90); console.log(sin90); // => 1 ``` ## 乱数を生成する randomメソッドは乱数の生成をします。 0以上1未満の範囲内で、疑似ランダムな浮動小数点数を返しています。 ```javascript for (let i = 0; i < 5; i++) { // 毎回ランダムな浮動小数点数を返す console.log(Math.random()); } ``` ## 数値の大小を比較する `Math.max`と`Math.min` で引数に渡した複数要素の最大と最小の値を出力しています。 ```javascript const numbers = [1, 2, 3, 4, 5]; console.log(...numbers); // => 1, 2, 3, 4, 5 console.log(Math.max(...numbers)); // => 5 console.log(Math.min(...numbers)); // => 1 ``` スプレッド構文は配列を展開することができます。 ```javascript console.log(...numbers); ``` 結果: 1, 2, 3, 4, 5 ## 数値を整数にする 整数を、返す関数です。 floorは繰り下げ、ceilは繰り上げ、roundは四捨五入です。 ```javascript // 底関数 console.log(Math.floor(1.3)); // => 1 console.log(Math.floor(-1.3)); // => -2 // 天井関数 console.log(Math.ceil(1.3)); // => 2 console.log(Math.ceil(-1.3)); // => -1 // 四捨五入 console.log(Math.round(1.3)); // => 1 console.log(Math.round(1.6)); // => 2 console.log(Math.round(-1.3)); // => -1 ``` Math.truncメソッドは小数点以下を単純に切り落とした整数を返します。 ```javascript // 単純に小数部分を切り落とす console.log(Math.trunc(1.3)); // => 1 console.log(Math.trunc(-1.3)); // => -1 ``` ## まとめ この章では、Mathオブジェクトについて学びました。 紹介したメソッドはMathオブジェクトの一部にすぎないため、そのほかにもメソッドが用意されています。 - Mathは数学的な定数や関数を提供するビルトインオブジェクト - Mathはコンストラクタではないためインスタンス化できない - 疑似乱数の生成、数値の比較、数値の計算などを行うメソッドが提供されている ## 参考 [Math(JSprimer)](https://jsprimer.net/basic/math/#math) # 2022年3月18日 [ES2015] ECMAScriptモジュール (JavaScript Primer) ## [ES2015] ECMAScriptモジュール > ECMAScriptモジュールはTodoアプリのユースケースで実際に動かしながら学ぶため、ここでは構文の説明とモジュールのイメージをつかむのが目的です。 モジュールは、保守性・名前空間・再利用性で使われます。 - 保守性: 依存性の高いコードの集合を一箇所にまとめ、それ以外のモジュールへの依存性を減らせます - 名前空間: モジュールごとに分かれたスコープがあり、グローバルの名前空間を汚染しません - 再利用性: 便利な変数や関数を複数の場所にコピーアンドペーストせず、モジュールとして再利用できます **保守とは** 対象の機器やシステム等が不便なく使えるように維持管理することです。 ## ECMAScriptモジュールの構文 ECMAScriptモジュールは、export文によって変数や関数などをエクスポートできます。 import文でエクスポートされたものをインポートすることが出来ます。 **インポートとエクスポートの種類** - 名前つき - デフォルト ## 名前つきエクスポート/インポート **名前つきエクスポート** ```javascript const foo = "foo"; // 宣言済みのオブジェクトを名前つきエクスポートする export { foo }; ``` - export文のあとに続けて{}を書き、その中にエクスポートする変数を入れることで、宣言済みの変数を名前つきエクスポートできます。 ```javascript // 宣言と同時に名前つきエクスポートする export function bar() { }; ``` - 関数のエクスポートはfuntionキーワードの前にexportをつけて行います。 **名前つきインポート** ```javascript:my-module.js export const foo = "foo"; export function bar() { } ``` 変数fooと関数barを名前付きインポートしています。 ```javascript import { foo, bar } from "./my-module.js"; console.log(foo); // => "foo" console.log(bar); // => "bar" ``` - import文のあとに続けて{}を書き、その中にインポートしたい名前つきエクスポートの名前を入れます。 - 複数の値をインポートしたい場合は、それぞれの名前をカンマで区切ります。 #### 名前つきエクスポート/インポートのエイリアス internalFoo変数を名前つきエクスポートをas fooとすることでインポート時にfooとしても変数を認識することができます。 ```javascript const internalFoo = "foo"; // internalFoo変数をfooとして名前つきエクスポートする export { internalFoo as foo }; ``` 🔽インポート ```javascript // fooとして名前つきエクスポートされた変数をmyFooとしてインポートする import { foo as myFoo } from "./named-export-alias.js"; console.log(myFoo); // => "foo" ``` - export時もimport時も、`as`をつける事で別名を付けられます。 ## デフォルトエクスポート/インポート **デフォルトエクスポート** モジュールごとに1つしかエクスポートできない特殊なエクスポートです。 ```javascript const foo = "foo"; // foo変数の値をデフォルトエクスポートする export default foo; ``` - 名前付きエクスポートでは変数を{}で囲っていましたがdefaultでは{}は必要ありません。 ```javascript export default function() {} ``` - 宣言と同時にデフォルトエクスポートすることが出来ます。 - 関数やクラスの名前を省略することが出来ます。 変数宣言は宣言とデフォルトエクスポートを同時に行うことはできません。 変数宣言はカンマ区切りで複数の変数を定義できてしまうためです。 ```javascript // 変数宣言と同時にデフォルトエクスポートはできない export default const foo = "foo", bar = "bar"; ``` **デフォルトインポート** 指定したモジュールのデフォルトエクスポートに名前をつけてインポートします。 次の例では my-module.jsのデフォルトエクスポートにmyModuleという名前をつけてインポートしています。 import文のあとに任意の名前をつけることで、デフォルトエクスポートをインポートできます。 my-module.jsファイルからexport ```javascirpt export default { baz: "baz" }; ``` default-import.jsにimport ```javascript // デフォルトエクスポートをmyModuleとしてインポートする import myModule from "./my-module.js"; console.log(myModule); // => { baz: "baz" } ``` - デフォルトインポートする際に名前を付けることが出来ます。 - インポートの名前の指定時に{}を省略することが出来ます。 - 名前つきインポートにおいてもdefaultという名前がデフォルトインポートに対応しています。 次のように、名前つきインポートでdefaultを指定するとデフォルトインポートできます。 ただし、defaultは予約語なので、この方法では必ずas構文を使ってエイリアスをつける必要があります。 ```javascipt // デフォルトエクスポートをmyModuleとしてインポートする import { default as myModule } from "./my-module.js"; console.log(myModule); // => { baz: "baz" } ``` - defaultという予約語を持っていると覚えておけば良いでしょう。 ## その他の構文 **再エクスポート** 別のモジュールからインポートしたものを、改めて自分自身からエクスポートし直すことです。 複数のモジュールからエクスポートされたものをまとめたモジュールを作るときなどに使われます。 ```javascript // ./my-module.jsのすべての名前つきエクスポートを再エクスポートする export * from "./my-module.js"; // [ES2020] ./my-module.jsのすべての名前つきエクスポートを名前空間オブジェクトとして再エクスポートする export * as myNameSpace from "./my-module.js"; // ./my-module.jsの名前つきエクスポートを選んで再エクスポートする export { foo, bar } from "./my-module.js"; // ./my-module.jsの名前つきエクスポートにエイリアスをつけて再エクスポートする export { foo as myModuleFoo, bar as myModuleBar } from "./my-module.js"; // ./my-module.jsのデフォルトエクスポートをデフォルトエクスポートとして再エクスポートする export { default } from "./my-module.js"; // ./my-module.jsのデフォルトエクスポートを名前つきエクスポートとして再エクスポートする export { default as myModuleDefault } from "./my-module.js"; // ./my-module.jsの名前つきエクスポートをデフォルトエクスポートとして再エクスポートする export { foo as default } from "./my-module.js"; ``` - export文のあとにfromを続けて、別のモジュール名を指定します。 **すべてインポート** import * as構文は、すべての名前つきエクスポートをまとめてインポートします。 この方法では、モジュールごとの 名前空間 となるオブジェクトを宣言します。 エクスポートされた変数や関数などにアクセスするには、その名前空間オブジェクトのプロパティを使います。 エクスポートする側のファイル ```javascript export const foo = "foo"; export function bar() { } export default { baz: "baz" }; ``` インポートする側のファイル ```javascript // すべての名前つきエクスポートをmyModuleオブジェクトとしてまとめてインポートする import * as myModule from "./my-module.js"; // fooとして名前つきエクスポートされた値にアクセスする console.log(myModule.foo); // => "foo" // defaultとしてデフォルトエクスポートされた値にアクセスする console.log(myModule.default); // => { baz: "baz" } ``` #### 副作用のためのインポート **副作用** オブジェクトが変化することです。 side-effects.jsファイル ```javascript // グローバル変数を操作する(副作用) window.foo = "foo"; ``` インポートするファイル ```javascript // ./side-effects.jsのグローバルコードが実行される import "./side-effects.js"; ``` - グローバル変数をモジュールで操作したものをインポートしています。 上記をインポートする際は以下のように書きます。("副作用のためのインポート構文") ```javascript import "./side-effects.js"; ``` ## ECMAScriptモジュールを実行する ```javascript <!-- my-module.jsをECMAScriptモジュールとして読み込む --> <script type="module" src="./my-module.js"></script> <!-- インラインでも同じ --> <script type="module"> import { foo } from "./my-module.js"; </script> ``` - type="module"をつける事でECMAScriptモジュールになりエクスポート・インポートが可能になります。 - type="module"属性が付与しない場合はECMAScriptモジュールの機能を持っていない通常のスクリプトとして扱われます。 ## 参考 [[ES2015] ECMAScriptモジュール](https://jsprimer.net/basic/module/#module) # 2022年3月19日 [ES2015] ECMAScript (JavaScript Primer) 第一章の最後の章では、ECMAScriptの仕様についてまとめられていました。 この章ではリテラシーを高める手段を知れたという印象です。 - ECMAScriptはEcma Internationalという団体によって標準化されている仕様です。 - Microsoft、Mozilla、Google、Appleといった企業が技術委員会となって仕様について議論されています。 - バージョンのリリースが年ごとに行われるようになりました。 - Living Standardという仕様で最新の仕様は[GitHub](https://tc39.github.io/ecma262/)上で管理されています。 ## ECMAScriptのバージョンの歴史 |バージョン|リリース期間| |---|---| |1|1997年6月| |2|1998年6月| |3|1999年12月| |4|破棄| |5|2009年12月| |5.1|2011年6月| |2015|2015年6月| |2016|2016年6月| |2017|2017年6月| |以下毎年リリース|| ## 単語メモ ・**プロポーザル方式** 主に業務の委託先や建築物の設計者を選定する際に、複数の者に目的物に対する企画を提案してもらい、その中から優れた提案を行った者を選定する方式です。 ・**エミュレート(emulate)** ある装置がそれとは異なる別のハードウェアやソフトウェアの環境を真似することをいう。 エミュレートは、模倣することである。 ・**Transpile** ある言語のコードを元に別の言語や別のバージョンの言語のファイルを生成すること ・**polyfilling** ブラウザでみ対応のAPIに変わる機能を提供する小さなライブラリのこと ・**babel(バベル)** JavaScriptのコードを新しい書き方から古い書き方に変換するツールである。具体的には、JavaScriptの言語仕様であるES2015以上の仕様のJavaScriptで記述すると、Internet Explorer11といった古いブラウザでは動作しない。そこで、Babelを使ってES2015・ES2016といった仕様で記述したJavaScriptファイルを互換性のあるEXMAScript5に変換する。 ・**Living Standard** バージョン番号をつけずに、常に最新版を公開する仕様のことです。 ## 仕様策定のプロセス > 仕様に追加する機能(API、構文など)をそれぞれ個別のプロポーザル(提案書)として進めていきます。 現在策定中のプロポーザルはGitHub上のtc39/proposalsに一覧が公開されています。 それぞれのプロポーザルは責任者であるチャンピオンとステージ(Stage)と呼ばれる0から4の5段階の状態を持ちます。 |ステージ|ステージの概要| |---|---| |0|アイデアの段階| |1|機能提案の段階| |2|機能の仕様書ドラフトを作成した段階| |3|仕様としては完成しており、ブラウザの実装やフィードバックを求める段階| |4|仕様策定が完了し、2つ以上の実装が存在している 正式にECMAScriptにマージできる段階| ## 仕様や策定プロセスを知る意味 #### 言語を学ぶため? もっとも単純な理由はJavaScriptという言語そのものを学ぶためです。 言語の詳細を知りたい場合にはECMAScriptという仕様を参照できます。 しかしながら、JavaScriptの言語機能に関してはMDN Web Docsという優れたリファレンスサイトなどがあります。 そのため、使い方を覚えたいなどの範囲ではECMAScriptの仕様そのものを参照する機会は少ないでしょう。 ## まとめ > JavaScriptと一言に言ってもECMAScript、ウェブブラウザ、Node.js、WebAssembly、WebGL、WebRTCなど幅広い分野があります。 すべてのことを知っている必要はありませんし、知っている人もおそらくいないでしょう。 このような状況下においては知識そのものよりも、それについて知りたいと思ったときに調べる方法を持っていることが大切です。 > 何ごとも突然まったく新しい概念が増えるわけではなく、ものごとには過程が存在します。 ECMAScriptにおいては策定プロセスという形でどのような段階であるかが公開されています。 つまり、仕様にいきなり新しい機能が増えるのではなくプロポーザルという段階を踏んでいます。 > 日々変化しているソフトウェアにおいては、自身に適切な調べ方を持つことが大切です。 ## 参考 [ECMAScript](https://jsprimer.net/basic/ecmascript/#ecmascript)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully