background image

Na czym polega chaining w JavaScript? 

Implementacja kalkulatora oraz własnej biblioteki DOM, które działałyby na zasadzie chainingu, 

czyli uruchamianiu metod po metodzie na danym obiekcie, po kropce, podobnie jak w jQuery. 

W przypadku kalkulatora mogłoby to wyglądać np. tak: 

czysty tekst

 

1.

 

calculator.add(2).multiply(3).subtract(3).getResult(); // 3

 

Działania wykonywane byłyby od lewej, nie zwracalibyśmy również uwagi na kolejność (a więc mnożenie 

byłoby równe dodawaniu). Zatem najpierw dodajemy do zera dwójkę, potem mnożymy to przez trzy, a na 

końcu odejmujemy trójkę. Wynikiem jest… trzy. 

W wypadku biblioteczki DOMowej, przykładowe jej użycie mogłoby wyglądać następująco: 

czysty tekst

 

1.

 

$("div > p").css("margin-left", "20px").click(function() {

 

2.

 

console.log("klik!");

 

3.

 

});

 

Skupmy się jednak na kalkulatorze. 

Przed przystąpieniem do implementacji zazwyczaj pojawia się wiele wątpliwości. Po pierwsze należy 

zauważyć, analizując nasz przykład, że najpierw uruchamia się funkcja 

add

, potem

multiply

następnie 

subtract

, a na końcu 

getResult

. Wszystko dzieje się po kropce. 

Warto więc przypomnieć sobie, co oznacza konstrukcja 

foo.bar()

, czyli dwie części kodu oddzielone 

kropką. Otóż używając takiego kawałka kodu spodziewamy się, że bar jest funkcją (w końcu mamy do 

czynienia z nawiasami, więc zapewne chcemy uruchomić jakąś funkcję), a fooobiektem. Otóż tylko wtedy 

taka konstrukcja ma sens – po prawej stronie kropki powinna być funkcja, a po lewej obiekt zawierający 

taką funkcję. Kod mógłby wyglądać więc tak: 

czysty tekst

 

1.

 

var foo = { bar: function() { return "test"; } };

 

2.

 

foo.bar(); // test

 

W naszym z kolei przypadku mamy kilka uruchomień funkcji po kropce. Zgadujemy więc, że to co będzie 

z lewej musi zwracać jakiś obiekt, zawierający metodę z prawej. A więc po 

odpaleniu

calculator.add(2)

 spodziewamy się, że 

calculator

 będzie obiektem zawierającym 

metodę

add

. Następnie po uruchomieniu 

calculator.add(2).multiply(3)

 chcemy, by to, co 

zwróci wywołanie 

calculator.add(2)

, zawierało metodę 

multiply

, ponieważ JS napotkał kropkę, 

co znaczy, że z lewej ma znaleźć się obiekt, a z prawej funkcja bądź inna własność z tego obiektu. I tak 

dalej. A więc każde wywołanie funkcji kończące się kropką ma zwrócić jakiś obiekt, by kod mógł 

zadziałać. 

background image

Rozpocznijmy implementację: 

czysty tekst

 

1.

 

var calculator = {};

 

Tworzymy obiekt calculator, od którego wszystko się zacznie. Taki obiekt ma metodę add, w końcu nasz 

przykład wygląda tak: 

czysty tekst

 

1.

 

calculator.add(2).multiply(3).subtract(3).getResult(); // 3

 

Piszemy! 

czysty tekst

 

1.

 

var calculator = {

 

2.

 

add: function(n) {

 

3.

 

}

 

4.

 

};

 

Nic prostszego, prawda? 

Używamy tego tak: 

czysty tekst

 

1.

 

calculator.add(2);

 

Ale my chcemy mieć więcej możliwości, chcemy teraz pomnożyć wszystko przez trzy: 

czysty tekst

 

1.

 

calculator.add(2).multiply(3)

 

