Javascript oop 未定义循环中的读取长度

Javascript oop undefined reading length in loop

我正在 class 中创建一个具有多个功能的 oop 项目。我有一段代码,但由于我正在使用绑定,所以它不再起作用了。

    document.getElementById("finalLink").innerHTML +=
        "<a id='FLink' href='https://www.voetbalshop.nl/voetbalschoenen.html#' onclick='location.href=this.href+getLink(url);return false;'>Result</a>";

    class QuestionControl{
        /**
         * @param {QuizPart[]} quiz
         */
        constructor(quiz) {
            this.quiz = quiz;
            this.url = [];
            this.questionNumber = -1;
            this.button = document.getElementById('answer');
            this.questionName = document.getElementById('question');
            this.nextbtn = document.getElementById('nextbtn');
            this.prevbtn = document.getElementById('prevbtn')
            this.resultbtn = document.getElementById('FLink');
        }

        Initialize(){
            this.NextQuestion();
        }

        /**
         *
         * @param {int} question
         * @returns {QuizPart}
         */
        SetQuestion(question){
            if (this.questionNumber >= 0){
                let oldAnswerButton = document.querySelectorAll('.filter_anwser');

                // Deletes old question when the next question is clicked
                for (let answerButton of oldAnswerButton) {
                    answerButton.style.display = 'none';
                }
            }

            this.questionNumber = question;

            let q = this.quiz[question];
            // Check if your at the last question so the next button will stop being displayed.
            if (this.questionNumber === Quiz.length-1) {
                this.nextbtn.style.display = 'none';
                this.prevbtn.style.display = 'block';
                this.resultbtn.style.display = 'grid';
            } else if (this.questionNumber === 0 ) {
                this.nextbtn.style.display = 'block';
                this.prevbtn.style.display = 'none';
                this.resultbtn.style.display = 'none';
            } else{
                this.nextbtn.style.display = 'block';
                this.prevbtn.style.display = 'block';
                this.resultbtn.style.display = 'none';
            }

            // Displays Question
            this.questionName.textContent = q.questionDescription;
            this.questionName.id = "questionID";

            return q;
        }

        NextQuestion() {
            let question = this.SetQuestion.bind(this.questionNumber + 1);

            // Displays answers of the questions
            for (let y = 0; y < question.chosenAnswer.length; y++) {
                let item = question.chosenAnswer[y];
                // Display answer buttons
                let btn = document.createElement('button');
                btn.value = item.id;
                btn.className = "filter_anwser";
                btn.textContent = item.name;
                this.button.appendChild(btn);
            }
        }

        PrevQuestion() {
            let question = this.SetQuestion(this.questionNumber - 1);

            // Displays answers of the questions
            for (let y = 0; y < question.chosenAnswer.length; y++) {
                let item = question.chosenAnswer[y];
                // Display answer buttons

                let btn = document.querySelector('button[value="' + item.id + '"]');
                btn.style.display = 'block';
                console.log(btn);
            }
        }

        /**
         * Returns the parameters for the URL.
         *
         * @returns {string}
         */
        getLink() {
            let tmp = [];
            for (let i = 0; i < this.url.length; i++) {
                // Check if question is from the same quiz part and adds a , between chosen answers and add the right prefix at the beginning
                if (this.url[i].length > 0){
                    tmp.push("" + Quiz[i].prefix + this.url[i].join(","))
                }
            }
            /// If answers are from different quiz parts add a & between answers.
            console.log(this.url, this.questionNumber);
            return "" + tmp.join("&");
        };
    }

    class QuizPart{
        constructor(questionDescription, chosenAnswer, prefix){
            this.questionDescription = questionDescription;
            this.chosenAnswer = chosenAnswer;
            this.prefix = prefix;
        }
    }

    class ChosenAnswer{
        constructor(id, name){
            this.id = id;
            this.name = name;
        }
    }

    let Quiz = [
        new QuizPart('Whats your size?', [
            new ChosenAnswer('6595', '41'),
            new ChosenAnswer('6598', '42'),
            new ChosenAnswer('6601', '43'),
        ], 'bd_shoe_size_ids='),

        new QuizPart('What color would you like?', [
            new ChosenAnswer('6053', 'Red'),
            new ChosenAnswer('6044', 'Blue'),
            new ChosenAnswer('6056', 'Yellow'),
            new ChosenAnswer('6048', 'Green'),
        ], 'color_ids='),

        new QuizPart('What brand would you like?', [
            new ChosenAnswer('5805', 'Adidas'),
            new ChosenAnswer('5866', 'Nike'),
            new ChosenAnswer('5875', 'Puma'),
        ], 'manufacturer_ids='),
    ]

    let control = new QuestionControl(Quiz);
    control.Initialize();

    document.getElementById('nextbtn').addEventListener("click", control.NextQuestion);
    document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion);

    control.button.addEventListener("click", function (e) {
        const tgt = e.target;

        // clear the url array if there's nothing clicked
        if (control.url.length === control.questionNumber) {
            control.url.push([]);
        }

        let quizUrl = control.url[control.questionNumber];

        // Check if a button is clicked. Changes color and adds value to the url array.
        if (quizUrl.indexOf(tgt.value) === -1) {
            quizUrl.push(tgt.value);
            e.target.style.backgroundColor = "orange";
            // Check if a button is clicked again. If clicked again changes color back and deletes value in the url array.
        } else {
            quizUrl.splice(quizUrl.indexOf(tgt.value), 1);
            e.target.style.backgroundColor = "white";
        }
        console.log(control.getLink());
    })

问题是,当我开始使用绑定将函数 SetQuestion 连接到 NextQuestion 时,它给了我以下错误:

Uncaught TypeError: Cannot read properties of undefined (reading 'length')

在 nextQuestion 的 for 循环中,但如果我停止使用 bind,它将无法识别函数 SetQuestion。有人知道为什么会这样吗?

这不是 bind 的正确用法:

let question = this.SetQuestion.bind(this.questionNumber + 1);

bindreturns一个函数;它不执行它。您传递给 bind 的参数应该是最终调用返回的函数时用于 this 的值。

因为这不是这里的意图,你应该删除这里的 .bind,并且只需要:

let question = this.SetQuestion(this.questionNumber + 1);

真正的问题出在这里:

document.getElementById('nextbtn').addEventListener("click", control.NextQuestion);
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion);

这些点击处理函数将在没有特定 this 值的情况下被调用(在严格模式下它将是 undefined,否则是全局对象)。这不是您想要的,因为您的代码期望 this 代表 QuestionControl.

的实例

这可以用bind解决,如下:

document.getElementById('nextbtn').addEventListener("click", control.NextQuestion.bind(control));
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion.bind(control));

所以不是你写的:

using bind to connect the function SetQuestion to NextQuestion

bind用于将函数连接到对象,该对象必须在函数执行期间充当this值。在你的情况下是 control.

或者可以显式提供绑定函数:

document.getElementById('nextbtn').addEventListener("click", () => control.NextQuestion());
document.getElementById('prevbtn').addEventListener("click", () => control.PrevQuestion());