Autor Zpráva
hypot
Profil
Zdravím,
chtěl jsem na odrolování ke kotvám využít tuto funkci, ale nechtěl jsem psát ke každému odkazu onclick, takže jsem HTML upravil takto
<p class="kotvy">Nadpis 1</p>
<p class="kotvy">Nadpis 2</p>
(atd.)
<h3 id="kotva-1">Nadpis 1</h3>
<h3 id="kotva-2">Nadpis 2</h3>
a z dotyčné funkce udělal anonymní takto
var kotvy = document.getElementsByClassName("kotvy");
for (var i=1; i<=kotvy.length; i++) {
    kotvy[i].addEventListener("click", function () {
    var kotva = document.getElementById("kotva-"+i).offsetTop;
  (atd.)
Čekal jsem, že když kliknu na první odkaz, odroluje se stránka na první nadpis H3, když kliknu na druhý odkaz, skončím na druhém nadpisu atd., což se však nenaplnilo. Vždy po kliknutí na jakýkoli odkaz s třídou "kotvy" se stránka odrolovala až na poslední kotvu (a to ještě jen díky tomu, že jsem id v nadpisech omylem začal číslovat až od jedničky). Když jsem hledal chybu, zjistil jsem, že proměnná i má na čtvrtém řádku (kde se vkládá hodnota do proměnné kotva) vždy - bez ohledu, na co se klikne - hodnotu o 1 vyšší, než je poslední hodnota indexu ve smyčce (odkazů mám celkem 5 a i má na čtvrtém řádku vždy hodnotu 5). Což mě překvapuje, protože jsem čekal, že i bude mít na třetím i čtvrtém řádku stejnou hodnotu. Takže by mě jednak zajímalo, proč ta hodnota je taková, jaká je, a ne taková, jakou jsem ji čekal, a jednak, jak z toho vybruslit, čili zda můžu nějak podobně použít addEventListener (bez jQuery) nebo nezbývá než pětkrát přidat onclick s dotyčnou funkcí přímo do HTML.
Děkuji.
weroro
Profil
Skús toto:
Živá ukázka
Dan Charousek
Profil
hypot:
Můžeš ID cílového nadpisu uvést ve vlastním atributu na odkazu: Živá ukázka Což má výhodu v tom, že nezáleží na pořadí prvků. Nebo cesta, kterou jsi se chtěl vydat původně: Živá ukázka
hypot
Profil
To poslední vypadá zajímavě, ale nerozumím syntaxi: nevím, k čemu se vztahuje na konci (i + 1) (a proč je to zapsáno takto), a nerozumím, proč je celá přechozí anonymní funkce v kulatých závorkách. A vlastně mi taky není jasné, jak se dosadí konkrétní hodnoty (kde se vezmou) za parametr index. Díky za vysvětlení.
Dan Charousek
Profil
hypot:
Je to celkem jednoduché:

for (var i = 0; i < kotvy.length; i++) {
    // ...
}

Tohle je jasné, iteruji nad kolekcí kotev. i jde od nuly a moje IDčka na kotvách jdou od 1 proto je tam to (i + 1), ale o tom později.

Proč nefunguje

for (var i = 0; i < kotvy.length; i++) {
    kotvy[i].addEventListener('click', function() {
            const nadpis = document.getElementById('kotva-' + i);
            console.log(nadpis.textContent);
    });
}

které se zdá být správné?

Na vině je rozsah viditelnosti proměnné i, která je definována v tom for cyklu (for (var i ...).
Jde o to, že takový zápis je ekvivalentní zápisu:
var i;
for (i = 0; i < kotvy.length; i++) {
    kotvy[i].addEventListener('click', function() {
            const nadpis = document.getElementById('kotva-' + index);
            console.log(nadpis.textContent);
    });
}

// proměnná i je definována i zde

To znamená, že po skončení cyklu hodnota i je 3. Důležité je to, že když potom nastane click event tak selectuješ element právě podle tohohle i, které je 3 nehledě na to, na které tlačítko klikneš. Jak to lze řešit? Ve svém předchozím příspěvku jsem nastínil 2 řešení. To první je vcelku jasné, takže rozeberu to druhé a přidám třetí nejjednodušší řešení.

Potřebuješ dosáhnout toho, aby každý callback toho eventu referencoval statickou proměnnou s pevně daným indexem. Proto potřebuješ zajistit, aby proměnná podle které vyhledáváš ty kotvy v document.getElementById nebyla závisla na té iterační proměnné i. Toho lze dosáhnout vytvořením nové scope pomocí funkce. Já jsem to napsal všechno najednou, takže proto ta anonymní funkce uzavřena do kulatých závorek a rovnou zavolána: (function(i) { console.log(i); })('Hello World!') - Okamžitě se zavolá a provede - vypíše se předaný parametr i no a já předávám 'Hello World', takže se vypíše hello world. Pokud bych to chtěl rozepsat, tak to můžu napsat takto:

// Toto je funkce, která nám vytvoří novou scope, která vrátí funkci, která se pak stane právě tím callbackem. Jde o to, že ta vnitrní funkce referencuje proměnnou index, která se už nijak nemění.
function callbackFactory(index) {
  return function() {
           const nadpis = document.getElementById('kotva-' + index);
          console.log(nadpis.textContent);
  }
}

for (...) {
  kotvy[i].addEventListener('click',callbackFactory(i));
}

Tohle je ekvivalentní té mé druhé ukázce.

Jiné řešení umožňuje nové klíčové slovo let, které bylo představeno v ES6 (kompatibilita s prohlížeči), které celý problém řeší daleko elegantněji. let je podobné klasickému var, s tím že pokud se pomocí let deklaruje iterační proměnná for cyklu, tak je ta proměnná viditelná pouze uvnitř daného for cyklu, ale především, v každé iteraci ukazuje na jinou instanci, takže tam nedochází k problémům, jako s var. Jinými slovy. Tvé původní řešení bude funkční, pokud vyměníš var za let:

for (let i=1; i<=kotvy.length; i++) {
    kotvy[i].addEventListener("click", function () {
    var kotva = document.getElementById("kotva-"+i).offsetTop;
  (atd.)


Toto už bude fungovat, tak jak má. Rozdílů mezi let a var je více, já jen zmínil ty, které jsou relevantní tomuto problému.
hypot
Profil
Přinejmenším část problému jsem myslím pochopil, i když některé věci mi zůstávají nejasné. Dovolím si zeptat se na jednu věc k tomu. Na stránce Closures je příklad
function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();
Nevím, proč není možné přímo zavolat funkci makeFunc a proč je nutné ji nejprve vložit do proměnné myFunc.
Dan Charousek
Profil
hypot:

Nevím, proč není možné přímo zavolat funkci makeFunc a proč je nutné ji nejprve vložit do proměnné myFunc.

Je to trochu jinak než píšeš. funkci makeFunc je možné zavolat bez přiřazení do proměnné, ale všimni si, co ta funkce vrací. Vrací další funkci, která se uloží do proměnné myFunc a pak se zavolá myFunc(). Pokud bys to chtěl udělat vše najednou, tak by to bylo makeFunc()().

Vaše odpověď


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: