Pokazywanie postów oznaczonych etykietą JavaScript. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą JavaScript. Pokaż wszystkie posty

wtorek, 2 lutego 2021

undefined vs null

Ostatnio natknęłam się na artykuł stwierdzający, że typeof null zwraca "object" z powodu błędu w JavaScript. W ciągu ostatnich 20 lat pisania w różnych językach widziałam kilka błędów (np różna kolejność tych samych parametrów w funkcjach explode i implode w starym PHP) ale ta logika jest w JavaScript od początku i jakoś nie chce mi się wierzyć, że od ponad 20 lat nikt tego nie poprawił.

Tak więc wzięłam się (po raz któryś z kolei) za czytanie dokumentacji na mdn. A tam stoi: The value null represents the intentional absence of any object value. It is one of JavaScript's primitive values and is treated as falsy for boolean operations..

Co to oznacza w praktyce? A oznacza to, że jeśli nasz obiekt (lub cokolwiek co po nim dziedziczy) nie ma wartości i jest to zamierzone (np takiego użytkownika nie ma) to używamy null. A co jeśli pracujemy ze stringiem? Wtedy zazwyczaj używamy "", żeby oznaczyć, że nie ma wartości, dla liczb dajemy 0 a dla wartości logicznych false.

Ten null dla obiektów jest bardzo przydatny. Zapis {} zwraca istniejącą wartość (jest obiekt, choć pusty), tak samo z []. Więc żeby zachować konwencję używania !zmienna obiektom ustawiamy null, gdy celowo nie mają wartości.

Z undefined historia jest trochę inna. To jest domyślna wartość używana przez JS gdy wartość czegoś nie została ustawiona (jeszcze). Warto utrzymać to rozróżnienie dla zwiększenia czytelności kodu.

typeof {} // "object"
typeof [] // "object"
typeof (() => {}) // "function"

({}) instanceof Object // true
new Object() instanceof Object // true
([]) instanceof Object // true
new Array() instanceof Object // true
(() => {}) instanceof Object // true

!!null === false // true
!null === true // true
!!"" === false // true
!"" === true // true
!!0 === false // true
!0 === true // true
Dla zainteresowanych dodatkowy artykuł undefined vs. null revisited

poniedziałek, 19 października 2020

react hooks - useState

Hooki w react pojawiły się pod koniec 2018 roku. Umożliwjają one wygodne pisanie komponentów funkcyjnych, dzielenie odpowiedzialności w kodzie oraz tworzenie małych plików - łatwych w testowaniu, utrzymaniu i wygodnych, gdy trzeba znaleźć błąd.

Najbardziej podstawowym hookiem jest useState(). Umożliwia on zarządzanie wartością stanu pomiędzy kolejnymi wywołaniami funkcji komponentu.

useState() zwraca tablicę z dwiema wartościami: stanem i funkcją do jego aktualizacji. Zgodnie z konwencją nazwa funkcji do aktualizacji powinna zaczynać się od czasownika set, czyli ustawiać. Jeśli zmienną stanu nazwiemy user to funcja powinna nazywać się setUser.
const [state, setState] = useState();

useState() przyjmuje jako parametr początkową wartość stanu
const [state, setState] = useState(4);
console.log(state); // 4

Funkcja setState przyjmuje jako parametr nową wartość stanu lub callback
const [state, setState] = useState(4);
console.log(state); // 4
setState(24);
console.log(state); // 24

Callback przyjmuje jako parametr aktualna wartość lub referencję stanu:
const [state, setState] = useState(4);
console.log(state); // 4
setState(state => state + 1);
console.log(state); // 5

Po co setState przyjmuje callback jako parametr? Po to, żeby w każdym przypadku, gdy nasz kod nie ma dostępu do aktualnego state móc jednak z niego korzystać. Dość toporny przykład pokazujący jak to działa jest z wykorzystaniem setInterval
.
import React from "react";

export default function App() {
  const [state, setState] = React.useState(0);

  function updateState() {
    setState((state || 0) + 1);
  }

  React.useEffect(() => {
    const interval = setInterval(updateState, 3000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="App">
      {console.log("rerender", state)}
      <h3>{state}</h3>
    </div>
  );
}

Powyższy przykład pokazuje ustawianie nowej wartości stanu co 3 sekundy. Niestety, stan ustawia się na 1, cały czas co 3 sekundy, ponieważ funkcja updateState jest utworzona, gdy state wynosi 0. I tak zostaje przekazana do setInterval. Co 3 sekundy setInterval zwieksza state równy 0 o 1...
zobacz na Sandbox

Sposobem na naprawienie (poza zabawą scope i clojure) jest użycie callbacku jako parametru w setState, wtedy cała funkcja bedzie wygladać tak:
zobacz na Sandbox

  function updateState() {
    setState(state => (state || 0) + 1);
  }

state przekazywany do callbacku jest tym aktualnym, wiec co 3 sekundy rośnie jego wartośc o 1.

react i referencja

Cykl życia komponentu reacta obejmuje update komponentu, gdy dane na których pracuje komp[onent się zmienią. Dotyczy to zarówno danych przychodzacych z zewnątrz (props) jak i tych tworzonych i uaktualnianych wewnątrz komponentu (state). Jeśli chodzi o dane prymitywne (liczby, ciągi tekstowe, typy logiczne, null, undefined i Symbol) to sprawa jest dość prosta. Wiadomo, że:
console.log(1 === 1); // true
console.log('a' === 'a'); // true
console.log(true === true); // true
console.log(undefined === undefined); // true
console.log(Symbol('a').toString() === Symbol('a').toString()); // true

Czyli jeśli wartość props lub state zmieni się z 1 na 2 to komponent wie, że ma się zrerenderować. Proste.

A jednak, nie do końca. Wszystko co nie jest prymitywem w JS (jest to pewne uproszczenie, będzie osobny wpis na ten temat) jest obiektem. A obiekty nie są przekazywane przez wartość a przez referencję. Innymi słowy JS umieszcza utworzony obiekt gdzieś w pamięci i posługuje się adresem tego obiektu a nie nim samym. Każde utworzenie obiektu tworzy nową referencję.
const a = { id: 1 };
const b = { id: 1 };

console.log(a === a); // true
console.log(a === b); // false
console.log(Object.is(a, a)); // true console.log(Object.is(a, b)); // false

Czyli nawet dwa identyczne obiekty mają dwie różne referencje, więc nie da się ich łatwo porównać. React do porównywania obiektów używa Object.is().

Dlatego, gdy zmieniamy jakąś wartość w obiekcie, powinniśmy stworzyć nową referencję, żeby react zauważył zmianę, na przykład tak:
const [formData, setFormData] = useState({});

function handleChange(evt) {
  const id = evt.target.id;
  const value = evt.target.value;

  const formCopy = { ...formData };
  formCopy[id] = value;
  setFormData(formCopy);
}

Alternatywnym sposobem może być używanie biblioteki jak immutable.js aby każda zmiana w obiekcie zwracała nową referencję.

sobota, 30 maja 2020

Function vs arrow function

Arrow function came in 2015 together with new ECMAScript 5. It was a revolution! No more var that = this; to be able to use parent context in a callback.

I really enjoyed this time and like others was using arrow functions everywhere. But 5 years later I started to hate arrow functions used as normal functions.

Code with one-liner functions and other variables:
const getData = dataKey => get(state, dataKey);
const makePlainObj = () => ({});
const doSth = ({ a, b }) => {console.log(b)};
const abc = { get: someFn };


Code with normal functions:
function getData(dataKey) {
  return get(state, dataKey);
}

function makePlainObj() {
  return {};
}

function doSth({ a, b }) {
  console.log(b);
}

const abc = { get: someFn };

Yes, second code is longer but at the same time much more readable. On first look it is visible what is a variable and what is a function. Also functions are hoisted when created as variables arrow functions are not.

niedziela, 30 czerwca 2019

JavaScript built-in functionality - be careful and read docs

JavaScript has a lot of very useful built-in functions, like isNaN(), parseInt(), isFinite() and so on. Those functions are simple and single purpose pieces of code used very often.

The isNaN() function takes only one argument - the value which should be checked and returns a boolean - true or false. The only purpose is to check if the value is NaN - not a number value which could be returned by functions parsing numbers or by arithmetical operators. There is no other way to check if the value is NaN, so this function is very important.

There is also other 'is' function - isFinite() which determines whether the passed value is a finite number. Also very important as some of the arithmetic operators can return Infinity as a result of the operation and then isNaN() returns false. It also takes only one argument - the value to be checked and returns boolean.

Another useful single purpose function is parseInt(). It checks provided string if it could be the number and returns the number or NaN.

As there are more than one numeral systems, this function accepts the radix as a second argument. There is NO DEFAULT value for radix. If radix is falsy then the value itself is taken in consideration. Because we can use Number(string) to determine whether the string is a number or not parseInt() works in a slighty different way. In general it checks if the string starts with the number and returns this number till first non-numerical value.

parseInt('1', 10); // 1
parseInt('1abc', 10); // 1
parseInt('1f0', 10); // 1

parseInt('1f0', 16); // 496

The parseFloat() built-in function works the same way, except it accepts only one argument - the value, as floats are decimals, so the numeral system is already known.

Both parseX functions are documented that the value should be a string but any value type works properly.


Array.prototype.map is a built-in Array method. It takes 2 arguments: a callback function and this. Returns the brand new array with the same number of elements as the array it was called upon with values produced by the callback function. Simple and beautiful.

It is very important to remember that callback function also accepts arguments. All of them are optional, as the logic for creating values for the new array could not depend on existing ones, but still the callback accepts arguments. The first one is a current value. For [1,7,11] it will be 1, then 7 and then 11. The second argument is index of the value - for 1 it will be 0, for 7 - 1 and for 11 - 2. The third argument is the array map was called upon.

[1,7,11].map(() => null); // [null, null, null]
[1,7,11].map((value) => value * 2); // [2, 14, 22]
[1,7,11].map((value, index) => value * index); // [0, 7, 22]

So what will happen if we want to use parseFloat() as a callback for map?

['1','7','11'].map(parseFloat); // [1, 7, 11]
['1.1','7.9876','11.444442'].map(parseFloat); // [1.1, 7.9876, 11.444442]

Works like a charm!

So why parseInt() doesn't work as expected?

['1','7','11'].map(parseInt); // [1, NaN, 3]

What is a problem here? Let's test the values:

parseInt('1'); // 1
parseInt('7'); // 7
parseInt('11'); // 11

It should work...

Everytime if something doesn't work as expected checking docs can save hours of tests. The second argument in parseInt() is radix and what passes map() as a second parameter to the callback? An index of the value...

So testing the values should look like that:

parseInt('1', 0); // 1

For index 0 value is '1'. The radix is falsy, so the value itself is taken in consideration. If value doesn't start with 0 decimal system is used.

parseInt('7', 1); // NaN

For index 1 value is '7'. Proper radix value is between 2 and 36. Any value parsed with 1 as a radix is NaN.

parseInt('11', 2); // 3

For index 2 value is '11'. Radix 2 means binary: 2^0 + 2^1 = 3.

So everything works just as it should. To make the callback work as expected a small change is required:

['1','7','11'].map((value) => parseInt(value, 10)); // [1, 7, 11]

It is not the newbie mistake. Such problems happen for seasoned developers, as they are not machines and don't store not used information like the order or all arguments for built-in functions. Using documentation during development process doesn't mean developer has no experience or knowledge. Using docs is a very good habit and the best possible practice which saves hours of bug fixing.

All recruitment processes which check the knowledge of docs are leading to such problems. General understanding of the language, what happens under the hood and problem solving skills and strategies (for example how to debug such bug) are more important than memorizing the documentation.

Good to read:
operacje arytmetyczne w JS [polish]
parseInt() - JavaScript | MDN
Array.prototype.map() - JavaScript | MDN

poniedziałek, 4 marca 2019

OOP - polimorfizm

Na wielu rozmowach kwalifikacyjnych, poza pytaniami o sam JavaScript padają pytania z ogólnej wiedzy o programowaniu. Czasami o best practices, czasami twarda wiedza, którą, jakby nie było trzeba znać, choć niektóre jej aspekty w JS są zupełnie nieużyteczne.

W zależności od doświadczenia osób pytających, można natrafić na przykład o pytanie czym jest polimorfizm i jak się go implementuje w JS. Polimorfizm łączy się z nadpisywaniem (override) metod obiektu i ich przeładowaniem (overload).

Obecnie (bo w podręcznikach z których ja się uczyłam, tego podziału nie było) dzieli się polimorfizm, ze względu na implementację, na dynamiczny i statyczny. W JS można zaimplementować polimorfizm dymamiczny i dość często się go stosuje (choć mogę się założyć, że większość programistów nie ma pojęcia, że to co robią ma taką nazwę). Jednak nie do końca on wygląda jak w definicji, choćby dlatego, że JS nie implementuje metod i klas abstrakcyjnych ani interfejsów. Do tego nie ma w nim twardego typowania. I bardzo dobrze, bo JS to nie Java czy C++. Jak ktoś nie umie żyć bez tych wynalazków, to jest mnostwo języków kompilowanych, które je oferują.

Zgodnie z definicją - polimorfizm zakłada, że obiekty różnych klas korzystają z z jednej konkretnej metody. Załóżmy, że mamy samolot, pyłek, ptaka i muchę. Te 4 obiekty mają jedną wspólną właściwość - latają. Nieważne jak, ale latają. Załóżmy, że mamy funkcję, która oczekuje na "latające" obiekty i jedyne co robi, to wywołuje na nich metodę lataj. W Javie, zazwyczaj się to implementuje za pośrednictwem interfejsu. Interfejs wymusza implementację konkretnych metod. W JS w komentarzach do prototypu lub klasy musimy napisać, że dana metoda musi być implementowana. Można również zaimplementować daną metodę by default, żeby rzucała wyjątek i przy dziedziczeniu nadpisywać ją tak, żeby przy używaniu jej w obiekcie, który umie latać nie było błędu

.

piątek, 22 lutego 2019

świadome operacje arytmetyczne na zmiennych w JS

NaN

Każdy szanujący się programista JS wie, że jeśli chce wykonać operację arytmetyczną na dwóch zmiennych i co najmniej jedna z nich jest undefined lub obiektem, to wynikiem takiego działania będzie NaN.

1 + undefined // NaN
undefined / 2 // NaN
3 * {} // NaN

Tak samo będzie, jeśli operacja nie jest dodawaniem i żadnej z liczb nie da się sparsować do liczby (parseInt lub parseFloat).

1 - 'jeden' // NaN
'dwa' / 1 // NaN

Infinity

dzielenie przez 0 lub przez null daje już inny wynik - Infinity.

3 / null // Infinity
4 / 0 // Infinity

Wartości logiczne

Wartości logiczne (true i false) są traktowane w operacjach arytmetycznych jak... 1 i 0...

3 / false // Infinity
3 / true // 3
8 * false // 0
8 * true // 8

Niebezpieczny string

Dodawanie, w którym jedną z wartości jest string jest zamieniane na łączenie stringów. Druga wartość, nieważne, jaką ma oryginalną wartość, też jest transformowana do stringa

1 + '1' // "11"
1 + 'dwa' // "1dwa"
null + 'undefined' // "nullundefined"

ale, każda inna operacja arytmetyczna już działa inaczej

3 * '4' // 12

Tablice

Jeśli operacja arytmetyczna jest dodawaniem, to wszystko co między nawiasami kwadratowymi jest traktowane, jak string. Literalnie wszystko.

2 + [3] // "23"
2 + [3, 4] // "23,4"

Zagnieżdżanie struktur nic nie daje:

2 + [3, [4, 'aaa', true, null, { a: 1}]] // "23,4,aaa,true,,[object Object]"

Jeśli chcemy wykonać operację arytmetyczną inną niż dodawanie i co najmniej jedna ze zmniennych będzie tablicą, to sprawa się komplikuje. Jeśli tablica ma tylko jeden element i jego wartość jest typem prostym, to ta wartość jest brana pod uwagę w operacji arytmetycznej.

2 * [3] // 6
2 * ['4'] // 8
3 / [0] // Infinity
['cztery'] * 2 // NaN

Przy operacji arytmetycznej innej niż dodawanie, jeśli tablica ma więcej elementów, to wynik jest zawsze NaN.

wyjątki:

[true] - 1 zwraca NaN chociaż true - 1 daje 0.
3 / [] zwraca Infinity choć [][0] daje undefined

Jak się chronić przed błędami?

Jeśli operacje arytmetyczne mają dużą wagę i muszą być wykonywane w JS jedynym działającym sposobem jest sprawdzanie typu zmiennych na których dokonujemy operacji.

Funkcje typu isFinite lub isNaN są niestety ułomne.

isFinite(true) // true
isNaN('') // false

Jeśli czekamy na liczby, które nie są wynikiem innej operacji arytmetycznej operator typeof załatwia sprawę

typeof true === 'number' // false
typeof undefined === 'number' // false
typeof null === 'number' // false
typeof '1' === 'number' // false

Jeśli natomiast wchodzące dane są wynikiem innych operacji arytmetycznych (czyli ich wartość moze być NaN), to trzeba pamiętać, że:

typeof NaN === 'number' // true

więc należy sprawdzić, czy to co wchodzi nie jest NaN

isNaN(NaN) // true

poniedziałek, 13 listopada 2017

Domknięcie (closure), co to jest i z czym to się je?

Na stronie MDN jest taka definicja A closure is the combination of a function and the lexical environment within which that function was declared. Pamiętam, jak przeczytałam ją pierwszy raz. Jak to mawiała moja mama "mózg mi stanął w poprzek". Każde słowo z osobna wydawało mi się co najmniej znajome, nawet jeśli nie w 100% zrozumiałe. Ale wszystkie do kupy powodowały, że mi się odechciewało.

To było wiele lat temu. Wiele lat, w trakcie których wciąż od nowa odkrywałam definicję domknięcia. Wczoraj robiłam proste zadanko, podpięcie pod kolumnę z danymi numerycznymi sortowania po liczbach a nie alfabetycznie (żeby 70 było mniejsze niż 320). Metodę napisałam dość banalną:

function sort(a, b, order) {
  return order === 'desc' ?
    parseFloat(a.balance) - parseFloat(b.balance) :
    parseFloat(b.balance) - parseFloat(a.balance);
};

Zadowolona z wyniku wzięłam się za testowanie. Już po chwili odkryłam, że poza kolumną opisaną w zadaniu, w tabeli jest więcej sortowalnych kolumn z danymi numerycznymi. Moja metoda, która świetnie spełniała swoje zadanie, dla jednej konkretnej kolumny, nie będzie działać, dla dowolnej innej kolumny, ponieważ sortuję wartości ukryte we właściwości balance (a.balance i b.balance). Dla innych kolumn potrzebowałam bardziej elastycznego rozwiązania.

Najłatwiej byłoby dodać nazwę kolumny, do argumentów funkcji. Ale interface funkcji przewidywał 3 konkretne argumenty: pierwszą wartość do sortowania, drugą wartość do sortowania oraz kierunek sortowania. I tu właśnie na pomoc przyszło mi domknięcie.

Ponieważ nie mogę przekazać do mojej funkcji nazwy właściwości, która przechowuje wartość do sortowania, muszę sprawić, żeby moja funkcja została stworzona w środowisku, które już zna tę nazwę. Ten przykład jasno pokazuje o co mi chodzi:

var name = 'Ania';

function greeting() {
  console.log('hello ' + name);
}

Wewnątrz funkcji greeting użyta jest zmienna name utworzona na zewnątrz funkcji. Tego typu działanie nazywa się scope, wszystko to co jest stworzone na zewnątrz funkcji jest widoczne wewnątrz, ale jeśli coś zostało stworzone wewnątrz (z użyciem któregoś ze słów kluczowych var, let lub const) nie jest widoczne na zewnątrz.

Czyli potrzebuję stworzyć środowisko, w którym znana będzie nazwa właściwości która przechowuje wartość do sortowania. Do tego środowiska będę przekazywać tę nazwę (w każdej kolumnie będę korzystać z innej właściwości):

function sortByPropName(propName) {}

Środowisko już jest, wnętrze funkcji będzie znało wartość przekazanego tam argumentu propName, jednak ja do tabeli mam przekazać funkcję, która bedzie przyjmować 3 konkretne argumenty.

function sortByPropName(propName) {
  return function(a, b, order) {}
}

Coś zaczyna z tego wychodzić. Mam już funkcję z potrzebnymi danymi, która zwraca inną funkcję, która oczekuje argumenty wyspecyfikowane w interfejsie. Teraz wystarczy dodać samą logikę, która uwzględnia przekazaną nazwę właściwości przechowujacej wartość do sortowania:

function sortByPropName(propName) {
  return function(a, b, order) {
    return order === 'desc' ?
      parseFloat(a[propName]) - parseFloat(b[propName]) :
      parseFloat(b[propName]) - parseFloat(a[propName]);
  }
}

Wygląda nieźle, prawda? To teraz wystarczy przekazać wynik sortByPropName jako funkcję sortujacą dla kolumny (kod jsx):

<TableHeaderColumn dataField="balance" dataFormat={signedNumber} dataSort sortFunc={sortByPropName('balance')}>Balance</TableHeaderColumn>
<TableHeaderColumn dataField="total_amount" dataFormat={signedNumber} dataSort sortFunc={sortByNumericValue('total_amount')}>Total amount</TableHeaderColumn>

Powyższy kod używa HTML wewnątrz JS, ale nie jak ciągu tekstowego, a bardziej jak obiektów XML. To co jest najważniejsze, to sortFunc, do której przekazuję WYNIK działania sortByPropName. sortFunc oczekuje na funkcję sortującą, która będzie przyjmowała 3 argumenty, czyli dokładnie to, co zwraca sortByPropName.

A gdzie w tym wszystkim jest domknięcie?

Dokładnie w tym, co zwraca sortByPropName. Wartość argumentu propName jest znana tylko wewnątrz funkcji, gdzie jest tworzona kolejna funkcja, która z tej wartości korzysta. Tę funkcję zwracamy na zewnątrz, gdzie jest przypisana do nazwy sortFunc i wykorzystana wewnątrz komponentu TableHeaderColumn. Pomimo, że sortFunc jest używana w zupełnie innym środowisku, ma dostęp do zmiennych ze środowiska, w którym została stworzona. To jest właśnie domknięcie.

wtorek, 22 sierpnia 2017

Oferty szybkiej nauki programowania - przestroga

Przeglądając maile natrafiłam na reklamę "coders lab", szkoły programowania. Jako, ze mój syn w sposób dość intensywny pracuje nad swoimi umiejętnościami w zakresie programowania w JavaScript z dużym zainteresowaniem przejrzałam stronę. Marketing mają niezły, ofertę wpisującą się w moje potrzeby, ale... diabeł tkwi w szczegółach.

Na studiach psychologicznych każdy student wcześniej czy później styka się z teorią krzywej uczenia się. Otóż ta teoria zakłada, że uczenie się jest PROCESEM i, jak każdy proces, wzmaga CZASU. Procesem składającym się z kilku faz. Najważniejsze są dwie: faza zdobywania nowej wiedzy i faza utrwalania (tzw plateau), kiedy krzywa jest zupełnie płaska, bo podczas utrwalania nie jesteśmy w stanie realnie nauczyć się niczego nowego, bo zdobywamy doświadczenie w nowo zdobytej wiedzy. Realnie krzywa ma kształt najbardziej zbliżony do schodów.

Znając teorie uczenia się, posuwamy się z Młodym, w nauce, pozornie żółwim tempem. Na tę chwilę wciąż jesteśmy na etapie czystego JS. Żadnych bibliotek, same zasady kierujące językiem, który poznaje. Typy danych i wbudowane obiekty mamy omówione. W ramach utrwalania tej wiedzy (faza plateau) Młody przerabia metody należące do prototypu różnych obiektów. I tak wiem, ze więcej nie zrobi, więc niech zdobywa doświadczenie w zakresie nowo poznanej wiedzy. Na tę chwilę trwa to już kilka tygodni, ale Młody ma wiedzę utrwalona. Pytania o to, jak działa scope czy jak zmienić kontekst w jakim wykonywana jest funkcja nie stanowią już dla niego wyzwania. Wiedza utrwaliła się.

Coders lab niestety hołduje zasadzie im szybciej tym lepiej. Jednym z interesujących mnie kursów z ich oferty jest "JavaScript developer: React". Kurs trwa 30 dni, w opisie stoi: "Kurs od podstaw odpowiedni dla tych, którzy wcześniej nie mieli styczności z programowaniem. Spodoba się osobom, które lubią zagadki logiczne i wolą skupić się na tym, jak coś działa niż na tym, jak wygląda[...]". Gdzieś w tym momencie przestałam rozumieć ofertę. Kurs oferuje naukę od podstaw, dla osób, które nigdy wcześniej nie miały styczności z programowaniem. Zgodnie z tytułem jest to kurs JavaScript rozszerzony o bibliotekę react. I trwa 30 dni. Nauka samego JavaScriptu to kilka miesięcy. React to też co najmniej kilka następnych tygodni. Bo przecież nie chodzi o samą znajomość składni czy używanych metod, a o zrozumienie, JAK i DLACZEGO to działa.

Hasłem przewodnim strony jest: "W Coders Lab zmieniamy edukację – w ciągu 3 miesięcy zostaniesz junior developerem". Junior developer nie musi mieć doświadczenia (stąd junior), nie musi znać zaawansowanych przypadków użycia, opracować rozwiązań skomplikowanych problemów. Junior MUSI rozumieć język i technologie, w których pracuje. Musi mieć podstawy umożliwiające mu rozwijanie wiedzy. React jest biblioteką napisaną w czystym JS. Jest tylko biblioteką, żeby react zadziałał, programista sam musi napisać kod, który skorzysta z reacta i wynikiem działania napisanego kodu będzie to, co programista chciał osiągnąć. React jest NARZĘDZIEM, którego używa się za pośrednictwem JavaScriptu, wiec żeby zostać junior developerem kandydat musi znać co najmniej bardzo dobrze JavaScript. Musi rozumieć jak działa interpreter (w przeglądarce i na serwerze - nodejs), jak JS buduje scope, czym jest kontekst. Musi znać i swobodnie używać metod obiektów wbudowanych (tablice, plain obiekty, Date, JSON). Musi widzieć dlaczego null jest specjalny i jak i kiedy go używać.

Powyższe zagadnienia są poruszane podczas rozmów kwalifikacyjnych, na stanowiska związane z JS. Bibliotek można szybko się nauczyć, a to są podstawy samego języka i są wymagane, żeby w ogóle zacząć pracę z bibliotekami.

W szczegółach kursu stoją technologie, które wchodzą w jego skład: HTML, CSS, JavaScript, jQuery, React, Gulp, Sass, RWD. Dobór trochę z Księżyca. Skoro kurs skupia się na logice a nie na prezentacji (jak jest opisane) to po co CSS, SASS i RWD? Po co komu jQuery do reacta? I co z tym wszystkim ma wspólnego gulp? I dlaczego nie ma webpacka i babel? I gdzie jest ES6/7/8?

Podsumowując. Te, i wszystkie podobne im szkolenia, muszą być dokładnie przejrzane przez kogoś, kto ma rozległą wiedzę z tematu. I cały czas należy pamiętać, że nie da się przyswoić większej ilości wiedzy w krótkim czasie, tak by ta wiedza została i służyła. Dlatego nauka w szkole trwa 12 lat a nie 4, mimo, ze dzieci uczą się szybciej. Zdobywanie wiedzy polega również na powtarzaniu i używaniu świeżo zdobytych umiejętności, wiec powracanie do wcześniej przerobionego materiału jest niezbędne. A to wszystko wymaga czasu.

czwartek, 20 lipca 2017

example how and what for to use anonymous functions

I wrote some short introduction to anonymous function here, some time ago. I wrote some simple example, but no example for real life situation. Today I decided to write down an example which could be very useful on technical interview.

This is a very simple code example. We have a loop and inside this loop we create an array of functions. Every of functions logs i on the console. Then in the end we are calling the first function from the array.

var i = 0;
var funcArray = [];
for (; i < 20; i += 2) {
  funcArray.push(function() {
    console.log('i inside function', i);
  };
});

funcArray[0]();

What we want to achieve here is, to have an array of functions, to use inside of function a value of i. We cannot use index of function in array, as i is incremented by 2. This implementation looks like the easiest solution for our need.

So what would display on console as a result of this line funcArray[0]();? We expect it to be 0, but unfortunately it is... 20... WHY??

The answer is very easy - the scope. The thing which makes JavaScript harder to learn. But is very logic. Scope exists on function level (ES5) or on block level (ES6+). In this case inside of loop the function which uses scope variable i is created. But the function is called outside the loop, where i has a value 20 (the first value, which didn't meet the requirement, so there was no more incrementation of i).

So it doesn't matter which index would be called, the i outside of loop is always equal the first value, which didn't pass the condiition. It means we have to change the way how we create the scope for our function. And here the anonymous function will come with help.

What should be done, then? We have to create immediately invoked anonymous function, which will create the scope and will return the function which will be assigned to array element. As a parameter to anonymous function i will be passed to make it available inside the scope.

funcArray.push(( // immediately invoked function
// anonymous function, which gets i from scope as index
  function (index) {
    return function() { // returns a our function
      console.log('index inside function', index);
    }
  }
)(i)); // i passed to immediately invoked function

piątek, 1 lipca 2016

shallow and deep cloning objects in JavaScript

As all values in JavaScript are pass by reference clonig is quite popular operation, to save original object. Unfortunately JavaScript does not provide any method to clone deeply the whole object.

ES6 provided some features, which could be useful, but still we have to remember, that this is only a shallow cloning.

The first solution is to use Object.assign:
const a = {
  b: {
    c: 1
  },
  z: 15
};

console.log(a); // {"b":{"c":1},"z":15}

let d = Object.assign({}, a);

d.b.c = 5;
d.z = 29;

console.log(a); // {"b":{"c":5},"z":15}
console.log(d); // {"b":{"c":5},"z":29}

The first console.log will display original a object, but in the second a.b.c value will be changed. Only a.z value will stay the same. This is what shallow cloning is. Only first level properties are cloned, rest is passed by reference.

So what would happen if d.b would be changed?
d.b = 118;

console.log(a); // {"b":{"c":5},"z":15}
console.log(d); // {"b":118,"z":29}

The same situation will happen if we will use spread operator:
const aa = {
  g: {
    h: 1
  },
  y: 15
};

console.log(aa); // {"g":{"h":1},"y":15}

let e = { ...aa };

e.g.h = 9;
e.y = 23;
console.log(aa); // {"g":{"h":9},"y":15}

So, how to make a deep cloning in JavaScript?

1. Use JSON stringify and parse
JSON.parse(JSON.stringify(object));

2. Write own method to do a deep clone of passed object.

czwartek, 3 września 2015

ES6 - arrows

The biggest difference between arrow and function is this value. Function uses its own this value, but arrow uses this value of the enclosing context.

Arrows are always anonymous functions!

Their syntax is slightly different than function expression:
(args) => { statements }


There could be few variations from this main version:
(arg1, arg2, argN) => { statements }
arg1 => { statements }
() => { statements }

(arg1, arg2, argN) => { statements }
(arg1, arg2, argN) => expression
(arg1, arg2, argN) => ({ prop: val })


It means - prentheses are optional. Mostly if there is only one argument, or only one value to return, this could be simplified.
But there is a catch:
(arg1, arg2, argN) => expression

but
(arg1, arg2, argN) => { return expression }


Example:
(arg1, arg2) => arg1 * arg2

(arg1, arg2) => { return arg1 * arg2 }


Both return the same, but we can write it in different way.

poniedziałek, 6 lipca 2015

Codility Lesson 1 - JavaScript solutions

Some time ago I started to play with Codility challenges. As JavaScript solutions are not so popular I decided to create a GitHub repository with my solutions.

Lesson 1 - Time complexity is already merged to the repo. It contains all three tasks: FrogJmp, PermMissingElem and TapeEquilibrium.

wtorek, 16 czerwca 2015

Arrays in JavaScript - how to create an array and fill it with numbers

Most common way (and fastest if array would be big, please check jsperf.com/array-magic-vs-for):
var arr = [];
var max = 100;
for (var i = 0; i < max; i++) {
  arr.push(i);
}

But if array wouldn't be so big, we can use:
var max = 7;
var arr = Array.apply(null, {length: max}).map(Number.call, Number);

Of course very often we need some specific values. Fortunately map() gives us many options:
var max = 9;
var arr1 = Array.
   apply(null, {length: max}).
   map(function(item, index){ return ++index;});
var arr2 = Array.
   apply(null, {length: max}).
   map(Function.call, Math.random);

piątek, 28 listopada 2014

gdzie jest moje ciasteczko??

Czasami nie da sie inaczej i trzeba sięgnąć po cookie. Tak właśnie było tym razem. Prosta sprawa, user wchodzi na nasz portal, to zapamiętujemy w cookie stronę od której zaczął przeglądanie naszej witryny. Napisałam sobie prosty kod, bez żadnych bibliotek (takie coś na start, żeby mieć kod do refaktoringu i testowania rozwiązania). Pod Chrome i FF działa jak złoto, pod natywną Androidową przeglądarką również.

Pokazałam rozwiazanie naszemu seniorowi. Rzucił oczkiem i podpowiedział kilka usprawnień, m.in. wykorzystanie ścieżki ze zmiennej globalnej a nie wpisywanie jej z palca (biorąc pod uwagę fakt, że to było tylko dla jednego adresu, to co różnica?). Poprawiłam, przetetsowałam. Działa.

Jako, że rozwiązanie ogólnie przeznaczone jest tylko pod przegladarki mobilne, nie przykładałam specjalnej wagi do testowania na desktopowych. Sprawdziłam na wszystkich przeglądarkach zainstalowanych na moim HTC M8 - natywna, Chrome i FF. Działa jak złoto. Chciałam sprawdzaić pod Operą mini, ale złosliwa małpa nie chciała się łączyć z serwerem dev. Poszłam po inny telefon, to działu mobilnego. Tam na jakimś Samsungu postawili mi Operę mini, która też odmówiła współpracy. Olałam sprawę i wzięłam się za testowanie pod zwykłą Operą.

No i nie działa :(. Błędem nie rzuca, ale i nie działa. No pięknie... Jeszcze koleżanka, z którą robiłam pair programming sprawdziła na swoim WindowsPhone pod IE i też nie działa. No ki diabeł? Zostawiłam jak jest. Następnego dnia rano usiadłam do debuggowania. Modern.IE stoi na Virtualboxie, wiec wzięłam IE10 na tapetę i zaczynam debuggować. Linijka po linijce... i co? I jajo... cookie sie nie zapisuje. No jak to się nie zapisuje? - prowadzę monolog w duszy debuggując dalej. Krok po kroku testując różne ustawienia doszłam dlaczego to cookie się nie zapisuje...

Składnia jest banalnie prosta: document.cookie("nazwa=wartość");. Można ją rozszerzyć o datę ważności cookie: document.cookie("nazwa=wartość; expire=data") albo o ścieżkę dla której cookie jest ważne: document.cookie("nazwa=wartość; expire=data; path=ścieżka"). I to właśnie ta ścieżka była przyczyną problemów dla Opery i IE. Używałam ścieżki pełnej, branej ze zmiennej globalnej: http://server-name.domain.com/costam/ co okazało się zupełnie niestrawne dla tych dwóch przeglądarek. Obie wymagają podania ścieżki zaczynając od /, czyli bez domeny.

Oczywiście jaśnie oświecona Opera, ani tym bardziej IE nie dały nawet pół błędu, nic. Po prostu nie zapisywały cookie...

czwartek, 7 lutego 2013

lamanie zasad widzialnosci zmiennych w JavaScript

W poscie zasieg zmiennych (scope) w JavaScript opisalam jak wyglada widocznosc zmiennych w JavaScript. Podstawowa regula jest dosc jasna. Konkretny zasieg jest ograniczony przez funkcje. To co jest w srodku moze korzystac z tego co jest na zewnatrz, ale to co jest na zewnatrz nie ma dostepu do tego co jest w srodku.

var abc = "abc";

function xyz() {
  var def = "def";
}

console.log(abc);
console.log(def);

Wykonanie powyzszego kodu wyswietli abc oraz blad: ReferenceError: def is not defined. Zmienna def zostala zdefiniowana ze slowkiem var, przez co nie jest zmienna globalna, tylko lokalna we wlasnym scopie oraz scopach wewnetrznych. Z zewnatrz jej nie widac. Takie podejscie nazywamy zakresem statycznym lub leksykalnym (lexical scope). Tak na prawde o tym, gdzie sa jakie zmienne i jaki maja zasieg mozemy sie dowiedziec analizujac kod programu.

To na tyle powtorzenia. Czy istnieje sposob na udostepnienie zmiennych z wnetrza funkcji do globalnej przestrzeni nazw? Sprawdzmy.

Zalozmy, ze mamy taka strukture:

var a = "a";

function foo1 () {

  var b = "b";
  console.log(b);

  function foo2 () {

    var c = "c";
    console.log(c);

    console.log(b);

  }
}

foo1();
foo2();

Zasieg globlany wie o istnieniu zmiennej a i funkcji foo1. Reszta jest przed nim ukryta o czym swiadczy blad: ReferenceError: foo2 is not defined. Naszym celem jest wyciagniecie foo2 i jej zawartosci na swiatlo dzienne czyli do globalnej przestrzeni zmiennych.

Przede wszystkim zeby to zrobic musimy zmienic troszke podejscie. Statyczna prezentacja zakresow widzialnosci zmiennych nam tu nie pomoze. Do problemu trzeba podejsc dynamicznie!! Jedno z wielu rozwiazan podalam w poscie funkcje w JavaScript, part 5 - funkcje moga zwracac funkcje:

var a = "a";

function foo1 () {

  var b = "b";
  console.log("foo1 " + b);

  return function () {

    var c = "c";
    console.log("foo2 " + c);
    console.log("foo2 " + b);
  };
}

var foo2 = foo1();
foo2();

Jak widac na konsoli mamy wypisane foo1 b, ktore pochodzi z wykonania foo1 podczas przypisania tego co ona zwraca do foo2. Nastepnie wywolanie foo2 wypisuje foo2 c oraz foo2 b. Czyli z poziomu zasiegu globalnego mamy dostep do "wnetrznosci" foo1. Takie pamietanie przez funkcje swojego kontekstu nazywamy domknieciem (closure).

Innym sposobem na wykorzystanie domkniecia jest operowanie na zmiennych globalnych wewnatrz funkcji. Czym rozni sie zmienna globalna od zmiennej lokalnej w JavaScript? Slowkiem var:

var a = "a";

function foo1 () {

  var b = "b";
  console.log("foo1 " + b);

  foo2 = function () { // nie ma var

    var c = "c";
    console.log("foo2 " + c);
    console.log("foo2 " + b);
  };
}

foo1();
foo2();

Tutaj tak samo jak w poprzednim przykladzie istnienie foo2 zalezy od wykonania foo1. Foo2 nie istnieje, dopoki nie zostanie utworzone w foo1 jako zmienna globalna. Tutaj tez mamy domnkniecie, nasza zmienna globalna foo2 pamieta kontekst w ktorym zostala utworzona i wyswietla prawidlowo zmienna b.

środa, 6 lutego 2013

funkcje w JavaScript, part 5 - funkcje moga zwracac funkcje

Funkcje zawsze zwracaja jakas wartosc, nawet jesli jawnie nie jest zdefiniowane return (wtedy zwracaja undefined). Funkcja moze zwrocic tylko jedna wartosc, na przyklad tablice obiektow lub... inna funkcje:

function a() {
  console.log('A!');
  return function(){
    console.log('B!');
  };
}

a();

Jak widac, funkcja a() zwraca funkcje anonimowa. W momencie, gdy wywolamy a() na konsoli wypisze sie A!, kazde kolejne wywolanie a() ponownie wypisze tylko A! na konsoli. To co jest w return nie jest wykonywane w trakcie wolania funkcji, to jest to, co funkcja zwraca. To jak sie dobrac do tego co zwraca return?

var b = a();

a();
b();

Rozwiazaniem jest przypisanie wyniku dzialania funkcji do zmiennej. W tej sytuacji wywolanie a() nadal bedzie wypisywac A!, natomiast b(), do ktorego zostala przypisana funkcja anonimowa, ktora zwraca funkcja a(), bedzie wypisywac B!.

W ten sam sposob mozna zrealizowac nadpisanie (czy przepisanie) sie funkcji. Wystarczy pod wynik dzialania funkcji a() podstawic zmienna... a:

var a = a();

a();
a();

Tylko za pierwszym razem, w momencie przypisania wykonuje sie cialo oryginalnej funkcji a() i na konsoli pojawia sie A!. Po przypisaniu, funkcja a() przy kazdym wywolaniu na konsoli pisze B!, poniewaz teraz jej cialem stala sie funkcja anonimowa zwracana przez oryginalna funkcje a().

No dobrze, wszystko pieknie, ale co jesli funkcja anonimowa zwracana przez oryginalna funkcje a() korzysta z zakresu zmiennych dostepnych w oryginalnej funkcji a()? Ot, na przyklad:

function a() {
  console.log('A!');
  var abc = "abc";
  return function(){
    console.log(abc);
  };
}

Otoz, wszystko bedzie dzialac. Dla funkcji anonimowej scope zostanie zachowany (jak i dlaczego, o tym za chwile, w innym poscie):

var a = a();

a();
a();

Przy pisaniu na konsoli zobaczymy A!, natomiast kazde kolejne wywolanie a() wypisze juz na konsoli abc.

w sprawie samowywolujacych sie funkcji

Do postu o samowywolujacych sie funkcjach dostalam komentarz, bardzo trafny zreszta. Wiec postanowilam napisac kilka slow na ten temat.

Faktem jest, ze polskie tlumaczenie self-invoking nie jest jakos specjalnie udane, ale z braku lepszego uzywamy wlasnie okreslenia samowolajaca sie funkcja. Ale tak na prawde, dziekuje Ci Anonimowy Czytelniku, za podniesienie tej kwestii, ta funkcja jest wykonywana w miejscu w ktorym zostala w kodzie wstawiona. Sama sie nie wola, jest od razu wykonywana przez przegladarke. Oczywiscie dzieje sie tak dzieki dwom parom nawiasow okraglych, z ktorych jedna para obejmuje funkcje (jako calosc) a druga wskazuje ze jest to funkcja, dzieki czemu przegladarka ja wykonuje.

wtorek, 6 listopada 2012

funkcje w JavaScript, part 4 - funkcje prywatne

W poscie zasieg zmiennych (scope) w JavaScript pisalam o zasiegu zmiennych. Tworzenie funkcji prywatnych (lub wewnetrznych - private, inner functions) dziala w opisany w wyzej wymienionym poscie sposob:
function a {
  var abc = 12;
  function b () {
    console.log(abc);
  }
  b();
}

Funkcje sa danymi, wiec mozemy je definiowac wewnatrz innych funkcji. I zachowuja sie dokladnie tak samo jak zmienne. Jesli teraz wywolamy funkcje a:
a();

to na konsoli zostanie wypisana liczba 12. Jesli natomiast sprobujemy wywolac b:
b();

to otrzymamy ReferenceError: b is not defined. Ten sam blad (tylko z nazwa innej zmiennej) pojawi sie, jesli sprobujemy wyswietlic wartosc zmiennej abc. Zarowno funkcja b jak i zmienna abc sa zdefiniowane wewnatrz funkcji a, wiec sa widoczne tylko w jej wnetrzu, a poza nia srodowisko ich nie zna.