70-480 – Wdrażanie wywołania zwrotnego

70-480

Odbieranie wiadomości z HTML5 WebSocket API; stosowanie kwerendy jQuery w celu realizacji wywołania AJAX; podłączanie zdarzenia; wdrażanie wywołania zwrotnego przy użyciu funkcji anonimowych; obsługa wskaźnika „this”.



Opis zagadnienia zawiera wiele elementów, które na pierwszy rzut oka mogą wydawać się zupełnie różne, ale to co ich łączy, to mechanizm callback. Jest to tak zwana funkcja zwrotna, która rejestrowana jest przez autora kodu do wykonania przez bibliotekę, której używa. Callback wykonywany jest w odpowiednim czasie przez bibliotekę, która nie wie nic o tym co się wydarzy. Wszystko zależy od ciała zarejestrowanej funkcji.

HTML5 WebSocket API

Egzamin 70-480 wymaga od nas znajomości API WebSocket, które pojawiło się wraz z HTML5. Specyfikacja definiuje założenia komunikacji full-duplex, czyli w dwie strony serwer <-> przeglądarka <-> serwer.

Zakładane są cztery typy obsługiwanych callback:

  • onopen – połączenie jest gotowe i można wysyłać wiadomości
  • onmessage – w momencie otrzymania wiadomości z serwera
  • onclose – w momencie zamknięcia połączenia
  • onerror – w momencie wystąpienia błędu

Pierwszym krokiem jest oczywiście utworzenie połączenia:

var connection = new WebSocket('ws://localhost/');

Następnie możemy zdefiniować odpowiednie callback:

connection.onopen = function () {
  connection.send('Ping');
};

connection.onmessage = function (e) {
  console.log('Server: ' + e.data);
};

connection.onclose = function(e) {
  console.log('Closed!');
};

connection.onerror = function (error) {
  console.log('WebSocket Error ' + error);
};

Każdy z callbacków wykona się w ściśle zdefiniowanym momencie. Decyduje o tym API WebSocket oraz komunikacja z serwerem.

Staram się skupiać na podstawach, a zarazem materiale, który w pełni pokrywa założenia egzaminu 70-480. Jeżeli ktoś chce dowiedzieć się nieco więcej na temat HTML5 WebSocket API, to odsyłam do artykułu na html5rocks.com.

jQuery – get, post, ajax

Biblioteki jQuery przedstawiać nikomu nie trzeba. Zyskała ona ogromną popularność społeczności, a to za sprawą wielu ułatwień i funkcji, które zwyczajnie ułatwiają życie. jQuery również świetnie radzi sobie z asynchronicznością oraz obsługą requestów typu AJAX.

Polegają one na wykonywaniu operacji komunikacji z serwerem w tle. Tym samym możemy pobierać dane oraz zmieniać interfejs strony bez przeładowania okna przeglądarki.

Przedstawię niżej trzy najbardziej popularne metody do zapytań typu AJAX, które znajdziemy w bibliotece jQuery.

$.get

Pozwala wykonywać asynchroniczne zapytania typu GET. Sygnatura metody wygląda następująco:

$.get(url [,data] [,success] [,dataType])

, gdzie poszczególne parametry to:

  • url – [String] – url pod jaki ma zostać wysłane zapytanie typu GET
  • data – [PlainObject | String] – obiekt lub string, które wysłane zostają razem z zapytaniem do serwera
  • success – [Function(data, textStatus, jqXHR)] – funkcja i zarazem nasz callback, który wykonany zostanie w momencie poprawnego obsłużenia odpowiedzi. Parametry, które zostaną przekazane do wywołania funkcji to:
    • data – [PlainObject] – zwrócone dane
    • textStatus – [String] – status odpowiedzi
    • jqXHR – [jqXHR] – odpowiednik XMLHTTPRequest
  • dataType – [String] – typ zwracanych danych – xml, json, script, html. Domyślnie ustalany na podstawie zwróconych danych.

Funkcja $.get jest odpowiednikiem (skrótem) dla funkcji $.ajax:

$.ajax({
  url: url,
  data: data,
  success: success,
  dataType: dataType
});

$.post

W zasadzie od funkcji $.get różni się tym, że tym razem wysyła na serwer asynchroniczne żądanie typu POST. Zestaw parametrów jest tutaj identyczny, a odpowiednikiem funkcji $.ajax jest:

$.ajax({
  type: "POST",
  url: url,
  data: data,
  success: success,
  dataType: dataType
});

$.ajax

Funkcja, która zawsze wywoływana jest pod spodem. Jest to najbardziej ogólna funkcja pozwalająca na definiowanie asynchronicznych zapytań. Jej pełna specyfikacja znajduje się na stronie jQuery. Posiada pokaźną liczbę parametrów i opisywanie ich wszystkich tutaj byłoby sporym nadmiarem materiału do przyswojenia.

callback i słowo kluczowe this

W zasadzie o czymś podobnym pisałem już we wpisie o wywoływaniu i obsłudze zdarzeń. Chodzi mianowicie o dostęp do kontekstu this wywołanej funkcji. Spójrzmy na przykład funkcji, która wykonuje callback na sam koniec swojego działania:

function callbackFn() {
  console.log(this);
}

function invoke(x, y, callback) {
  var xy = x + y;
  callback();  
}

invoke(1, 2, callbackFn);

Co będzie wynikiem console.log(this)? Na konsoli otrzymamy obiekt window. Jak przekazać kontekst funkcji wywołującej callback? No to kolejny przykład:

function callbackFn() {
  console.log(this);
}

function invoke(x, y, callback) {
  var xy = x + y;
  callback.call(xy);  
}

invoke(1, 2, callbackFn);

Zmiana nastąpiła w linii 6 poprzez użycie call. Tym razem wyliczona wartość xy została przekazana do funkcji callbackFn i stanowi jej kontekst (this).

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.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/ah4edp7d/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

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.