Wykorzystując obecny, skromny kod, dostajemy błąd. Nic dziwnego – to, co zwraca metoda 

add

, nie 

zawiera funkcji 

multiply

. Naprawmy nasz błąd: 

czysty tekst

 

1.

 

var calculator = {

 

2.

 

add: function(n) {

 

3.

 

return {

 

4.

 

multiply: function(n2) {

 

5.

 

}

 

6.

 

};

 

7.

 

}

 

8.

 

};

 

Wszystko działa, tylko co, gdyby po add nie było multiply, a na przykład odejmowanie lub kolejne 

dodawanie? Pisalibyśmy takie podobiekty w nieskończoność. Zdefiniujmy więc najpierw proste API, które 

będzie implementował kalkulator. 

czysty tekst

 

1.

 

var calculator = {

 

background image

2.

 

add: function() {},

 

3.

 

subtract: function() {},

 

4.

 

multiply: function() {},

 

5.

 

divide: function() {}

 

6.

 

};

 

Wszystko fajnie. Brakuje tylko miejsca na przechowywanie wyniku: 

czysty tekst

 

1.

 

var calculator = {

 

2.

 

add: function() {},

 

3.

 

subtract: function() {},

 

4.

 

multiply: function() {},

 

5.

 

divide: function() {},

 

6.

 

result: 0

 

7.

 

};

 

Aktualny rezultat naszych operacji składowany jest w 

calculator.result

. Początkowa jego wartość 

to 0. Uzupełnijmy teraz metody: 

czysty tekst

 

1.

 

var calculator = {

 

2.

 

add: function(n) {

 

3.

 

if (typeof n === "number") {

 

4.

 

calculator.result += n;

 

5.

 

}

 

6.

 

},

 

7.

 

subtract: function(n) {

 

8.

 

if (typeof n === "number") {

 

9.

 

calculator.result -= n;

 

10.

 

}

 

11.

 

},

 

12.

 

multiply: function(n) {

 

13.

 

if (typeof n === "number") {

 

14.

 

calculator.result *= n;

 

15.

 

}

 

16.

 

},

 

17.

 

divide: function(n) {

 

18.

 

if (typeof n === "number" && n !== 0) {

 

19.

 

calculator.result /= n;

 

20.

 

}

 

21.

 

},

 

22.

 

getResult: function() { return calculator.result; },

 

23.

 

result: 0

 

24.

 

};

 

W każdej z nich sprawdzamy, czy podana liczba jest rzeczywiście typy liczbowego (

typeof number

). 

Dodatkowo w dzieleniu 

divide

 nie chcemy dzielić przez zero. 

background image

Wszystko pięknie, tylko nadal nie działa nam zapis metod po kropce. Jak wiemy, aby uruchomić metodę 

poprzedzoną kropką, musi być ona składnikiem obiektu z lewej strony. A więc każda z naszych funkcji 

musi zwracać obiekt z tymi czteroma metodami. 

czysty tekst

 

1.

 

var calculator = {

 

2.

 

add: function(n) {

 

3.

 

if (typeof n === "number") {

 

4.

 

calculator.result += n;

 

5.

 

}

 

6.

 

return calculator;

 

7.

 

},

 

8.

 

subtract: function(n) {

 

9.

 

if (typeof n === "number") {

 

10.

 

calculator.result -= n;

 

11.

 

}

 

12.

 

return calculator;

 

13.

 

},

 

14.

 

multiply: function(n) {

 

15.

 

if (typeof n === "number") {

 

16.

 

calculator.result *= n;

 

17.

 

}

 

18.

 

return calculator;

 

19.

 

},

 

20.

 

divide: function(n) {

 

21.

 

if (typeof n === "number" && n !== 0) {

 

22.

 

calculator.result /= n;

 

23.

 

}

 

24.

 

return calculator;

 

25.

 

},

 

26.

 

getResult: function() { return calculator.result; },

 

27.

 

result: 0

 

28.

 

};

 

