85720


GitHub Pagesで以下のサイトを制作しました。  
<https://imaimai17468.github.io/Products/CreditsCounterforKNCT/>

正常な動作の場合、「特別学修」タブを開くと、一般の特別学修と専門系の特別学修のタブがあり、それを開くとその一覧表が表示されます。また、特別学修タブの下には教科一覧の表があります。  
しかしスマホで見ると、特別学修タブを開いてもそこからほかのタブが表示されなかったり、その下の教科の一覧表が表示されないことがあります。  
なぜスマホの場合のみ正常に機能しないのでしょうか？

本来は、html上で

[code]

    <div class="tokubetu_hogehoge"></div>
    <div id="sikaku_hogehoge"></div>
    
[/code]

となっているところにjsでclass名やid名を読み取って表を展開するような仕組みになっています。

リポジトリはこちらになります。  
<https://github.com/imaimai17468/imaimai17468.github.io/tree/b6a86cc89b78319dc7963e27385dbf63148bb85b/Products/CreditsCounterforKNCT>

スマホはiPhone13を使用しておりOSはiOS 15.1.1です。Safari、Chromeなどで確認していますがどちらも正常に機能しません。  
また、PCのChromeの検証を使ってスマホの幅にしたりして試してみましたが、こちらは正常に機能しました。スマホからアクセスした場合のみ機能しません。

どなたか教えて頂けますと幸いです。よろしくお願いします。


CC BY-SA 4.0
2022-01-15T01:07:42.467
85704
2022-01-16T08:38:05.383
2022-01-16T08:38:05.383
3054
49383
question
javascript safari
JavaScriptがiPhoneでのみ正常に動作しない


### 出ているエラー

該当ページをWebkit系のブラウザの開発ツールで確認すると、`convertArray` という関数で以下のエラーが出ています。

> ReferenceError: Can't find variable: department

`department` 変数は `convertArray`
関数より前に宣言・代入されていますし、Webkit系以外のブラウザでは問題なくアクセスできることからも、理解が難しいエラーです。

### 原因

Webkit系とその他でブロック内での関数宣言の巻き上げの動作が異なることが原因です。(参考:
<https://stackoverflow.com/questions/57035056/javascript-object-definition-
available-before-code-execution-on-safari> )  
私のおおまかな理解は、以下です。

  * Webkit系のスクリプトエンジンでは、関数の宣言がスクリプトの先頭まで巻き上げられます。その結果、ブロックの外に出てしまい、当該変数を参照できなくなります。
  * その他の処理系では、関数の宣言はブロックの先頭まで巻き上げられます。依然としてブロックの中なのでブロック内の変数を参照できます。

正確には解りませんが、ブロック内での関数宣言は仕様外（動作が未定義？）だった（あるいは今でも）ことから生じた差異のようです。

### 対策その1: 即時実行関数式

現状、各スクリプトファイルの全体を `{` と `}` で囲んでブロックとしていますが、それは止めて、`;(function(){` と `})();`
で囲み、[即時実行関数式](https://developer.mozilla.org/ja/docs/Glossary/IIFE)とします。これが、もっとも一般的で他のコードに影響しない解決方法だと思います。

### 対策その2: Strictモード

各スクリプトファイルの先頭に `"use strict"`
を挿入し、[Strictモード](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode)にします。他の箇所でStrict
モードとして不適格なコードが有った場合、それらも修正しなければならなくなります。

### 対策その3: 関数式

[関数宣言](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/function)では無く、[関数式](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/function)を使い、`let`
か `const`
で宣言する変数に代入します。この場合は巻き上げが行なわれませんので、宣言箇所より前に関数を呼び出していたコードは修正しなければならなくなります。

* * *

### 検証コード

下記のスニペットは問題を再現する最小限のコードです。ブラウザにより動作が異なります。

[code]

    document.write("スクリプトの先頭では `foo` の type は `" + typeof foo + "` です。<br/>")
    
    {
        document.write("ブロックの先頭では `foo` の type は `" + typeof foo + "` です。<br/><br/>")
    
        const message = "Hello"
    
        foo()
    
        function foo() {
            document.write("foo 内での `message` の type は `" + typeof message + "` です。<br/>")
    
            if (typeof message == "undefined") {
                document.write("foo 内では `message` は未定義です。<br/>")
            } else {
                document.write("foo 内での `message` の値は `" + message + "` です。<br/>")
            }
        }
    }
[/code]

Webkit系の出力:

> スクリプトの先頭では `foo` の type は `function` です。  
>  ブロックの先頭では `foo` の type は `function` です。
>
> foo 内での `message` の type は `undefined` です。  
>  foo 内では `message` は未定義です。

FirefoxとChromeの出力:

> スクリプトの先頭では `foo` の type は `undefined` です。  
>  ブロックの先頭では `foo` の type は `function` です。
>
> foo 内での `message` の type は `string` です。  
>  foo 内での `message` の値は `Hello` です。


CC BY-SA 4.0
2022-01-15T21:01:06.487
85720
2022-01-15T23:49:34.553
2022-01-15T23:49:34.553
3054
3054
85704
answer