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
xzostał już zadeklarowany
To samo dotyczy deklaracji y za pomocą var :
var y = 4;
let y = 7;
TypeError: Identyfikator
yzostał 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
-
objzostanie 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
