

現在javascriptとHTMLを使ってある機能を作ってるんですけど、制作過程でオブジェクトの定義に関するエラーがあり、抜け出せません。これを解決する方法を教えて下さい。

**エラー内容**

[code]

    test.html:23 Uncaught TypeError: Cannot read properties of undefined (reading 'options')
        at test.html:23:26
    
[/code]

**ソースコード:**

[code]

    <body>
        
        <p id="display"></p>
            <select id="numbers">
                <option value="1">10</option>
                <option value="2">20</option>
                <option value="3">30</option>
                <button id="output">出力</button>
        </select>
            
        <script>
        
        var element = {
            select: document.getElementById('numbers'),
            index: this.select.options.selectedIndex,
            selected: this.select.options[this.index],
            display: document.getElementById('display'),
            button: document.getElementById('output'),
            output: this.button.addEventLister('click', () => {
               element.display.innerText = element.selected.value
            }),
        }
        
        </script>
    
    </body>
[/code]


CC BY-SA 4.0
2022-06-12T01:30:08.907
89341
2022-06-13T23:49:06.597
2022-06-13T21:51:52.050
3054
53032
question
javascript html dom
オブジェクトの初期化中にthisで他のプロパティを使うとエラーになる


変数の初期化の仕方に問題があります。変数 `element` の初期化のための値を作る部分で `this`
を参照しても期待するものにはなりません。したがって `this.select` が `undefined`
になり、`this.select.options` を参照するところで `undefined.options`
を参照してしまい、「プロパティを参照できない」という今回のエラーに繋がります。

また、今回使っている変数たちを `element`
のプロパティとしてひとまとめにする必要も現時点では無いでしょう。以下のように順番にそれぞれ変数として順番に初期化していけばよいです。

[code]

    const select = document.getElementById('numbers');
    const index = this.select.options.selectedIndex;
    const selected = this.select.options[this.index];
    // （以下略）
    
[/code]

更に、今回の処理内容であれば select で選択されている option の value
を取得するのはイベントリスナーの実行時であるべきです。なぜなら、選択されている option
が変化しても上記の代入部分は再実行されたりせず、値が更新されないからです。

というところまで考えると、実は今回の処理で変数を使う必要はそこまで無いことが分かります。たとえば以下のように書けます。

[code]

    document.getElementById('output').addEventListener('click', () => {
      document.getElementById('display').innerText = document.getElementById('numbers').value;
    });
[/code]

[code]

    <body>
        <p id="display"></p>
        <select id="numbers">
            <option value="1">10</option>
            <option value="2">20</option>
            <option value="3">30</option>
        </select>
        <button id="output">出力</button>
    </body>
[/code]


CC BY-SA 4.0
2022-06-12T03:15:43.697
89343
2022-06-13T03:35:16.940
2022-06-13T03:35:16.940
19110
19110
89341
answer
  
  
主に `this` を使っている部分に問題があります。

  * [関数(`function`)の外にある `this` は グローバルオブジェクト](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this#%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88)であり、意図したオブジェクトを指していません
  * そもそも `this` を使っている部分の値は、ユーザーの選択によって初期化時から変わっている可能性があるので、呼ばれるたびに求める必要があります
  * よって、`this` を用いている部分を[関数 (メソッド)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects#defining_methods)にすると解決します

以下に参考になりそうなコード、リンクを挙げていますが、基本的には書籍などで体系的に理解することをお勧めします。

### `this` が参照するもの

`this` が参照するものは文脈により様々です。

  * [JavaScript Primer 「関数とthis」](https://jsprimer.net/basic/function-this/)
  * [MDN 「this」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this)

しかし、様々とはいっても質問者が期待した機能はありません。

質問のコードでは、オブジェクト初期化子（リテラル）の中で `this`
を用いることで、初期化（生成）しているオブジェクト自身を指すことを期待していますが、JavaScriptにそのような機能はありません。`this`
にそのような機能が無いだけでなく、他にもそのような事を実現する構文は無いと思います。

「`this` で、初期化中のオブジェクトを参照できる」といった表現がされることがありますが、それは主に `new`
で呼び出される関数(コンストラクタ)の中で使用する場合です。

では、質問のコードの `this` が何を指しているかというと、グローバルオブジェクト(ブラウザの場合は `window`
と同じ)です。下のコードで確認して下さい。

[code]

    var a = "グローバル変数 a の値"
    
    let obj = {
        a: "obj のプロパティ a の値",
        what_is_this: this.a,
    }
    
    // ここで、obj.what_is_this の値はグローバル変数 a と同じです。
    // つまり、"グローバル変数 a の値" となっています。
    // "obj のプロパティ a の値" とはなりません。
    console.log("obj.what_is_this の値は: ", obj.what_is_this)
    
    // 上は、ここでの this がグローバルオブジェクトを指すことを示します。
    // もちろん、オブジェクト初期化子の外であっても同じです。
    // よって、以下の値も同じです。
    console.log("this.a の値は:           ", this.a)
[/code]

### メソッドにする例

質問のコードの形をできるだけ残したもの:

[code]

    var element = {
        select: document.getElementById('numbers'),
        getIndex: function() {
            return this.select.options.selectedIndex
        },
        getSelected: function() {
            return this.select.options[this.getIndex()]
        },
        display: document.getElementById('display'),
        button: document.getElementById('output'),
        init: function() {
            this.button.addEventListener('click', () => {
                this.display.innerText = this.getSelected().value
            })
        },
    }
    element.init()
    
[/code]

より慣用的なJavaScript
(コンストラクター関数や[プロトタイプ](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Objects/Object_prototypes)を用いたオブジェクト指向):

[code]

    function App() {
        this.select = document.getElementById('numbers'),
        this.display = document.getElementById('display'),
        this.button = document.getElementById('output'),
        this.button.addEventListener('click', () => {
            this.display.innerText = this.getSelected().value
        })
    }
    App.prototype.getIndex = function() {
        return this.select.options.selectedIndex
    }
    App.prototype.getSelected = function() {
        return this.select.options[this.getIndex()]
    }
    
    var app = new App()
    
[/code]

より[最近の構文](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Objects/Classes_in_JavaScript#ecmascript_2015_classes)を使用したもの:

[code]

    class App {
        select = document.getElementById('numbers')
        display = document.getElementById('display')
        button = document.getElementById('output')
    
        constructor() {
            this.button.addEventListener('click', () => {
                this.display.innerText = this.getSelected().value
            })
        }
        getIndex() {
            return this.select.options.selectedIndex
        }
        getSelected() {
            return this.select.options[this.getIndex()]
        }
    }
    
    let app = new App()
    
[/code]


CC BY-SA 4.0
2022-06-12T10:12:45.360
89354
2022-06-13T23:49:06.597
2022-06-13T23:49:06.597
3054
3054
89341
answer