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 letpodnoszone . 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.

wprowadź opis zdjęcia tutaj

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" }
    ];

Fiddle to zilustrować

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
5

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

6

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

  1. obj zostanie użyte jako wartość this
  2. przekaż argumenty do funkcji
  3. 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



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow