JavaScript od kuchni
Krzysztof Folwarczny
Java developer@Objectivity, od 3 lat developer w zespole ImmobilienScout24
Plan prezentacji
- ZJAVA meetup ~ JavaScript od kuchni?
- Compiler, scopes, functions
- Kompilator JIT
- funkcje, hoisting
- lexical, dynamic scope
- IIFE pattern
- 'this' keyword
- czym jest this
- funkcja konstruktora
- Closure
- module pattern
- przykłady
ZJAVA meetup i
JavaScript od kuchni??
... ponieważ
JavaScript jest językiem kompilowalnym?
Klasyczna kompilacja
Klasyczne podejście generuje jednak problemy:
- jest powolne
- angażuje duże zasoby systemu
- nie jest trywialne dla języków, które:
- nie są silnie typizowane
- są dynamiczne
- wykorzystują late-biding
wprowadzone do JavaScriptu w 2008 roku
Rozwiązaniem jest
JIT
Just-In-Time compiling
Ewolucja silnika JavaScript
Od 2008 możemy powiedzieć, że JavaScript jest językiem kompilowanym.
Ale po co?
Scope
W JavaScript zasięg zmiennych definiuje funkcja*
*za wyjątkiem przypadków
- Blok Try-Catch
- Blok tworzony za pomocą słowa 'let' ---> od ECMA 6
Proces wykonywania kodu można podzielić na dwie części:
- etap deklaracji zmiennych
- etap wykonania programu
JavaScript posiada lexical scope
Lexical scope
var foo = 1;
function bar() {
var foo = 2;
foo2 = 3;
console.log('in bar foo: ' + foo);
console.log('in bar foo2: ' + foo2);
}
function baz(param){
param = 1;
console.log(param);
}
bar();
baz();
console.log(foo);
console.log(foo2);
console.log(param);
Scope cheating!
JavaScript umożliwia wprowadzenie pewnej dynamizmu do kodu:
- eval
- with
Obu konstrukcji należy unikać!
// Przykład nr 1
var foo = 1;
function doMagic(magicCode){
eval(magicCode);
console.log(foo);
}
doMagic('var foo = 42;');
// Przykład nr 2
var person = {
age: 20,
name: 'Janusz'
};
with (person){
age = 21;
email = 'cool@mail.com';
}
console.log(person.email);
console.log(email);
zawsze 'use strict'
Function expression and function declaration
function foo(){
function bar() {
return "foo";
}
return bar();
function bar() {
return "bar";
}
}
alert(foo());
function foo(){
var bar = function() {
return "foo";
};
return bar();
var bar = function() {
return "bar";
};
}
alert(foo());
function foo(){
return bar();
var bar = function() {
return 3;
};
var bar = function() {
return 8;
};
}
alert(foo());
Funkcje w JavaScript
function [name]([param[, param[, ... param]]]) {
statements
}
- są tzw. first-class-object
- zachowują się jak zwykłe obiekty
- mają metody i parametry
- mogą być tzw. high-order functions
- zawsze zwracają wartość
- tworzą nowy scope
- parametry do funkcji są przekazywane przez wartość
- obiekty są przekazywane przez referencję
Functions
Function expresion
Function declaration
named
anonymouse
- named function variable
- wymagają podania nazwy funkcji
- są zabronione w non-function scope
- rejestrują się na scopie swoim oraz rodzica
- są wykonywane zawsze na początku
- wyrażenie function expression nie może zaczyna się od słowa "function"
- nie wymagają nazewnictwa
- rejestrują się tylko na swoim scopie
Tip & Trick
- nigdy nie umieszczać function declaration w bloku if
- function expression tylko w wersji named
- function declaration umieszczaj na górze
Hoisting
var a = b();
var c = d();
a;
c;
function b(){
return c;
}
var d = function(){
return b();
}
to mechanizm wynoszenia na górę kontekstu deklaracji zmiennych i funkcji:
- funkcje są zawsze wynoszone ponad zmienne jako pierwsze
- function expression nie podlegają hoistingowi
IIFE
Immediately invoked function expression
Wzorzec projektowy, który wprowadza pojęcie publicznych i prywatnych obiektów do JS.
var rest = (function restTemplate(global){
var host = 'www.hostname:8080/';
function get(url){
console.log('getting from url: ' + host + url);
};
function post(url, body){
console.log('post object: ' + body + ' to url: ' + host + url);
}
global.api = {
get : get,
post : post
};
})(window);
'this'.
Każda funkcja w momencie wywołania ma referencje do swojego kontekstu wywołania nazwanego 'this'
Lexical scope
'this' mówi nam do którego budynku mamy wejść
Jak określić czym jest 'this', 4 zasady!
- default binding
- implicit binding
- explicit binding
- 'new' keyword
Default binding
Najbardziej ogólna zasada wyznaczania 'this', która mówi że 'this' może być albo global, albo undefined w zależności od tego czy pracujemy w strict mode.
Zasada ta ma znaczenie gdy funkcja jest wywoływana w 'czysty' sposób poprzez swoją referencję, np. foo();
var caller = 'global';
var name = 'world!';
// Reference call
function sayHello(){
console.log('Hello ' + this.name);
}
sayHello();
//IIFE
(function(){
console.log('IIFE call: ' + this.caller);
})();
Implicit binding
Zasada ta mówi, że obiekt 'this'
przyjmuje wartość obiektu na którym została wykonana dana funkcja.
Zasada ta ma znaczenie gdy funkcja jest wywoływana poprzez referencję na obiekcie, np. object.foo();
var person1 = {
name: 'Janusz',
age: 30,
introduce: sayHello
};
var person2 = {
name: 'Stefan',
age: 35,
introduce: sayHello
};
function sayHello(){
console.log('Hello ' + this.name +
', you are ' + this.age + ' years old');
}
person1.introduce();
person2.introduce();
Explicit binding
Zasada explicit binding oznacza, że jasno wskazujemy jakim obiektem 'this' ma być podczas wywołania funkcji.
Takie "twarde" połączenie można uzyskać za pomocą funkcji:
- apply()
- call()
- bind()
var user = {
name: 'world',
clickHandler: function(){
console.log('Hello ' + this.name);
}
};
$ ("button")
.click(user.clickHandler.bind(user));
'new' keyword
Zasada ta mówi, że obiekt 'this'
przyjmuje wartość nowo utworzonego obiektu przy pomocy 'new'.
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var bmw = new Car("BMW", "318d", 2016);
console.log(bmw.make);
Dodanie 'new' przed funkcją powoduje:
- utworzenie nowego pustego obiektu
- obiekt zostaje podlinkowany do prototypu funkcji
- utworzony obiekt zostaje przypisany do 'this'
- jeśli funkcja nic nie zwraca, to zwróć 'this'
4 pytania Kyle Simpson'a
- Czy funkcja została wywołana za pomocą 'new'?
- Czy funkcja została wywołana poprzez 'call', 'apply', czyli explicit binding?
- Czy funkcja została wywołana na jakimś konkretnym obiekcie?
- 'this' = global || undefined
Closure
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
MDN
Rozważmy dwa przykłady
function init() {
var name = "Mozilla";
function displayName() {
alert (name);
}
displayName();
}
init();
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
Lexical scoping
Closure
Wykorzystując closure możemy tworzyć konstrukcje w stylu partial function w JavaScript.
function power(x){
return function powerTo(y){
return Math.pow(y,x);
};
}
var pow2 = power(2);
var pow4 = power(4);
pow2(2); //4
pow4(6); //1296
...
Niezależnie od liczby wewnętrznych funkcji zawsze tworzony jest jeden closure.
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
var num = 666;
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 667
gSetNumber(5);
gLogNumber(); // 5
Często closure jest wykorzystywany błędnie w pętlach.
function logToFive(){
for(var i = 0; i<6;i++){
setTimeout(function(){
console.log(i);
}, 1000);
}
}
logToFive();
//// After 5 seconds
6
function logToFive(){
for(var i = 0; i<6;i++){
(function(x){
setTimeout(function(){
console.log(x);
}, 1000);
})(i);
}
}
logToFive(); //// After
0
1
2
3
4
5
Module pattern
Module pattern umożliwia wprowadzenie do JavaScriptu metod publicznych i prywatnych. Dzięki zastosowaniu IIFE, lexical scope i closure JavaScript jest wstanie emulować zakres dostępu do obiektów.
var counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
Wykorzystywanie Module pattern zapewnia:
- Enkapsulacje i ukrywanie danych
- Lepsze zarządzanie przestrzenią nazw
- Zarządzalną strukturę kodu
Materiały
Q & A
Dzięki!
JavaScript od kuchni
By Krzysztof Folwarczny
JavaScript od kuchni
- 595