Szukaj…
Uwagi
Zakres to kontekst, w którym zmienne żyją i mogą być dostępne przez inny kod w tym samym zakresie. Ponieważ JavaScript może być w dużej mierze wykorzystywany jako funkcjonalny język programowania, znajomość zakresu zmiennych i funkcji jest ważna, ponieważ pomaga zapobiegać błędom i nieoczekiwanym zachowaniom w czasie wykonywania.
Różnica między var i let
(Uwaga: wszystkie przykłady wykorzystujące let
są również ważne dla const
)
var
jest dostępny we wszystkich wersjach JavaScript, podczas gdy let
i const
są częścią ECMAScript 6 i dostępne tylko w niektórych nowszych przeglądarkach .
var
ma zasięg do funkcji zawierającej lub przestrzeni globalnej, w zależności od tego, kiedy zostanie zadeklarowany:
var x = 4; // global scope
function DoThings() {
var x = 7; // function scope
console.log(x);
}
console.log(x); // >> 4
DoThings(); // >> 7
console.log(x); // >> 4
Oznacza to, że „ucieka”, if
instrukcje i wszystkie podobne konstrukcje blokowe:
var x = 4;
if (true) {
var x = 7;
}
console.log(x); // >> 7
for (var i = 0; i < 4; i++) {
var j = 10;
}
console.log(i); // >> 4
console.log(j); // >> 10
Dla porównania, let
jest blokowy:
let x = 4;
if (true) {
let x = 7;
console.log(x); // >> 7
}
console.log(x); // >> 4
for (let i = 0; i < 4; i++) {
let j = 10;
}
console.log(i); // >> "ReferenceError: i is not defined"
console.log(j); // >> "ReferenceError: j is not defined"
Zauważ, że i
i j
są zadeklarowane tylko w pętli for
i dlatego nie są zadeklarowane poza nią.
Istnieje kilka innych istotnych różnic:
Deklaracja zmiennych globalnych
W górnym zakresie (poza funkcjami i blokami) deklaracje var
umieszczają element w obiekcie globalnym. nie let
:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Ponowna deklaracja
Dwukrotne zadeklarowanie zmiennej przy użyciu var
nie powoduje błędu (mimo że jest to równoważne z deklarowaniem jej raz):
var x = 4;
var x = 7;
W przypadku let
powoduje to błąd:
let x = 4;
let x = 7;
TypeError: Identyfikator
x
został już zadeklarowany
To samo dotyczy deklaracji y
za pomocą var
:
var y = 4;
let y = 7;
TypeError: Identyfikator
y
został już zadeklarowany
Jednak zmienne zadeklarowane za pomocą let mogą być ponownie użyte (nie ponownie zadeklarowane) w zagnieżdżonym bloku
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
W obrębie bloku można uzyskać dostęp do zewnętrznego i
, ale jeśli wewnątrz bloku ma deklarację let
dla i
, nie można uzyskać dostępu do zewnętrznego i
wyrzuci błąd ReferenceError
jeśli zostanie użyty przed zgłoszeniem drugiego.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i nie jest zdefiniowany
Podnoszenie
Zmienne deklarowane zarówno za pomocą var
i let
są podnoszone . Różnica polega na tym, że do zmiennej zadeklarowanej za pomocą var
można odwoływać się przed jej własnym przypisaniem, ponieważ jest ona automatycznie przypisywana (z undefined
wartością), ale let
może - wymaga konkretnie deklaracji zmiennej przed wywołaniem:
console.log(x); // >> undefined
console.log(y); // >> "ReferenceError: `y` is not defined"
//OR >> "ReferenceError: can't access lexical declaration `y` before initialization"
var x = 4;
let y = 7;
Obszar między początkiem bloku a deklaracją let
lub const
jest znany jako Strefa martwa w czasie , a wszelkie odwołania do zmiennej w tym obszarze spowodują błąd ReferenceError
. Dzieje się tak, nawet jeśli zmienna jest przypisana przed zadeklarowaniem :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
W trybie bez ścisłego przypisywanie wartości zmiennej bez deklaracji automatycznie deklaruje zmienną w zakresie globalnym . W tym przypadku zamiast y
są automatycznie zadeklarowana w zakresie globalnym, let
rezerwy zmiennej Nazwa ( y
) i nie dopuszcza żadnego dostępu lub przypisanie do niej przed linią, gdzie jest zadeklarowanym / zainicjowany.
Domknięcia
Kiedy funkcja jest deklarowana, zmienne w kontekście jej deklaracji są przechwytywane w jej zakresie. Na przykład w poniższym kodzie zmienna x
jest powiązana z wartością w zakresie zewnętrznym, a następnie odniesienie do x
jest przechwytywane w kontekście bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Próbka wyjściowa:
4
Ta koncepcja „przechwytywania” zakresu jest interesująca, ponieważ możemy używać i modyfikować zmienne z zakresu zewnętrznego nawet po wyjściu z zakresu zewnętrznego. Rozważ na przykład:
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
Próbka wyjściowa:
4
W powyższym przykładzie, po wywołaniu foo
, jego kontekst jest przechwytywany na bar
funkcji. Nawet po powrocie bar
może nadal uzyskiwać dostęp do zmiennej x
i modyfikować ją. Mówi się, że funkcja foo
, której kontekst jest ujęty w innej funkcji, jest zakończeniem .
Prywatne dane
To pozwala nam robić kilka interesujących rzeczy, takich jak definiowanie zmiennych „prywatnych”, które są widoczne tylko dla określonej funkcji lub zestawu funkcji. Przemyślany (ale popularny) przykład:
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
Przykładowe dane wyjściowe:
1 0
Po makeCounter()
jest migawka kontekstu tej funkcji. Cały kod w makeCounter()
użyje tej migawki podczas wykonywania. Dwa wywołania metody makeCounter()
utworzą zatem dwie różne migawki z własną kopią counter
.
Wywołane natychmiast wyrażenia funkcyjne (IIFE)
Zamknięcia są również stosowane w celu zapobiegania globalnym zanieczyszczeniom przestrzeni nazw, często poprzez użycie natychmiast wywoływanych wyrażeń funkcyjnych.
Wywołane natychmiast wyrażenia funkcyjne (lub, być może bardziej intuicyjnie, samoczynnie wykonujące się funkcje anonimowe ) są zasadniczo zamknięciami, które są wywoływane zaraz po deklaracji. Ogólną ideą IIFE jest wywołanie efektu ubocznego stworzenia osobnego kontekstu, który jest dostępny tylko dla kodu w IIFE.
Załóżmy, że chcemy móc odwoływać się do jQuery
pomocą $
. Rozważ naiwną metodę bez użycia IIFE:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
W poniższym przykładzie użyto IIFE, aby upewnić się, że $
jest związany z jQuery
tylko w kontekście utworzonym przez zamknięcie:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
Zobacz kanoniczną odpowiedź na Stackoverflow, aby uzyskać więcej informacji na temat zamknięć.
Podnoszenie
Co to jest podnoszenie?
Podnoszenie jest mechanizmem, który przenosi wszystkie deklaracje zmiennych i funkcji na szczyt zakresu. Jednak przypisania zmiennych nadal zachodzą tam, gdzie były pierwotnie.
Na przykład rozważ następujący kod:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
Powyższy kod jest taki sam jak:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Zauważ, że z powodu podniesienia powyższy undefined
nie jest tym samym, co not defined
wynikający z uruchomienia:
console.log(foo); // → foo is not defined
Podobna zasada dotyczy funkcji. Gdy funkcje są przypisane do zmiennej (tj. Wyrażenia funkcyjnego ), deklaracja zmiennej jest podnoszona, podczas gdy przypisanie pozostaje w tym samym miejscu. Poniższe dwa fragmenty kodu są równoważne.
console.log(foo(2, 3)); // → foo is not a function
var foo = function(a, b) {
return a * b;
}
var foo;
console.log(foo(2, 3)); // → foo is not a function
foo = function(a, b) {
return a * b;
}
Podczas deklarowania instrukcji funkcji występuje inny scenariusz. W przeciwieństwie do instrukcji funkcji, deklaracje funkcji są podnoszone na szczyt zakresu. Rozważ następujący kod:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
Powyższy kod jest taki sam, jak następny fragment kodu z powodu podnoszenia:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Oto kilka przykładów tego, co jest, a co nie jest podnoszeniem:
// Valid code:
foo();
function foo() {}
// Invalid code:
bar(); // → TypeError: bar is not a function
var bar = function () {};
// Valid code:
foo();
function foo() {
bar();
}
function bar() {}
// Invalid code:
foo();
function foo() {
bar(); // → TypeError: bar is not a function
}
var bar = function () {};
// (E) valid:
function foo() {
bar();
}
var bar = function(){};
foo();
Ograniczenia podnoszenia
Inicjalizacji zmiennej nie można podnieść ani w prostych skryptach JavaScript Podnośniki nie inicjować.
Na przykład: Poniższe skrypty dają różne wyniki.
var x = 2;
var y = 4;
alert(x + y);
To da ci wynik 6. Ale to ...
var x = 2;
alert(x + y);
var y = 4;
To da ci wyjście NaN. Ponieważ inicjalizujemy wartość y, podnoszenie JavaScript nie ma miejsca, więc wartość y będzie niezdefiniowana. JavaScript uzna, że y nie jest jeszcze zadeklarowane.
Drugi przykład jest taki sam jak poniżej.
var x = 2;
var y;
alert(x + y);
y = 4;
To da ci wyjście NaN.
Używanie pętli let in zamiast var (przykład obsługi kliknięć)
Powiedzmy, że musimy dodać przycisk dla każdego elementu tablicy loadedData
(na przykład każdy przycisk powinien być suwakiem pokazującym dane; dla uproszczenia po prostu powiadomimy wiadomość). Można spróbować czegoś takiego:
for(var i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Ale zamiast ostrzegać, każdy przycisk spowoduje
TypeError :loadData [i] jest niezdefiniowany
błąd. Wynika to z tego, że zasięg i
jest zasięgiem globalnym (lub zasięgiem funkcji), a po pętli i == 3
. Potrzebujemy nie „pamiętać stanu i
”. Można to zrobić za pomocą let
:
for(let i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Przykład loadedData
do przetestowania za pomocą tego kodu:
var loadedData = [
{ label:"apple", content:"green and round" },
{ label:"blackberry", content:"small black or blue" },
{ label:"pineapple", content:"weird stuff.. difficult to explain the shape" }
];
Wywołanie metody
Wywołanie funkcji jako metody obiektu, wartością this
będzie ten obiekt.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
Możemy teraz wywołać print jako metodę obj. this
będzie obj
obj.print();
Spowoduje to zatem zalogowanie:
bla
Anonimowe wywołanie
Wywołanie funkcji jako funkcji anonimowej spowoduje, this
będzie to obiekt globalny ( self
w przeglądarce).
function func() {
return this;
}
func() === window; // true
W trybie ścisłym ECMAScript 5'S , this
będzie undefined
, jeśli funkcja jest wywoływana anonimowo.
(function () {
"use strict";
func();
}())
To wyjdzie
undefined
Wywołanie konstruktora
Gdy funkcja jest wywoływana jako konstruktor z new
hasła this
przyjmuje wartość obiektu w trakcie budowy
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
To się zaloguje
{name: „Foo”}
Wywołanie funkcji strzałki
Podczas korzystania z funkcji strzałek this
przyjmuje wartość od kontekstu wykonania otaczająca jest this
(czyli this
w funkcji strzałek ma zakres leksykalny, a nie zwykły zakres dynamiczny). W kodzie globalnym (kod, który nie należy do żadnej funkcji) byłby to obiekt globalny. I tak jest, nawet jeśli wywołasz funkcję zadeklarowaną za pomocą notacji strzałkowej z dowolnej z innych opisanych tutaj metod.
var globalThis = this; //"window" in a browser, or "global" in Node.js
var foo = (() => this);
console.log(foo() === globalThis); //true
var obj = { name: "Foo" };
console.log(foo.call(obj) === globalThis); //true
Zobacz, jak this
dziedziczy kontekst niż odnoszące się do przedmiotu metoda została wezwana.
var globalThis = this;
var obj = {
withoutArrow: function() {
return this;
},
withArrow: () => this
};
console.log(obj.withoutArrow() === obj); //true
console.log(obj.withArrow() === globalThis); //true
var fn = obj.withoutArrow; //no longer calling withoutArrow as a method
var fn2 = obj.withArrow;
console.log(fn() === globalThis); //true
console.log(fn2() === globalThis); //true
Zastosuj i wywołaj składnię i wywołanie.
apply
i call
metody w każdej funkcji pozwalają na dostarczenie wartość niestandardową dla this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Możesz zauważyć, że składnia obu wywołań użytych powyżej jest taka sama. tzn. Podpis wygląda podobnie.
Ale jest niewielka różnica w ich użyciu, ponieważ mamy do czynienia z funkcjami i zmieniamy ich zakresy, nadal musimy zachować oryginalne argumenty przekazane do funkcji. Zarówno apply
jak i obsługa call
przekazują argumenty do funkcji docelowej w następujący sposób:
function speak() {
var sentences = Array.prototype.slice.call(arguments);
console.log(this.name+": "+sentences);
}
var person = { name: "Sunny" };
speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups"
speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
Zauważ, że apply
pozwala ci przekazać Array
lub obiekt arguments
(podobny do tablicy) jako listę argumentów, podczas gdy call
wymaga przekazania każdego argumentu osobno.
Te dwie metody dają ci swobodę, by być tak fantazyjnym, jak chcesz, jak implementacja złej wersji natywnego bind
ECMAScript w celu utworzenia funkcji, która zawsze będzie wywoływana jako metoda obiektu z funkcji oryginalnej.
function bind (func, obj) {
return function () {
return func.apply(obj, Array.prototype.slice.call(arguments, 1));
}
}
var obj = { name: "Foo" };
function print() {
console.log(this.name);
}
printObj = bind(print, obj);
printObj();
To się zaloguje
"Bla"
Wiele się dzieje w funkcji bind
-
obj
zostanie użyte jako wartośćthis
- przekaż argumenty do funkcji
- a następnie zwróć wartość
Związane wywołanie
Metoda bind
każdej funkcji umożliwia tworzenie nowej wersji tej funkcji z kontekstem ściśle powiązanym z określonym obiektem. Szczególnie przydatne jest wymuszenie wywołania funkcji jako metody obiektu.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Spowoduje to zalogowanie:
bar