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);
bind
returns一个函数;它不执行它。您传递给 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());
我正在 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);
bind
returns一个函数;它不执行它。您传递给 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 functionSetQuestion
toNextQuestion
bind
用于将函数连接到对象,该对象必须在函数执行期间充当this
值。在你的情况下是 control
.
或者可以显式提供绑定函数:
document.getElementById('nextbtn').addEventListener("click", () => control.NextQuestion());
document.getElementById('prevbtn').addEventListener("click", () => control.PrevQuestion());