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!

Made with Slides.com