70-480 – Określanie zakresu obiektów i zmiennych

70-480

Definiowanie okresu istnienia zmiennych; utrzymywanie obiektów poza globalną przestrzenią nazw; stosowanie słowa kluczowego „this” w celu odwołania się do obiektu, który wyzwolił zdarzenie; lokalne i globalne ustalanie zakresu zmiennych.



Kolejnym przystankiem na drodze do certyfikatu 70-480 jest znajomość zakresu zmiennych (scope) w JavaScript oraz słowo kluczowe “this”.

Scope i zakres zmiennych

W JavaScript jest nieco inaczej.. a jak! Mam na myśli sytuację, gdzie w wielu innych językach programowania zasięgiem zmiennej jest po prostu zasięg blokowy (metoda, klasa, pętla). W JavaScript zasięg zmiennej definiowany jest poprzez zakres funkcji.

Dlatego żaden inny zakres blokowy {} nie powoduje utworzenia lokalnej zmiennej. Spójrzmy na na naiwny przykład pętli while:

var invoke = true;

while (invoke) {
    var n = 10;
    invoke = false;    
}

console.log(n);

Wykona się ona dokładnie raz i utworzy w swoim bloku zmienną n i przypisze do niej wartość 10. Jaki będzie wynik na konsoli? Otóż 10! Zmienna zadeklarowana została globalnie, a nie lokalnie.

Dopiero utworzenie zmiennej wewnątrz ciała funkcji stworzy na scope lokalny i tym samym zmienne zadeklarowana zostanie lokalnie:

function invoke() {
    var n = 10;
    console.log('1: ' + n);
}

invoke();
console.log('2: ' + n);

Na konsoli ukarze się nam:

> 1: 10
> Uncaught ReferenceError: n is not defined

Zmienna n nie jest widoczna poza zakresem funkcji!

Ciekawy jest przypadek, którego używam na rozmowach kwalifikacyjnych i męczę nim nieco kandydatów:

var n = 1;
 
(function printSomething() {
    console.log('1: ' + n);
    var n = 2;
    console.log('2: ' + n);
})();

console.log('3: ' + n);

Potraficie powiedzieć, co oraz w jakiej kolejności pojawi się na konsoli przeglądarki bez uruchamiania tego kodu? Zostawiam to Wam, a dodam tylko, że spowodowane jest to poprzez: JavaScript Declarations are Hoisted.

Istnieje również zmodyfikowana wersja tego przypadku, ale tutaj analizę również zostawiam Wam:

var n = 1;
 
(function printSomething() {
    n = 2;
    console.log('1: ' + n);
    var n = 3;
    console.log('2: ' + n);
})();

console.log('3: ' + n);

“this” is this!

Skoro już wiemy czym jest scope i jak jest definiowany, to możemy przejść do definicji słowa kluczowego “this”. Definiujemy this globalne oraz zależne od scope, ale zawsze ważny jest tutaj kontekst i miejsce wywołania.

Kontekst globalny

console.log(this.document === document); // true

console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

Kontekst globalny odwołuje się bezpośrednio do obiektu window. Nasze this w tym wypadku to tak naprawdę referencja do tego obiektu. Tym samym przypisując coś do this w scope globalnym, przypisujemy tą wartość do obiektu window.

Kontekst funkcji

Spójrzmy na dwa przykłady i zastanówmy się nad wynikiem uruchomienia poszczególnych kawałków kodu:

function f1() {
  return this;
}

f1() === window; // true
function f2() {
  'use strict';
  return this;
}

f2() === undefined; // true

this w kontekście funkcji zależy od sposobu i miejsca w jakim dana funkcja zostanie wykonana. Używanie tak zwanego strict mode pozwala nam uniknąć problemu z niewłaściwym użyciem this w takim wypadku.

By this miało w powyższym przykładzie jakieś znacznie, powinniśmy stworzyć nowy obiekt za pomocą konstruktora i słowa new.

function f1() {
  this.echo = 'Hello World!';
  return this;
}

console.log(new f1()); // { echo: "Hello World!" }

Kontekst obiektu

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // 37

W tym wypadku this odnosi się do tego samego scope ograniczonego przez nawiasy klamrowe. Idealnie zilustruje to kolejny przykład:

var o = {
  prop: 37,
  p: {
    prop: 38,
    f: function() {
      return this.prop;
    }
  }
};

console.log(o.p.f()); // logs 38

W wyniku otrzymujemy 38, które zdefiniowane w tym samym scope, co funkcja f.

Następny przykład ilustruje jak ważny jest kontekst wywołania funkcji, o czym była już mowa wyżej.

var o = {
    prop: 37
};

function independent() {
  return this.prop;
}

console.log(independent()); // undefined

window.prop = 36;

console.log(independent()); // 36

o.f = independent;

console.log(o.f()); // 37

Funkcje call i apply

By ściśle zdefiniować kontekst wywołania funkcji, a tym samym this w ciele funkcji możemy wykorzystać funkcje call lub apply. Spójrzmy na przykład:

function add(c, d) {
  return this.a + this.b + c + d;
}

var o = {
    a: 1,
    b: 3
};

console.log(add.call(o, 5, 7)); // 1 + 3 + 5 + 7 = 16

console.log(add.apply(o, [10, 20])); // 1 + 3 + 10 + 20 = 34

Pierwszym argumentem obu funkcji jest obiekt, który w ciele funkcji użyty będzie jako this. Różnica w obu funkcjach polega na przekazywaniu pozostałych parametrów. W przypadku funkcji call parametry podawane są po prostu po przecinku, a funkcja apply przyjmuje jako drugi argument tablicę, w której znajdują się argumenty wywołania funkcji.

Funkcja bind

Kolejnym sposobem na przypisanie kontekstu this do ciała funkcji jest metoda bind (wprowadzona w ECMAScript 5). Jej wywołanie powoduje, że kontekst this przypisywany jest do danej funkcji na stałe. I kolejny przykład obrazujący tą zależność.

function f() {
  return this.a;
}

var g = f.bind({
    a: 'Hello World!'
});

console.log(g()); // Hello World!

var o = {
    a: 37,
    f: f,
    g: g
};

console.log(o.f(), o.g()); // 37, "Hello World!"

Elementy DOM

Ostatni już przykład wykorzystania słowa kluczowego this. Odwołam się w tym miejscu do ciała funkcji wywołanej w ramach zdarzenia DOM.

Funkcja echo posiada thiw postaci referencji do klikniętego boxa. Możemy w ten prosty sposób manipulować obiektem DOM.

Część przykładów została zaczerpnięta z MDN.