JavaScript od kuchni
Krzysztof Folwarczny
Java developer@Objectivity, od 3 lat developer w zespole ImmobilienScout24
Klasyczne podejście generuje jednak problemy:
*za wyjątkiem przypadków
Proces wykonywania kodu można podzielić na dwie części:
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);
JavaScript umożliwia wprowadzenie pewnej dynamizmu do kodu:
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);
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());
function [name]([param[, param[, ... param]]]) {
statements
}
Functions
Function expresion
Function declaration
named
anonymouse
Tip & Trick
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:
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);
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ść
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);
})();
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();
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:
var user = {
name: 'world',
clickHandler: function(){
console.log('Hello ' + this.name);
}
};
$ ("button")
.click(user.clickHandler.bind(user));
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:
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
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 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