Temat 34 · 2 × 45 min
jQuery – czytanie i porównanie z czystym JS
Otwórz źródło losowej strony firmowej, sklepu na WordPressie albo projektu z 2015 roku i zobaczysz $('#menu').slideToggle(). jQuery to biblioteka, która przez ponad dekadę była synonimem „JavaScriptu na stronie” – według W3Techs w październiku 2026 r. nadal działa na ok. 64 % wszystkich witryn. Nowych projektów raczej w niej nie zaczniesz, ale czytać ją musisz: w pracy odziedziczysz taki kod, a podstawa programowa INF.03 wymienia jQuery z nazwy. Ta lekcja uczy czytać jQuery przez porównanie z czystym JavaScriptem, który już znasz z lekcji 11–17 i 27.
Czym jest jQuery i dlaczego podbiło internet
jQuery (2006, John Resig) to jeden plik .js (ok. 30 kB po kompresji), który dodaje do strony funkcję $. W 2006–2015 rozwiązywała trzy realne problemy:
- Różnice między przeglądarkami. Internet Explorer 6–8 miał inne nazwy metod (
attachEventzamiastaddEventListener), inny model zdarzeń, brakquerySelector. jQuery ukrywało to za jednym API. - Rozwlekłość DOM.
document.getElementsByClassName('karta')plus pętla, żeby zmienić kolor – w jQuery$('.karta').css('color', 'red'). - AJAX przez
XMLHttpRequestbył koszmarnie niewygodny;$.ajaxi$.getJSONdawały to w jednej linii.
Potem przeglądarki dogoniły bibliotekę: querySelectorAll (2009), classList (2011), fetch (2015), addEventListener wszędzie od IE9. Dziś prawie każda linia jQuery ma równie krótki odpowiednik w czystym JS, a frameworki (React, Vue) rozwiązują problem, którego jQuery nie dotykało – synchronizację danych z widokiem. Dlatego nowy projekt w 2026 r. nie zaczyna się od jQuery, ale istniejące strony nie przestały działać i ktoś musi je utrzymywać.
Aktualne wersje: 3.7.1 (sierpień 2023, najczęściej spotykana) i 4.0.0 (styczeń 2026 – usunięto wsparcie IE 10 i starszych oraz przestarzałe funkcje typu $.trim, $.isArray; wszystko z tej lekcji działa w obu).
Dołączanie biblioteki
<!-- z CDN – wymaga internetu -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- albo lokalnie – plik pobrany raz, działa offline -->
<script src="js/jquery-3.7.1.min.js"></script>
<!-- twój kod ZAWSZE po bibliotece -->
<script src="js/app.js"></script>Po załadowaniu masz globalną funkcję jQuery i jej alias $. Starszy kod zaczyna się od „poczekaj, aż DOM będzie gotowy”:
$(document).ready(function () { /* … */ }); // wersja z 2010
$(function () { /* … */ }); // skrót – to samo
// czysty JS: document.addEventListener('DOMContentLoaded', () => { … }); albo <script defer>W placach zabaw tej lekcji jQuery jest dołączone w zakładce HTML – zajrzyj tam, zobaczysz ten sam tag <script src>.
$() i selektory
$(selektor) przyjmuje selektor CSS – taki sam jak w querySelectorAll – i zwraca obiekt jQuery: opakowanie na zero, jeden lub wiele elementów. Na tym obiekcie wołasz metody jQuery, a każda z nich działa na wszystkich znalezionych elementach i (zwykle) zwraca ten sam obiekt, więc wywołania można łańcuchować.
| jQuery | Czysty JS |
|---|---|
$('#wynik') | document.querySelector('#wynik') / getElementById('wynik') |
$('.karta') | document.querySelectorAll('.karta') |
$('ul li:first-child') | document.querySelectorAll('ul li:first-child') |
$('.karta').first() / .eq(2) | karty[0] / karty[2] |
$('#lista').find('li') | lista.querySelectorAll('li') |
$('#x').parent() / .closest('.karta') | x.parentElement / x.closest('.karta') |
$('#x').length | x !== null ? 1 : 0 |
Co wypisze ten kod, jeśli na stronie są trzy elementy z klasą karta?
console.log($('.karta').length, document.querySelector('.karta').length);<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<ul id="oceny">
<li>Matematyka: 4</li>
<li>Informatyka: 6</li>
<li>Polski: 3</li>
</ul>
<p id="info"></p>// jQuery: jeden łańcuch działa na wszystkich li
$('#oceny li').css('color', '#555').first().css('font-weight', 'bold');
$('#info').text('Pozycji: ' + $('#oceny li').length);
// to samo w czystym JS
const pozycje = document.querySelectorAll('#oceny li');
pozycje.forEach((li) => (li.style.color = '#555'));
pozycje[0].style.fontWeight = 'bold';
document.getElementById('info').textContent += ' | vanilla: ' + pozycje.length;Zdarzenia: .on()
// jQuery
$('#dodaj').on('click', function () {
console.log(this); // this = kliknięty element DOM (zwykła funkcja, nie strzałka!)
$(this).addClass('aktywny'); // żeby użyć metod jQuery, zapakuj this w $()
});
$('#dodaj').click(function () {}); // skrót spotykany w starym kodzie – to samo co .on('click')
// czysty JS
document.getElementById('dodaj').addEventListener('click', function (e) {
e.currentTarget.classList.add('aktywny');
});Najcenniejsza rzecz w .on() to wbudowana delegacja (lekcja 27): drugi argument to selektor dzieci, a nasłuchiwacz siedzi na rodzicu – działa też dla elementów dodanych później.
// jQuery: jeden nasłuchiwacz na liście, reaguje na każde li – także przyszłe
$('#lista').on('click', 'li', function () {
$(this).toggleClass('wykonane');
});
// czysty JS
document.getElementById('lista').addEventListener('click', (e) => {
const li = e.target.closest('li');
if (li) li.classList.toggle('wykonane');
});.off('click') zdejmuje nasłuchiwacze; .trigger('click') wywołuje zdarzenie programowo (vanilla: el.click() lub dispatchEvent).
Treść i wartości: .text(), .html(), .val()
Metody jQuery są jednocześnie getterem i setterem: bez argumentu czytają, z argumentem ustawiają. To najczęstsze źródło pomyłek przy przepisywaniu – w czystym JS to właściwości, nie funkcje.
| jQuery | Czysty JS | Uwagi |
|---|---|---|
$('#p').text() | p.textContent | odczyt |
$('#p').text('Suma: 5') | p.textContent = 'Suma: 5' | zapis, bez interpretacji HTML |
$('#p').html('<b>5</b>') | p.innerHTML = '<b>5</b>' | HTML interpretowany |
$('#pole').val() | pole.value | zawsze napis – jak w czystym JS |
$('#pole').val('') | pole.value = '' | czyszczenie |
$('#zgoda').prop('checked') | zgoda.checked | true/false |
$('#img').attr('src', 'b.jpg') | img.src = 'b.jpg' / setAttribute | atrybuty |
Pole #ilosc zawiera 7. Co wypisze ten kod?
var ilosc = $('#ilosc').val();
console.log(ilosc + 3, typeof ilosc);<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<label>Cena za kg <input id="cena" type="number" value="12"></label>
<label>Kilogramy <input id="kg" type="number" value="2.5"></label>
<button id="oblicz-jq">Oblicz (jQuery)</button>
<button id="oblicz-js">Oblicz (vanilla)</button>
<p id="wynik"></p>label { display: block; margin-bottom: 4px; }// jQuery
$('#oblicz-jq').on('click', function () {
var cena = parseFloat($('#cena').val());
var kg = parseFloat($('#kg').val());
$('#wynik').text('Koszt: ' + (cena * kg).toFixed(2) + ' zł').css('color', 'navy');
});
// czysty JS – identyczny efekt
document.getElementById('oblicz-js').addEventListener('click', () => {
const cena = parseFloat(document.getElementById('cena').value);
const kg = parseFloat(document.getElementById('kg').value);
const wynik = document.getElementById('wynik');
wynik.textContent = `Koszt: ${(cena * kg).toFixed(2)} zł`;
wynik.style.color = 'darkgreen';
});Style i klasy: .css(), .addClass(), .toggleClass()
| jQuery | Czysty JS |
|---|---|
$('#p').css('color', 'red') | p.style.color = 'red' |
$('#p').css({ color: 'red', fontSize: '20px' }) | Object.assign(p.style, { color: 'red', fontSize: '20px' }) |
$('#p').css('color') | getComputedStyle(p).color |
$('#p').addClass('aktywny') | p.classList.add('aktywny') |
$('#p').removeClass('aktywny') | p.classList.remove('aktywny') |
$('#p').toggleClass('aktywny') | p.classList.toggle('aktywny') |
$('#p').hasClass('aktywny') | p.classList.contains('aktywny') |
Różnica praktyczna: .css('background-color', …) przyjmuje nazwę CSS z myślnikiem lub camelCase – obie działają. W style.* musi być camelCase (backgroundColor). Jedna pułapka mniej w jQuery, ale za to kolejna rzecz, którą trzeba pamiętać przy przepisywaniu.
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<nav>
<button class="zakladka aktywna" data-cel="klient">Klient</button>
<button class="zakladka" data-cel="adres">Adres</button>
<button class="zakladka" data-cel="kontakt">Kontakt</button>
</nav>
<section id="klient" class="panel">Dane klienta…</section>
<section id="adres" class="panel" hidden>Ulica, miasto…</section>
<section id="kontakt" class="panel" hidden>E-mail, telefon…</section>
<p><label><input type="checkbox" id="tryb"> użyj wersji vanilla</label></p>.zakladka { padding: 6px 12px; border: 1px solid #999; background: #eee; cursor: pointer; }
.zakladka.aktywna { background: #15171c; color: #f2d338; }
.panel { padding: 10px; border: 1px solid #ccc; margin-top: 6px; }// wersja jQuery
$('nav').on('click', '.zakladka', function () {
if ($('#tryb').prop('checked')) return; // przełącznik tylko do porównania
$('.zakladka').removeClass('aktywna');
$(this).addClass('aktywna');
$('.panel').hide();
$('#' + $(this).data('cel')).show();
});
// wersja vanilla – ten sam efekt
document.querySelector('nav').addEventListener('click', (e) => {
const przycisk = e.target.closest('.zakladka');
if (!przycisk || !document.getElementById('tryb').checked) return;
document.querySelectorAll('.zakladka').forEach((z) => z.classList.remove('aktywna'));
przycisk.classList.add('aktywna');
document.querySelectorAll('.panel').forEach((p) => {
p.hidden = true;
p.style.display = ''; // usuń styl wpisany wcześniej przez .hide()/.show() jQuery
});
document.getElementById(przycisk.dataset.cel).hidden = false;
});Pokazywanie i ukrywanie: .hide(), .show(), .toggle(), .fadeIn()
Tu jQuery nadal wygląda wygodniej, bo ma wbudowane animacje:
| jQuery | Czysty JS |
|---|---|
$('#p').hide() | p.style.display = 'none' lub p.hidden = true |
$('#p').show() | p.style.display = '' lub p.hidden = false |
$('#p').toggle() | p.hidden = !p.hidden |
$('#p').fadeIn(400) / .fadeOut(400) | klasa + CSS transition: opacity .4s (lekcja 28) |
$('#p').slideToggle() | klasa + transition na max-height / grid-template-rows |
.hide() zapamiętuje poprzednie display, więc .show() przywraca flex czy inline-block, nie zawsze block. W czystym JS hidden robi to samo prościej – chyba że CSS nadpisuje display elementu; wtedy użyj klasy .ukryty { display: none }.
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<button id="jq">jQuery fadeToggle</button>
<button id="js">vanilla (klasa + transition)</button>
<p id="kom-jq" class="kom">Zapisano (jQuery)</p>
<p id="kom-js" class="kom widoczny">Zapisano (vanilla)</p>.kom { padding: 8px; background: #eef8f1; border-left: 4px solid #2e7d4f; }
#kom-js { opacity: 0; transition: opacity 0.4s; }
#kom-js.widoczny { opacity: 1; }$('#jq').on('click', function () {
$('#kom-jq').fadeToggle(400); // animuje opacity i na końcu ustawia display:none
});
document.getElementById('js').addEventListener('click', () => {
document.getElementById('kom-js').classList.toggle('widoczny');
});Tworzenie elementów: .append(), .prepend(), .remove(), .each()
// jQuery – element z napisu HTML
$('#lista').append('<li class="nowe">' + tytul + '</li>');
$('#lista').prepend($('<li>').text(tytul)); // $('<li>') tworzy element; .text() chroni przed HTML w danych
$('#lista li.wykonane').remove();
$('#lista').empty(); // usuń wszystkie dzieci
// czysty JS
const li = document.createElement('li');
li.className = 'nowe';
li.textContent = tytul;
lista.append(li); // append przyjmuje też napisy: lista.append('tekst')
lista.querySelectorAll('li.wykonane').forEach((el) => el.remove());
lista.replaceChildren(); // = empty().each() to pętla po elementach obiektu jQuery. Kolejność argumentów jest odwrotna niż w forEach – najpierw indeks, potem element (zwykły element DOM, nie obiekt jQuery):
$('#lista li').each(function (indeks, element) {
$(element).text((indeks + 1) + '. ' + $(element).text()); // this === element
});
document.querySelectorAll('#lista li').forEach((element, indeks) => {
element.textContent = `${indeks + 1}. ${element.textContent}`;
});Co zrobi ten kod?
$('#lista li').each(function (a, b) {
console.log(a);
});<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<form id="form"><input id="tytul" placeholder="Nowe zadanie" required> <button>Dodaj</button></form>
<ul id="lista"></ul>
<p id="licznik">Wykonane: 0 z 0</p>li { cursor: pointer; }
li.wykonane { text-decoration: line-through; color: #888; }function odswiezLicznik() {
$('#licznik').text('Wykonane: ' + $('#lista li.wykonane').length + ' z ' + $('#lista li').length);
}
$('#form').on('submit', function (e) {
e.preventDefault();
var tytul = $('#tytul').val().trim();
if (!tytul) return;
$('#lista').append($('<li>').text(tytul).hide().fadeIn(300));
$('#tytul').val('');
odswiezLicznik();
});
// delegacja: li dodane później też reagują
$('#lista').on('click', 'li', function () {
$(this).toggleClass('wykonane');
odswiezLicznik();
});Przepisanie tego placu zabaw na czysty JS to zadanie 3.
$.ajax vs fetch
Przed fetch jQuery było głównym sposobem na AJAX. W starym kodzie spotkasz trzy formy:
// 1. skrót do GET z JSON
$.getJSON('/api/zadania', function (dane) {
console.log(dane);
});
// 2. pełna forma z callbackami (w kodzie z lat 2010–2015)
$.ajax({
url: '/api/zadania',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ tytul: 'Nowe' }),
success: function (odpowiedz) { console.log(odpowiedz); },
error: function (xhr) { console.log('Błąd ' + xhr.status); },
});
// 3. forma „promise-like” (od jQuery 1.5) – .done/.fail zamiast then/catch
$.ajax({ url: '/api/zadania' }).done(function (dane) { /* … */ }).fail(function () { /* … */ });const res = await fetch('/api/zadania', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tytul: 'Nowe' }),
});
if (!res.ok) throw new Error(`Błąd ${res.status}`);
const odpowiedz = await res.json();Dwie różnice, które gryzą przy przepisywaniu: $.ajax sam parsuje JSON (gdy dataType: 'json' lub serwer wysyła Content-Type: application/json) i traktuje 404/500 jako błąd (error/fail). fetch wymaga await res.json() i uznaje 404 za poprawną odpowiedź – stąd obowiązkowe if (!res.ok).
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<button id="jq">$.getJSON</button>
<button id="js">fetch</button>
<pre id="out"></pre>var URL_TODO = 'https://jsonplaceholder.typicode.com/todos/1';
$('#jq').on('click', function () {
$.getJSON(URL_TODO)
.done(function (todo) { $('#out').text('jQuery: ' + JSON.stringify(todo)); })
.fail(function (xhr) { $('#out').text('Błąd ' + xhr.status); });
});
document.getElementById('js').addEventListener('click', async () => {
const out = document.getElementById('out');
try {
const res = await fetch(URL_TODO);
if (!res.ok) throw new Error(res.status);
out.textContent = 'fetch: ' + JSON.stringify(await res.json());
} catch (e) {
out.textContent = 'Błąd ' + e.message;
}
});Tabela: jQuery obok czystego JS
| Zadanie | jQuery | Czysty JS (2026) |
|---|---|---|
| gotowy DOM | $(function () {…}) | <script defer> lub DOMContentLoaded |
| jeden element | $('#id') | document.getElementById('id') |
| wiele elementów | $('.k') | document.querySelectorAll('.k') |
| zdarzenie | .on('click', fn) | .addEventListener('click', fn) |
| delegacja | .on('click', 'li', fn) | addEventListener + e.target.closest('li') |
| tekst | .text() / .text(x) | .textContent |
| HTML | .html() / .html(x) | .innerHTML |
| wartość pola | .val() / .val(x) | .value |
| checkbox | .prop('checked') | .checked |
| atrybut | .attr('src', x) | .src = x / .setAttribute |
| styl | .css('color', x) | .style.color = x |
| klasy | .addClass/.removeClass/.toggleClass/.hasClass | .classList.add/remove/toggle/contains |
| ukryj/pokaż | .hide()/.show()/.toggle() | .hidden = true/false |
| animacja | .fadeIn()/.slideToggle() | klasa + CSS transition |
| dodaj element | .append('<li>…</li>') | createElement + .append() |
| usuń | .remove() / .empty() | .remove() / .replaceChildren() |
| pętla | .each(function (i, el) {…}) | .forEach((el, i) => {…}) |
| rodzic / przodek | .parent() / .closest(sel) | .parentElement / .closest(sel) |
| GET JSON | $.getJSON(url, cb) | await (await fetch(url)).json() |
| POST JSON | $.ajax({ method:'POST', data: JSON.stringify(o), contentType:'application/json' }) | fetch(url, { method:'POST', headers:{…}, body: JSON.stringify(o) }) |
Czytanie starego kodu: komentarz linia po linii
Taki fragment znajdziesz w tysiącach motywów WordPress. Przeczytaj go, zanim spojrzysz na komentarze:
$(document).ready(function () {
$('.menu-toggle').click(function (e) {
e.preventDefault();
$(this).toggleClass('open');
$('#menu').slideToggle(300);
});
$('#menu a').each(function () {
if ($(this).attr('href') === window.location.pathname) {
$(this).parent().addClass('current');
}
});
$('.alert .close').on('click', function () {
$(this).closest('.alert').fadeOut(200, function () { $(this).remove(); });
});
});$(document).ready(fn)– uruchom po zbudowaniu DOM..menu-toggle+.click– przycisk hamburgera:preventDefault(to zwykle<a href="#">), przełącz klasęopenna przycisku, rozwiń/zwiń#menuz animacją 300 ms..eachpo linkach menu: jeślihrefrówna się aktualnej ścieżce, rodzicowi (li) dodaj klasęcurrent– podświetlenie bieżącej strony.- Zamknięcie alertu: znajdź najbliższy przodek
.alert, wygaś go, a po zakończeniu animacji (callbackfadeOut) usuń z DOM.
Ten sam fragment w czystym JS – zadanie 4 każe ci to napisać; porównaj potem z własną wersją.
Pułapki
Ściąga
| Składnia | Co robi | Vanilla |
|---|---|---|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"> | dołącz bibliotekę (przed własnym kodem) | – |
$(fn) | po załadowaniu DOM | defer / DOMContentLoaded |
$(sel) | zbiór elementów (ma .length) | querySelectorAll(sel) |
$(sel).on('click', fn) | zdarzenie; this = element | addEventListener |
$(sel).on('click', 'li', fn) | delegacja | closest('li') w nasłuchiwaczu |
.text(x) / .html(x) / .val(x) | setter; bez argumentu getter | .textContent / .innerHTML / .value |
.css('prop', x) | styl (nazwa z myślnikiem lub camelCase) | .style.prop (camelCase) |
.addClass/.removeClass/.toggleClass/.hasClass | klasy | .classList.* |
.hide()/.show()/.toggle() | display: none i z powrotem | .hidden |
.fadeIn(ms)/.fadeOut(ms)/.slideToggle(ms) | animacje | CSS transition + klasa |
.append(x)/.prepend(x)/.remove()/.empty() | drzewo DOM | .append/.prepend/.remove/.replaceChildren |
.each(function (i, el) {…}) | pętla (indeks, element) | .forEach((el, i) => …) |
$(el) / $(sel)[0] | element → jQuery / jQuery → element | – |
$.getJSON(url, cb) / $.ajax({...}) | AJAX; 404 = błąd; JSON parsowany | fetch + res.ok + res.json() |
Zadania
Przetłumacz dziesięć linii
Przepisz każdą linię na czysty JS (zakładamy, że selektory trafiają w pojedyncze elementy, poza .karta):
$('#tytul').text('Sklep');
$('#cena').val('99.99');
$('#opis').html('<b>Nowość</b>');
$('#baner').hide();
$('.karta').addClass('wyrozniona');
$('#przycisk').on('click', pokazKoszyk);
$('#zdjecie').attr('src', 'duze.jpg');
$('#komunikat').css('color', 'red');
var zaznaczony = $('#zgoda').prop('checked');
$('#lista').append('<li>Nowy</li>');Pokaż rozwiązanie
document.getElementById('tytul').textContent = 'Sklep';
document.getElementById('cena').value = '99.99';
document.getElementById('opis').innerHTML = '<b>Nowość</b>';
document.getElementById('baner').hidden = true; // lub .style.display = 'none'
document.querySelectorAll('.karta').forEach((k) => k.classList.add('wyrozniona'));
document.getElementById('przycisk').addEventListener('click', pokazKoszyk);
document.getElementById('zdjecie').src = 'duze.jpg'; // lub setAttribute('src', 'duze.jpg')
document.getElementById('komunikat').style.color = 'red';
const zaznaczony = document.getElementById('zgoda').checked;
const li = document.createElement('li'); // bezpieczniej niż innerHTML +=
li.textContent = 'Nowy';
document.getElementById('lista').append(li);Uwaga do linii 5: w jQuery jedna metoda działa na całym zbiorze; w czystym JS querySelectorAll zwraca kolekcję, więc potrzebna jest pętla.
Galeria z lekcji 17 w jQuery
Weź galerię < / > z zawijaniem indeksu 1–7 z lekcji 17 (obrazy 1.jpg…7.jpg, duży <img id="duzy">, podpis <p id="numer">). Napisz ją w jQuery (dialekt z lat 2012–2015: var, function, .click, .attr, .text), a potem policz linie w obu wersjach. Czy jQuery jest wyraźnie krótsze?
Pokaż rozwiązanie
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<button id="poprzedni"><</button>
<img id="duzy" src="1.jpg" alt="Zdjęcie 1" width="300">
<button id="nastepny">></button>
<p id="numer">1 / 7</p>var biezacy = 1;
var ILE = 7;
function pokaz() {
$('#duzy').attr('src', biezacy + '.jpg').attr('alt', 'Zdjęcie ' + biezacy);
$('#numer').text(biezacy + ' / ' + ILE);
}
$('#nastepny').click(function () {
biezacy = biezacy % ILE + 1; // 7 → 1
pokaz();
});
$('#poprzedni').click(function () {
biezacy = biezacy === 1 ? ILE : biezacy - 1;
pokaz();
});let biezacy = 1;
const ILE = 7;
const duzy = document.getElementById('duzy');
const numer = document.getElementById('numer');
function pokaz() {
duzy.src = `${biezacy}.jpg`;
duzy.alt = `Zdjęcie ${biezacy}`;
numer.textContent = `${biezacy} / ${ILE}`;
}
document.getElementById('nastepny').addEventListener('click', () => {
biezacy = (biezacy % ILE) + 1;
pokaz();
});
document.getElementById('poprzedni').addEventListener('click', () => {
biezacy = biezacy === 1 ? ILE : biezacy - 1;
pokaz();
});Obie wersje mają po kilkanaście linii – logika (indeks, zawijanie) jest identyczna, bo jQuery nie dotyka logiki, tylko dostępu do DOM. Różnica w długości jest kosmetyczna; różnica w zależnościach – 30 kB biblioteki z sieci kontra zero.
Lista zadań: z jQuery na vanilla
Przepisz plac zabaw „Lista zadań w jQuery” z tej lekcji na czysty JS. Zachowaj: dodawanie przez submit (z preventDefault), delegację kliknięć na ul, przełączanie klasy wykonane, licznik Wykonane: X z Y oraz efekt pojawiania się nowego li (użyj klasy i transition, nie fadeIn).
Pokaż rozwiązanie
<form id="form"><input id="tytul" placeholder="Nowe zadanie" required> <button>Dodaj</button></form>
<ul id="lista"></ul>
<p id="licznik">Wykonane: 0 z 0</p>li { cursor: pointer; opacity: 0; transition: opacity 0.3s; }
li.widoczne { opacity: 1; }
li.wykonane { text-decoration: line-through; color: #888; }const form = document.getElementById('form');
const pole = document.getElementById('tytul');
const lista = document.getElementById('lista');
const licznik = document.getElementById('licznik');
function odswiezLicznik() {
const wszystkie = lista.querySelectorAll('li').length;
const wykonane = lista.querySelectorAll('li.wykonane').length;
licznik.textContent = `Wykonane: ${wykonane} z ${wszystkie}`;
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const tytul = pole.value.trim();
if (!tytul) return;
const li = document.createElement('li');
li.textContent = tytul; // odpowiednik $('<li>').text(tytul)
lista.append(li);
void li.offsetWidth; // wymuś obliczenie stylu startowego (opacity: 0)
li.classList.add('widoczne'); // dopiero teraz zmiana uruchomi transition
pole.value = '';
odswiezLicznik();
});
// delegacja: odpowiednik $('#lista').on('click', 'li', …)
lista.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li) return;
li.classList.toggle('wykonane');
odswiezLicznik();
});Linia void li.offsetWidth jest potrzebna, bo klasa dodana zaraz po wstawieniu elementu nie uruchomi transition – przeglądarka musi najpierw „zobaczyć” stan opacity: 0. Odczyt offsetWidth zmusza ją do policzenia stylów (pojedynczy requestAnimationFrame nie wystarcza, bo jego callback wykonuje się jeszcze przed pierwszym obliczeniem stylów nowego elementu). To jedyny fragment, w którym fadeIn jQuery jest realnie wygodniejszy.
Legacy menu bez jQuery
Przepisz fragment legacy.js z sekcji „Czytanie starego kodu” na czysty JS tak, by motyw WordPress mógł pozbyć się biblioteki. Wymagania: slideToggle zastąp klasą open na #menu i CSS z transition na max-height; podświetlenie bieżącej strony ma działać dla wielu linków; zamknięcie alertu ma usunąć element z DOM po zakończeniu animacji wygaszania (zdarzenie transitionend). Kod ma działać przy <script defer>.
Pokaż rozwiązanie
#menu { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; }
#menu.open { max-height: 500px; }
.alert { transition: opacity 0.2s; }
.alert.znika { opacity: 0; }// <script src="menu.js" defer> – DOM gotowy, bez $(document).ready
// 1. hamburger: przełącz klasę na przycisku i rozwiń/zwiń menu
for (const toggle of document.querySelectorAll('.menu-toggle')) {
toggle.addEventListener('click', (e) => {
e.preventDefault();
toggle.classList.toggle('open');
document.getElementById('menu').classList.toggle('open');
});
}
// 2. podświetl link do bieżącej strony (jak .each + .attr('href'))
for (const link of document.querySelectorAll('#menu a')) {
if (link.getAttribute('href') === window.location.pathname) {
link.parentElement.classList.add('current');
}
}
// 3. zamknięcie alertu: wygaś, a po animacji usuń (jak callback fadeOut)
for (const close of document.querySelectorAll('.alert .close')) {
close.addEventListener('click', () => {
const alert = close.closest('.alert');
alert.classList.add('znika');
alert.addEventListener('transitionend', () => alert.remove(), { once: true });
});
}Uwagi: link.getAttribute('href') zamiast link.href, bo .href zwraca pełny adres z domeną, a jQuery .attr('href') – dokładnie to, co jest w HTML. { once: true } zdejmuje nasłuchiwacz po pierwszym użyciu – element i tak znika. Jeśli alert mógłby nie mieć transition (starsza przeglądarka, prefers-reduced-motion), dodaj zapasowe setTimeout(() => alert.remove(), 250).
Test powtórkowy
Test powtórkowy
Dziewięć pytań o jQuery i jego odpowiedniki w czystym JS. Jedna poprawna odpowiedź.
Który zapis jQuery odpowiada document.getElementById('wynik').innerHTML = 'Suma: 5'?
Co zwraca $('#pole').val() dla pola type=number z wpisaną wartością 5?
Który kod w czystym JS odpowiada $('.karta').addClass('aktywna')?
Co robi $('#lista').on('click', 'li', fn)?
Dlaczego ten kod nie działa?
$('#baner').style.display = 'none';W .each(function (a, b) {…}) czym są a i b?
Czym $.ajax różni się od fetch przy odpowiedzi 404?
Co się stanie na stanowisku egzaminacyjnym INF.03 ze skryptem zaczynającym się od $('#oblicz').on('click', …) i tagiem script z CDN?
Który zapis uruchamia kod po zbudowaniu DOM w jQuery?