zdublowany eventListener

0

Witam. Jestem nowy w świecie programowania.
W ramach ćwiczeń stworzyłem prostą grę kółko i krzyżyk.
Gra działa jak należy, jednak problem polega na tym, że duplikują mi się eventy (zauważyłem to w firefox dla developerów).

title

const gameBoard = [...document.querySelectorAll(".field")];
const desc = document.querySelector(".description");
const span = document.querySelector(".winDisplay");
const fields = ["", "", "", "", "", "", "", "", ""];
let counter = 0;
let gameOver = false;

resetGame = function (btn) {
    counter = 0;
    gameOver = false;
    span.innerText = "";
    btn.remove();
    gameBoard.forEach(item => {
        if (item.firstChild) {
            item.firstChild.remove();
        }
    });
    fields.forEach((field, index) => fields.splice(index, 1, ""));
    init();
};

reset = function () {
    gameOver = true;
    const btn = document.createElement("button");
    btn.innerText = "resetuj";
    desc.appendChild(btn);
    btn.addEventListener("click", () => resetGame(btn));
};
computerWin = function () {
    span.innerText = "Niestety, Przegrałeś!";
    reset();
};

playerWin = function () {
    span.innerText = "Udało się! Wygrałeś!";
    reset();
};

draw = function () {
    span.innerText = "Remis!";
    reset();
};
checkWin = figure => {
    if (
        (fields[0] === figure && fields[1] === figure && fields[2] === figure) ||
        (fields[0] === figure && fields[4] === figure && fields[8] === figure) ||
        (fields[0] === figure && fields[3] === figure && fields[6] === figure) ||
        (fields[1] === figure && fields[4] === figure && fields[7] === figure) ||
        (fields[2] === figure && fields[5] === figure && fields[8] === figure) ||
        (fields[3] === figure && fields[4] === figure && fields[5] === figure) ||
        (fields[6] === figure && fields[7] === figure && fields[8] === figure) ||
        (fields[6] === figure && fields[4] === figure && fields[2] === figure)
    ) {
        if (figure === "cross") computerWin();
        else playerWin();
    } else if (counter === 9) draw();
};
computerTurn = () => {
    if (counter === 9) return;
    const crossContainer = document.createElement("div");
    const crossElement = document.createElement("div");
    crossElement.classList.add("cross");
    crossContainer.classList.add("crossContainer");
    crossContainer.appendChild(crossElement);

    const crossIndex = Math.floor(Math.random() * fields.length);

    if (fields[crossIndex]) {
        computerTurn();
        return;
    }

    const div = document.querySelector(`.field:nth-child(${crossIndex + 1})`);
    div.appendChild(crossContainer);
    fields[crossIndex] = "cross";
    const cross = "cross";
    counter++;
    checkWin(cross);
};

pointCircle = (event, index) => {
    const circleElement = document.createElement("div");
    circleElement.classList.add("circle");
    circleElement.setAttribute("draggable", "false");
    event.target.appendChild(circleElement);
    fields[index] = "circle";
    counter++;
    const circle = "circle";
    checkWin(circle);
    computerTurn();
};

init = () => {
    gameBoard.forEach((field, index) =>
        field.addEventListener("click", function handler(event) {
            if (field.childElementCount || gameOver) {
                field.removeEventListener("click", handler);
                return;
            }

            pointCircle(event, index);
        })
    );
};
init();

Inna sprawa czy takie umieszczanie removeEventListenera jest poprawne?

Całość na codepen: https://codepen.io/MarekZet/pen/MWwjwpQ

1

Kod na CodePen znacząco się różni w tym miejscu:

init = () => {
  gameBoard.forEach((field, index) =>
    field.addEventListener("click", event => {
      if (field.childElementCount || gameOver) {
        field.removeEventListener("click", pointCircle);
        return;
      }

      pointCircle(event, index);
    })
  );
};

Za każdym razem tworzysz nowe metody i tracisz do nich referencje. Później próbujesz usuwać listener, który nie jest listenerem. Powinieneś stworzyć najpierw metodę i jej referencję zatrzymać, aby następnie usunąć za pomocą removeEventListener.

0

Dzięki za odpowiedź.
Tak różni się ponieważ gdzieś wyczytałem żeby funkcja nie była anonimowa, ale w tym przypadku na jedno wyszło. Zapomniałem dodać do CodePen.

Za każdym razem tworzysz nowe metody i tracisz do nich referencje.>

Mimo wszystko, jednak kliknąć kolejny raz w pole nie mogę, a więc w jakiś sposób działa.

Powinieneś stworzyć najpierw metodę i jej referencję zatrzymać

Jakaś sugestia jak nie stracić referencji? Coś z closure?

0

Nie może być anonimowa, bo właśnie dzieje się to samo, co teraz z lambdą. Za każdym razem tworzona jest nowa instancja i tracisz dostęp do starej. Działa, bo ciało funkcji jest w porządku, ale jest wywoływane n razy. Możesz zdefiniować lambdę wyżej i przypisać od zmiennej, a następnie użyć tej zmiennej i w addEventListener i w removeEventListener. Teraz w removeEventListener masz z jakiegoś powodu referencję do innej funkcji.

0

Lambda? Jeszcze tego nie przerabiałem.
Jakaś podpowiedź?
Próbowałem różne kombinacje, przenosić funkcje, przypisywać do zmiennych, co nie rozwiązywało problemu, a tworzyło nowe.

0

Lambda to swego rodzaju funkcja postaci () => {}.
Spróbuj w ten sposób:

init = () => {
  gameBoard.forEach((field, index) =>
    const listener = event => {
      if (field.childElementCount || gameOver) {
        field.removeEventListener("click", listener);
        return;
      }

      pointCircle(event, index);
    });
    field.addEventListener("click", listener);
  );
};

0

Dzięki za wyjaśnienie. Jednakże podany sposób nadal daje takie same efekty. Jak kliknę dwukrotnie w dany element to wtedy dopiero usuwa wszystkie eventy z tego elementu.

0
MarekZet napisał(a):

Dzięki za wyjaśnienie. Jednakże podany sposób nadal daje takie same efekty. Jak kliknę dwukrotnie w dany element to wtedy dopiero usuwa wszystkie eventy z tego elementu.

Czyli wszystko działa prawidłowo. Przecież dałeś usuwanie listenera w zdarzeniu "click" w zależności od field.childElementCount || gameOver. Innymi słowy musisz kliknąć, żeby usunąć listenera. W kodzie masz zaprogramowane to co opisałeś, więc w czym masz problem?

0

Chodzi o to aby event usuwał się po jednokrotnym kliknięciu. Teraz za każdym razem gdy resetuję grę do każdego elementu dodaje się nowy event, a stary zostaje jak na screenie. Dopiero dwukrotne naciśnięcie na element kasuje wszystkie eventy z tego elementu. Na pozostałych zostają.

0

W takim razie usuń tego if-a. Tylko zostaje jeszcze jeden problem z zaimplementowaną logiką. Co jeżeli nie klikniesz na jakieś pole, przecież gra może się zakończyć po uzupełnieniu 5 pól? :D

0

Jeszcze dziwniejsze rzeczy się dzieją.Chaos.

2
 reset = function () {

a gdzie jest deklarowana zmienna reset? To ci powinno zgłosić błąd składni (weź dodaj "use strict"; na początku pliku. Wtedy uaktywnisz strict mode, w którym tego typu rzeczy są traktowane jak błąd, a co może się przydać w sytuacjach, kiedy zrobisz literówkę choćby).

poprawnie byłoby poprzedzić nazwę niezadeklarowanej zmiennej słowem kluczowym const/let/var, np.:

const reset = function () {

Tylko w tym przypadku to trochę przerost formy nad treścią, skoro można tak:

function reset () {

chyba, że masz arrow function, to wtedy np. tak

const foo = () => {};

bo arrow functions nie mają własnej nazwy.

fields.forEach((field, index) => fields.splice(index, 1, ""));

Używanie splice (które ci zmienia tablicę), w momencie kiedy po niej iterujesz, może mieć niespodziewane skutki, jeśli robisz to nieuważnie. Tutaj nie powinno mieć, bo po prostu jeden element zabierasz i jeden dodajesz - ale kompletnie nie kumam, po co robisz to naokoło i używasz splice, kiedy możesz po prostu zmienić element w miejscu:

fields.forEach((_, index) => { fields[index] = ""}));

albo nawet, jeśli chodzi ci o wypełnienie tablicy tą samą wartością, po prostu użyć metody fill:

fields.fill("");
1

Dzięki za poprawki.Faktycznie wywaliło błędy. Poprawione.

1 użytkowników online, w tym zalogowanych: 0, gości: 1