Temat 23 · 2 × 45 min
Zasięg, hoisting, domknięcia i this
Wiesz już, że zmienna zadeklarowana w funkcji nie jest widoczna poza nią. Ta lekcja porządkuje dlaczego – i co z tego wynika, gdy funkcje tworzą inne funkcje albo są wywoływane później (w setTimeout, w addEventListener). Trzy pojęcia – zasięg (scope), domknięcie (closure) i this – stoją za większością „dziwnych” zachowań JavaScriptu, na które natkniesz się w cudzym kodzie, w pytaniach rekrutacyjnych i w React. Dobra wiadomość: reguł jest kilka i są spójne.
Zasięg: blokowy i funkcyjny
Zasięg (scope) to obszar kodu, w którym nazwa zmiennej jest widoczna. W JavaScripcie są trzy poziomy:
- globalny – zmienne zadeklarowane poza wszystkimi funkcjami i blokami; widoczne wszędzie,
- funkcyjny – zmienne zadeklarowane w funkcji; widoczne tylko w niej (i w funkcjach zagnieżdżonych),
- blokowy – zmienne z
let/constzadeklarowane w{ … }(pętla,if, zwykły blok); widoczne tylko w tym bloku.
Różnica między let/const a var sprowadza się do tego trzeciego poziomu: var ignoruje bloki i widzi tylko funkcje. Dlatego zmienna z var zadeklarowana w if czy w pętli „wycieka” poza nie.
Co wypisze ten kod?
if (true) {
var a = 1;
let b = 2;
}
console.log(a);
console.log(b);const globalna = 'widoczna wszędzie';
function pokaz() {
const lokalna = 'tylko w funkcji';
if (true) {
var funkcyjna = 'var widzi funkcję, nie blok';
let blokowa = 'tylko w if';
console.log(globalna, '|', lokalna, '|', blokowa);
}
console.log(funkcyjna); // działa – var „wyciekło” z bloku if
// console.log(blokowa); // ReferenceError – let zostało w bloku
}
pokaz();
console.log(typeof funkcyjna); // 'undefined' – funkcyjna była wewnątrz pokaz()
console.log(lokalna); // ReferenceError: lokalna is not definedZasięgi się zagnieżdżają: funkcja wewnętrzna widzi zmienne zewnętrznej, ale nie odwrotnie. Gdy silnik szuka nazwy, idzie od najbardziej wewnętrznego zasięgu na zewnątrz i bierze pierwszą, którą znajdzie. Jeśli w środku zadeklarujesz zmienną o tej samej nazwie co na zewnątrz, zewnętrzna jest w tym bloku przesłonięta (shadowing) – nie zmieniona, tylko niewidoczna.
let poziom = 'zewnętrzny';
function test() {
let poziom = 'wewnętrzny'; // nowa zmienna, przesłania zewnętrzną
console.log('w funkcji:', poziom);
}
test();
console.log('na zewnątrz:', poziom); // zewnętrzna nietknięta
// bez let/const w środku – to przypisanie do zewnętrznej, nie nowa zmienna
function zmien() {
poziom = 'zmieniony';
}
zmien();
console.log('po zmien():', poziom);Hoisting i strefa martwa (TDZ)
Przed wykonaniem kodu silnik najpierw „skanuje” zasięg i rejestruje wszystkie deklaracje. To zjawisko nazywa się hoisting (wyniesienie). Jego skutki różnią się w zależności od sposobu deklaracji:
| Deklaracja | Co jest wynoszone | Odczyt przed linią deklaracji |
|---|---|---|
function f() {} | cała funkcja z ciałem | działa – można wywołać |
var x = 1 | tylko nazwa (wartość undefined) | undefined, bez błędu |
let x = 1, const x = 1 | nazwa, ale bez dostępu | ReferenceError (TDZ) |
const f = () => {} | jak const | ReferenceError |
Obszar od początku bloku do linii z let/const to strefa martwa (Temporal Dead Zone, TDZ): zmienna „już istnieje”, ale jeszcze nie wolno jej dotknąć. To celowe – błąd jest lepszy niż ciche undefined.
Co wypisze ten kod?
console.log(a);
var a = 10;
console.log(typeof b);
function b() {}// deklaracja funkcji – można wywołać przed napisaniem
console.log(powitanie('Ola'));
function powitanie(imie) {
return `Cześć, ${imie}!`;
}
// var – nazwa wyniesiona, wartość jeszcze nie
console.log(licznik); // undefined (nie błąd!)
var licznik = 5;
console.log(licznik); // 5// let/const – strefa martwa
function pokaz() {
console.log('przed deklaracją…');
console.log(cena); // ReferenceError: Cannot access 'cena' before initialization
let cena = 100;
}
pokaz();To dlatego funkcje strzałkowe przypisane do const trzeba zdefiniować przed użyciem, a zwykłe function można trzymać na dole pliku. Oba style są poprawne; w zespole trzymaj się jednego.
Klasyka: var w pętli i setTimeout
Najsłynniejszy skutek zasięgu funkcyjnego var. Pętla rejestruje trzy funkcje do wykonania „za chwilę”. Gdy w końcu się wykonują, pętla dawno się skończyła – i wszystkie trzy patrzą na tę samą zmienną i, która ma wartość końcową.
Co wypisze ten kod (po chwili)?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var:', i), 0);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let:', j), 0);
}Z let każda iteracja pętli dostaje własną, świeżą zmienną j, a callback zapamiętuje tę, która istniała w jego iteracji. To właśnie domknięcie – i powód, dla którego ta lekcja łączy zasięg z closure. Przed ES6 obchodzono to funkcją natychmiast wywoływaną (IIFE), którą jeszcze zobaczysz w starszym kodzie: (function (k) { setTimeout(function () { console.log(k); }); })(i);.
Domknięcie (closure)
Domknięcie to funkcja razem ze zmiennymi z zasięgu, w którym powstała. Gdy funkcja wewnętrzna odwołuje się do zmiennej funkcji zewnętrznej, ta zmienna nie znika po zakończeniu zewnętrznej – żyje tak długo, jak długo żyje wewnętrzna funkcja. Funkcja „pamięta” swoje otoczenie.
Najprostszy przykład: fabryka liczników. Funkcja zrobLicznik tworzy zmienną stan i zwraca funkcję, która ją zwiększa. Z zewnątrz do stan nie ma dostępu – można go zmieniać tylko przez zwróconą funkcję.
Co wypisze ten kod?
function zrobLicznik() {
let stan = 0;
return () => ++stan;
}
const a = zrobLicznik();
const b = zrobLicznik();
a(); a();
console.log(a(), b());function zrobLicznik(start = 0) {
let stan = start; // „prywatna” zmienna – widzi ją tylko zwrócona funkcja
return () => {
stan++;
return stan;
};
}
const kliknieciaA = zrobLicznik();
const kliknieciaB = zrobLicznik(100);
console.log(kliknieciaA(), kliknieciaA(), kliknieciaA()); // 1 2 3
console.log(kliknieciaB()); // 101 – własny stan
console.log(typeof stan); // 'undefined' – z zewnątrz niedostępneZwróć uwagę: po return funkcja zrobLicznik już się skończyła, a mimo to stan istnieje i jest aktualizowany. Każde wywołanie fabryki daje nową, niezależną parę (zmienna + funkcja).
Obiekt z prywatnym stanem
Fabryka może zwrócić kilka funkcji w obiekcie – wszystkie dzielą ten sam stan, a świat zewnętrzny widzi tylko to, co zwróciłeś. To wzorzec, który przed klasami (i polami prywatnymi #, zob. lekcję o klasach) służył do hermetyzacji.
function zrobKonto(saldoPoczatkowe) {
let saldo = saldoPoczatkowe;
const historia = [];
return {
wplac(kwota) {
saldo += kwota;
historia.push(`+${kwota}`);
},
wyplac(kwota) {
if (kwota > saldo) return false;
saldo -= kwota;
historia.push(`-${kwota}`);
return true;
},
stan: () => saldo,
historia: () => [...historia], // kopia, żeby nikt nie zmienił oryginału
};
}
const konto = zrobKonto(100);
konto.wplac(50);
console.log(konto.wyplac(500)); // false – za mało
console.log(konto.wyplac(30)); // true
console.log(konto.stan(), konto.historia());
console.log(konto.saldo); // undefined – saldo nie jest właściwością, tylko zmienną w domknięciuDomknięcie w obsłudze zdarzeń – licznik bez zmiennej globalnej
W lekcji o stanie aplikacji licznik trzymałeś w zmiennej globalnej. Domknięcie pozwala zamknąć stan w funkcji, która konfiguruje przycisk – a wtedy możesz mieć wiele niezależnych liczników z tego samego kodu.
<button class="licznik">Kliknięto: 0</button>
<button class="licznik">Kliknięto: 0</button>
<button class="licznik">Kliknięto: 0</button>button { font-size: 16px; padding: 8px 14px; margin: 6px; }function podlaczLicznik(przycisk) {
let liczba = 0; // osobna zmienna dla każdego przycisku
przycisk.addEventListener('click', () => {
liczba++;
przycisk.textContent = `Kliknięto: ${liczba}`;
});
}
document.querySelectorAll('.licznik').forEach(podlaczLicznik);debounce – opóźnij reakcję, aż użytkownik skończy pisać
Praktyczny i bardzo częsty w pracy przykład domknięcia. Pole wyszukiwania wysyła zapytanie przy każdym wciśniętym klawiszu – to marnotrawstwo. debounce(fn, ms) zwraca nową funkcję, która wywoła fn dopiero wtedy, gdy przez ms milisekund nie było kolejnego wywołania. Identyfikator timera żyje w domknięciu między wywołaniami.
function debounce(fn, ms) {
let timer; // pamiętany między wywołaniami
return (...args) => {
clearTimeout(timer); // odwołaj poprzednie oczekiwanie
timer = setTimeout(() => fn(...args), ms);
};
}
const szukaj = debounce((fraza) => console.log('Szukam:', fraza), 50);
// symulacja szybkiego pisania – tylko ostatnie wywołanie dotrze do fn
szukaj('j');
szukaj('ja');
szukaj('jav');
szukaj('java');<input id="szukaj" placeholder="Wpisz szybko kilka liter…" autocomplete="off">
<p>Bez debounce: <span id="zwykle">0</span> wywołań</p>
<p>Z debounce (400 ms): <span id="opoznione">0</span> wywołań, ostatnia fraza: <strong id="fraza">–</strong></p>function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
const pole = document.getElementById('szukaj');
let zwykle = 0;
let opoznione = 0;
const wyszukaj = debounce((fraza) => {
opoznione++;
document.getElementById('opoznione').textContent = opoznione;
document.getElementById('fraza').textContent = fraza || '–';
}, 400);
pole.addEventListener('input', () => {
zwykle++;
document.getElementById('zwykle').textContent = zwykle;
wyszukaj(pole.value);
});this – kto wywołał funkcję
this to nie zmienna, którą deklarujesz – to słowo kluczowe, którego wartość zależy od tego, jak funkcja została wywołana, a nie gdzie została napisana. Cztery reguły, które pokrywają prawie wszystko:
obiekt.metoda()–thistoobiekt(ten przed kropką).funkcja()bez kropki –thisto obiekt globalny (windoww przeglądarce), a w trybie ścisłym (moduły, klasy)undefined.- Funkcja strzałkowa – nie ma własnego
this; bierze je z miejsca, w którym została napisana (z otaczającej funkcji). - Obsługa zdarzenia
el.addEventListener('click', function () {…})–thisto element, na którym nasłuchujesz (ale w strzałce – zob. regułę 3 – już nie).
Co wypisze ten kod?
const uczen = {
imie: 'Kasia',
przedstaw() { return 'Jestem ' + this.imie; },
};
const f = uczen.przedstaw;
console.log(uczen.przedstaw());
console.log(f());const koszyk = {
pozycje: ['mysz', 'kabel'],
pokaz() {
return `W koszyku: ${this.pozycje.join(', ')}`;
},
czyToJa() {
return this === koszyk;
},
};
console.log(koszyk.pokaz()); // reguła 1: this = koszyk
const oderwana = koszyk.pokaz; // sama funkcja, bez koszyka
try {
console.log(oderwana()); // reguła 2: this = obiekt globalny → this.pozycje to undefined
} catch (e) {
console.log('Błąd:', e.message); // Cannot read properties of undefined (reading 'join')
}
// ten sam problem przy przekazaniu metody jako callbacku
console.log(koszyk.czyToJa()); // true
setTimeout(() => console.log('w setTimeout(koszyk.czyToJa):', koszyk.czyToJa()), 0); // true – wywołanie z kropką
const callback = koszyk.czyToJa;
setTimeout(() => console.log('oderwany callback:', callback()), 0); // false – „bez kropki”Dokładnie to dzieje się przy setTimeout(obiekt.metoda, 1000) czy addEventListener('click', obiekt.metoda): przeglądarka wywołuje funkcję sama, bez obiektu przed kropką, więc this w środku nie jest obiektem.
Strzałki dziedziczą this – stąd ich popularność w callbackach
const licznikSekund = {
sekundy: 0,
startZwykly() {
setTimeout(function () {
// this to NIE licznikSekund – zwykła funkcja wywołana przez setTimeout
console.log('zwykła funkcja:', this === licznikSekund, typeof this.sekundy);
}, 0);
},
startStrzalka() {
setTimeout(() => {
// strzałka bierze this z metody startStrzalka, czyli licznikSekund
this.sekundy++;
console.log('strzałka:', this === licznikSekund, this.sekundy);
}, 0);
},
};
licznikSekund.startZwykly();
licznikSekund.startStrzalka();To działa też w forEach, map i każdym innym callbacku wewnątrz metody. Reguła praktyczna: metody obiektów i klas pisz zwykłą składnią nazwa() {} (potrzebują własnego this), callbacki wewnątrz nich pisz strzałkami (mają dziedziczyć this). Odwrotnie będzie źle: metoda-strzałka w literale obiektu nie widzi obiektu, bo bierze this z zewnątrz, gdzie obiektu jeszcze nie ma.
Naprawianie this: bind
Gdy musisz przekazać metodę jako callback (np. do addEventListener) i zachować jej this, użyj metoda.bind(obiekt) – zwraca nową funkcję z this przypiętym na stałe. Alternatywa to opakowanie w strzałkę: () => obiekt.metoda().
const gracz = {
imie: 'Nova',
punkty: 0,
dodajPunkt() {
this.punkty++;
console.log(`${this.imie}: ${this.punkty}`);
},
};
const przypieta = gracz.dodajPunkt.bind(gracz);
przypieta(); // this = gracz, mimo wywołania „bez kropki”
setTimeout(przypieta, 0); // też działa
setTimeout(() => gracz.dodajPunkt(), 0); // alternatywa – strzałka wywołuje z kropkąthis w obsłudze zdarzeń
<button id="zwykly">function – this to przycisk</button>
<button id="strzalka">strzałka – this to window</button>
<p id="wynik">Kliknij przycisk.</p>const wynik = document.getElementById('wynik');
document.getElementById('zwykly').addEventListener('click', function () {
// this = element, na którym nasłuchujemy
wynik.textContent = `this.id = ${this.id}, this.tagName = ${this.tagName}`;
});
document.getElementById('strzalka').addEventListener('click', (event) => {
// strzałka: this z otoczenia (skrypt → window), element bierzemy z event.currentTarget
wynik.textContent = `this === window: ${this === window}; element to event.currentTarget: ${event.currentTarget.id}`;
});W nowym kodzie zamiast polegać na this w handlerze używaj event.currentTarget (element z nasłuchiwaczem) lub event.target (element faktycznie kliknięty) – działa jednakowo w obu rodzajach funkcji. Różnicę między nimi omawia lekcja o zdarzeniach zaawansowanych.
Ściąga
| Pojęcie / składnia | Co znaczy | Przykład |
|---|---|---|
| zasięg blokowy | let/const widoczne tylko w { } | if (x) { let a = 1; } → a niedostępne po } |
| zasięg funkcyjny | var widoczne w całej funkcji, ignoruje bloki | if (x) { var a = 1; } → a dostępne po } |
| przesłanianie | wewnętrzna deklaracja tej samej nazwy ukrywa zewnętrzną | let x = 1; { let x = 2; } |
hoisting function | deklaracja wyniesiona z ciałem | wywołanie przed definicją działa |
hoisting var | nazwa wyniesiona, wartość undefined | console.log(a); var a = 1; → undefined |
| TDZ | let/const przed deklaracją | console.log(a); let a = 1; → ReferenceError |
var w pętli + setTimeout | jedna zmienna dla wszystkich iteracji | wypisuje 3 3 3 |
let w pętli | nowa zmienna co iterację | wypisuje 0 1 2 |
| domknięcie | funkcja + zmienne z zasięgu, gdzie powstała | const inc = zrobLicznik(); inc(); |
| fabryka ze stanem | zwróć funkcje, które dzielą prywatną zmienną | return { wplac, wyplac, stan } |
debounce(fn, ms) | wywołaj fn dopiero po ms ciszy | input.addEventListener('input', debounce(szukaj, 300)) |
obj.m() | this === obj | koszyk.pokaz() |
f() (bez kropki) | this globalne lub undefined (strict) | oderwana metoda traci obiekt |
() => {} | brak własnego this, bierze z otoczenia | callback w metodzie |
f.bind(obj) | nowa funkcja z this na stałe | btn.addEventListener('click', gra.ruch.bind(gra)) |
handler function | this to element z nasłuchiwaczem | this.classList.toggle('on') |
event.currentTarget | element z nasłuchiwaczem, niezależnie od this | działa też w strzałce |
Zadania
Przewidź, zanim uruchomisz
Dla każdego fragmentu zapisz na kartce, co wypisze konsola i dlaczego. Dopiero potem uruchom rozwiązanie i porównaj.
// A
var x = 1;
function f() { var x = 2; return x; }
console.log(f(), x);
// B
let y = 'zewn';
{ let y = 'wewn'; }
console.log(y);
// C
console.log(typeof g, typeof h);
function g() {}
var h = function () {};
// D
for (var i = 0; i < 2; i++) {}
for (let j = 0; j < 2; j++) {}
console.log(i, typeof j);Pokaż rozwiązanie
// A: var x w f() to osobna, lokalna zmienna – globalne x bez zmian
var x = 1;
function f() { var x = 2; return x; }
console.log(f(), x); // 2 1
// B: let w bloku przesłania, ale nie zmienia zewnętrznego y
let y = 'zewn';
{ let y = 'wewn'; }
console.log(y); // zewn
// C: deklaracja funkcji g wyniesiona w całości; h to var – wyniesiona tylko nazwa (undefined)
console.log(typeof g, typeof h); // function undefined
function g() {}
var h = function () {};
// D: var i wycieka z pętli (zostaje 2), let j zostało w pętli
for (var i = 0; i < 2; i++) {}
for (let j = 0; j < 2; j++) {}
console.log(i, typeof j); // 2 undefinedFabryka liczników z limitem
Napisz funkcję zrobLicznik(limit), która zwraca obiekt z metodami: zwieksz() (zwiększa o 1, ale nie powyżej limit; zwraca aktualną wartość), zmniejsz() (nie poniżej 0), reset() i wartosc(). Stan licznika ma być niedostępny z zewnątrz (nie może być właściwością obiektu). Utwórz dwa liczniki i pokaż, że są niezależne.
Pokaż rozwiązanie
function zrobLicznik(limit) {
let stan = 0; // widoczny tylko dla metod poniżej
return {
zwieksz() {
if (stan < limit) stan++;
return stan;
},
zmniejsz() {
if (stan > 0) stan--;
return stan;
},
reset() {
stan = 0;
},
wartosc: () => stan,
};
}
const bilety = zrobLicznik(3);
const osoby = zrobLicznik(10);
bilety.zwieksz(); bilety.zwieksz(); bilety.zwieksz();
console.log(bilety.zwieksz()); // 3 – limit zatrzymał
console.log(bilety.zmniejsz()); // 2
console.log(osoby.wartosc()); // 0 – niezależny
console.log(bilety.stan); // undefined – stan nie jest właściwością
bilety.reset();
console.log(bilety.wartosc()); // 0Napraw this
Poniższy obiekt ma dwa błędy związane z this. Metoda podsumuj zwraca NaN, a pokazPozniej po chwili rzuca błąd. Znajdź przyczyny i napraw, nie zmieniając struktury obiektu (ma zostać obiektem z trzema metodami).
const zamowienie = {
pozycje: [{ cena: 20 }, { cena: 35 }],
rabat: 5,
podsumuj() {
let suma = 0;
this.pozycje.forEach(function (p) {
suma += p.cena - this.rabat;
});
return suma;
},
pokazPozniej() {
setTimeout(this.wypisz, 0);
},
wypisz() {
console.log('Razem:', this.podsumuj());
},
};Pokaż rozwiązanie
const zamowienie = {
pozycje: [{ cena: 20 }, { cena: 35 }],
rabat: 5,
podsumuj() {
let suma = 0;
// Błąd 1: zwykła function w forEach ma własne this (globalne), więc this.rabat to undefined → NaN.
// Naprawa: strzałka dziedziczy this z metody podsumuj.
this.pozycje.forEach((p) => {
suma += p.cena - this.rabat;
});
return suma;
},
pokazPozniej() {
// Błąd 2: setTimeout(this.wypisz) wywołuje funkcję „bez kropki” – this nie jest zamowieniem.
// Naprawa: strzałka zachowująca kropkę (albo this.wypisz.bind(this)).
setTimeout(() => this.wypisz(), 0);
},
wypisz() {
console.log('Razem:', this.podsumuj());
},
};
console.log(zamowienie.podsumuj()); // 45
zamowienie.pokazPozniej(); // po chwili: Razem: 45Funkcja raz() i zapamiętywanie wyników
Napisz dwie funkcje wyższego rzędu oparte na domknięciach:
raz(fn)– zwraca funkcję, która wywołafntylko przy pierwszym użyciu, a każde kolejne wywołanie zwraca zapamiętany wynik pierwszego (bez ponownego uruchamianiafn). Przydatne np. do „inicjalizuj tylko raz”.zapamietaj(fn)(memoize) – zwraca funkcję, która dla tego samego argumentu nie liczy drugi raz, tylko zwraca wynik z pamięci (użyjMapjako pamięci podręcznej). Pokaż na „drogiej” funkcji, która wypisujeliczę…przy każdym prawdziwym obliczeniu.
Pokaż rozwiązanie
function raz(fn) {
let wywolana = false;
let wynik;
return (...args) => {
if (!wywolana) {
wywolana = true;
wynik = fn(...args); // pierwszy raz: liczymy i zapamiętujemy
}
return wynik; // potem zawsze to samo
};
}
const polaczZBaza = raz((adres) => {
console.log('Łączę z', adres);
return { polaczono: true };
});
console.log(polaczZBaza('db1'));
console.log(polaczZBaza('db2')); // nie wypisze „Łączę” – zwraca wynik z pierwszego razu
function zapamietaj(fn) {
const pamiec = new Map(); // argument → wynik, żyje w domknięciu
return (arg) => {
if (pamiec.has(arg)) return pamiec.get(arg);
const wynik = fn(arg);
pamiec.set(arg, wynik);
return wynik;
};
}
const kwadrat = zapamietaj((n) => {
console.log('liczę…', n);
return n * n;
});
console.log(kwadrat(12)); // liczę… 12 → 144
console.log(kwadrat(12)); // 144 z pamięci, bez „liczę…”
console.log(kwadrat(5)); // liczę… 5 → 25Test powtórkowy
Test powtórkowy
Zasięg, hoisting, domknięcia i this: 10 pytań.
Co wypisze ten kod?
function f() {
if (true) { var a = 'A'; }
return a;
}
console.log(f());Co wypisze ten kod?
console.log(cena);
let cena = 50;Co wypisze ten kod po chwili?
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 0);
}Co wypisze ten kod?
function zrob() {
let n = 10;
return () => n += 5;
}
const f = zrob();
f();
console.log(f());Które zdanie o funkcjach strzałkowych jest prawdziwe?
Co wypisze ten kod?
const auto = {
marka: 'Fiat',
pokaz() { return this.marka; },
};
const p = auto.pokaz;
console.log(typeof p(), auto.pokaz());Który zapis przekaże metodę gra.ruch do addEventListener tak, by this w niej wskazywało na gra?
Co robi funkcja debounce(fn, 300) zwrócona z fabryki opartej na domknięciu?
Co wypisze ten kod?
let a = 1;
function zmien() { a = 2; }
function nowa() { let a = 3; }
zmien();
nowa();
console.log(a);Dlaczego po wywołaniu o.start() wartość o.n się nie zmienia?
const o = {
n: 0,
start() { setTimeout(function () { this.n++; }, 0); },
};