3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Javascriptのデバッグ中にDevToolsに翻弄された話

Last updated at Posted at 2023-01-23

TL;DR

何の話か

  • Javascriptのオブジェクト配列からfilterメソッドで条件に合う要素を抽出する際、callback関数に引数を渡そうとしたらうまく動かなかった
  • ブラウザのDevToolsでデバッグしようとしたら、callback関数には引数が渡ってそうにも見えて、原因究明に数時間かかってしまった

わかったこと

  • filterメソッドのcallback関数にアロー関数を設定するとthisをバインドできない
  • scriptタグのtype属性を雑に設定するとよくない
    type属性について参考:HTMLのscriptタグのtype属性について

問題発生時の状況

  • Wordpress記事にReactアプリを埋め込むためにcdnでReactを取り込んで開発中、時刻データを含むオブジェクト配列からfilterメソッドで、変数で定義した時刻より前の時刻データを含むオブジェクトの数をカウントしたかったが、できなかった

開発環境(ローカルPC)

  • index.htmlapp.jsを用意
    (下記のapp.jsは、該当部分だけ抽出し、処理で使う変数をconst値に変更して記載)
    index.html
    <html><body>
        <script src="https://unpkg.com/react@latest/umd/react.development.js" crossorigin="anonymous"></script>
        <script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/@mui/material@latest/umd/material-ui.development.js" crossorigin="anonymous"></script>
        <script src="https://unpkg.com/babel-standalone@latest/babel.min.js" crossorigin="anonymous"></script>
        
        <script type="text/babel" src="./app.js"></script>
        
        <div id="root"></div>
    
    </body></html>
    
    app.js
    const date_a_array = [
        "2014-05",
        "2014-06",
        "2014-07"
    ]
    const date_b = "2014-06"
    
    class App extends React.Component{
    // --- 略
        let count
        
        count = date_a_array.filter(e => (
                        new Date(e) < new Date(this)
                        )
                    , date_b)
        console.log(count)
    // --- 略
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<App />)
    
  • コンソールへの出力が1になってほしいところ、0となったので修正を開始

DevToolsでデバッグ

Google Chromeの開発者ツールでデバッグした

  1. まず、filterメソッドのコールバック関数(e => (...)の部分)の中身(new Date(e) < new Date(this))にブレークポイントを設置し、thisdate_bが渡っているか確認したところ、下記のように確認できた
    image.png
  2. 別の場所で下記の処理を実行すると、コンソールにtrueが出力された
    console.log(new Date("2014-05") < new Date("2014-06"))
    
  3. app.jsの該当部分を下記のように修正すると、コンソールに1が出力された
    count = date_a_array.filter(e => (
                        new Date(e) < new Date("2014-06")
                        )
                    , date_b)
        console.log(count)
    
  4. デバッグツール上ではdate_bの文字列がcallback関数に渡されているのに、処理が思い通りいっていないため、再度調べなおすことに

結局何がおかしいのか

調査と実験をいくつかやって、前節の結果の原因を下記2つに絞った

原因1: アロー関数へthisがバインドされない

Many array methods take a callback function as an argument.
...
method(callbackFn, thisArg)
...
The thisArg argument (defaults to undefined) will be used as the this value when calling callbackFn.
...
The thisArg argument is irrelevant for any callbackFn defined with an arrow function, as arrow functions don't have their own this binding.
参考

  • 多くのArrayメソッドでは、callback関数を(第一)引数として持ち、第二引数に渡した値を、callback関数内でthisとして利用できる
  • ただし、アロー関数ではthisがバインドされない
  • したがって、下記のようにコードを修正しなければならない
    • アロー関数を使わずにthisを使う
      count = date_a_array.filter(function(e){
                      new Date(e) < new Date(date_b)
                      }
                  , date_b)
      console.log(count)
      
    • アロー関数を使ってthisを使わない
      count = date_a_array.filter(e => (
                      new Date(e) < new Date(date_b)
                      ))
      console.log(count)
      
  • これで想定通りのコンソールへ1を出力することを確認できた
  • しかし、アロー関数ではthisで値を渡せないはずなのに、なぜDevToolsでthisに値が渡っているように見えたのか

原因2: type="text/babel"としてapp.jsを読み込んでいた

  • 実験のため、下記のように確認したい該当箇所だけ抽出して、_app.jsという新しいファイルを作成した

    _app.js
    const date_a_array = [
        "2014-05",
        "2014-06",
        "2014-07"
    ]
    const date_b = "2014-06"
    
    let count
    
    // case 1他はここでは省略
    
    // case 2
    count = date_a_array.filter(e => (
        new Date(e) < new Date(this)
        )
    , date_b)
    
    console.log(count)
    
  • 次に、index.htmlでscriptを取り込むとき、下記のように2パターンで取り込んで、前節と同様にDevToolsでブレークポイントを作成してthisの値を調べた

    • パターン1: type="text/babel"
    <script type="text/babel" src="./_app.js"></script>
    

    結果:前節と同じ
    image.png

    • パターン2: typeを指定しない
    <script src="./_app.js"></script>
    

    結果thisに値がバインドされていない
    image.png

  • これらの結果より、最初にDevToolsで値がthisにバインドされているように見えたのは、type="text/babel"としてapp.jsを読み込んでいたことが原因であると考えられる

  • なぜbabelを読み込むとDevToolsでこのような挙動になるのかまでは未調査
    (もし心当たりがある方いらっしゃいましたらシェアいただけるとうれしいです)

補足・メモ

  • 今回のケースでは、DevToolsでthisがバインドされているように見えても結局動いていないので、filterメソッドのcallback関数へ値を渡すコードの書き方は変えないといけないことに注意
  • (Node.jsを使うなど)cdnを使わない場合も同じようになるのかは不明
  • 参考:MIMEについて

所感

  • 備忘録もかねてシェアさせていただきました
  • babelのDevToolsへの影響についてはっきりさせないと、完全に原因特定したとは言えないが、あたりがつかなさすぎるため断念しました
    (追記)
    @murasuke様からかなり掘り下げた補足・追加調査をしていただきました。コメント欄参照。
3
0
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?