Jak łatwo zauważyć, każda z nich zwraca obiekt, do którego należy – a więc możemy być pewni, że będzie 

zawierał on wszystkie z interesujących nas funkcji, w końcu zwracamy cały kalkulator. 

Pozostaje jednak jedna drażniąca kwestia. Otóż możemy łatwo ingerować w wynik – na przykład pisząc 

tak: 

czysty tekst

 

1.

 

calculator.add(2).add(3);

 

2.

 

calculator.result = 10;

 

3.

 

calculator.getResult(); // 10

 

Byłoby bardzo miło ukryć dostęp do zmiennej przechowującej tymczasowy wynik. Możemy to zrobić 

korzystając z closures i wywołania funkcji anonimowej. 

background image

czysty tekst

 

1.

 

var calculator = (function() {

 

2.

 

var result = 0;

 

3.

 

var calculator = {

 

4.

 

add: function(n) {

 

5.

 

if (typeof n === "number") {

 

6.

 

result += n;

 

7.

 

}

 

8.

 

return calculator;

 

9.

 

},

 

10.

 

subtract: function(n) {

 

11.

 

if (typeof n === "number") {

 

12.

 

result -= n;

 

13.

 

}

 

14.

 

return calculator;

 

15.

 

},

 

16.

 

multiply: function(n) {

 

17.

 

if (typeof n === "number") {

 

18.

 

result *= n;

 

19.

 

}

 

20.

 

return calculator;

 

21.

 

},

 

22.

 

divide: function(n) {

 

23.

 

if (typeof n === "number" && n !== 0) {

 

24.

 

result /= n;

 

25.

 

}

 

26.

 

return calculator;

 

27.

 

},

 

28.

 

getResult: function() { return result; }

 

29.

 

};

 

30.

 

 

31.

 

return calculator;

 

32.

 

})();

 

Z obiektu 

calculator

 wydzieliliśmy 

result

 do oddzielnej zmiennej o tej samej nazwie. Jest ona 

dostępna w scope anonimowej funkcji, a więc tylko wewnątrz: 

czysty tekst

 

1.

 

var calculator = (function() {

 

2.

 

// o, tutaj

 

3.

 

})();

 

Wszystkie inne funkcje zdefiniowane wewnątrz niej będą miały do niej dostęp – tak działa mechanizm 

closures. 

Voila! Podany przykład można by rozszerzyć o 

.prototype

 i tak dalej, co mogłoby wspierać 

dopisywanie pluginów. Można też dodać obsługę błędów. To jednak temat na następny artykuł. 

background image

Równie dobrze można skorzystać z this

czysty tekst

 

1.

 

var calculator = (function() {

 

2.

 

var result = 0;

 

3.

 

var calculator = {

 

4.

 

add: function(n) {

 

5.

 

if (typeof n === "number") {

 

6.

 

result += n;

 

7.

 

}

 

8.

 

return this;

 

9.

 

},

 

10.

 

subtract: function(n) {

 

11.

 

if (typeof n === "number") {

 

12.

 

result -= n;

 

13.

 

}

 

14.

 

return this;

 

15.

 

},

 

16.

 

multiply: function(n) {

 

17.

 

if (typeof n === "number") {

 

18.

 

result *= n;

 

19.

 

}

 

20.

 

return this;

 

21.

 

},

 

22.

 

divide: function(n) {

 

23.

 

if (typeof n === "number" && n !== 0) {

 

24.

 

result /= n;

 

25.

 

}

 

26.

 

return this;

 

27.

 

},

 

28.

 

getResult: function() { return result; }

 

29.

 

};

 

30.

 

 

31.

 

return calculator;

 

32.

 

})();

 

Ryzykujemy jednak w takim przypadku: 

czysty tekst

 

1.

 

var dodaj = calculator.add;

 

2.

 

dodaj(2).add(2).add(2).getResult();