Functions

JavaScript ProCamp GlobalLogic

Contents

  1. Function
    • definition
    • invoking
    • function specificity and ES6 improvements
  2. Arrow Functions
  3. A step into the Functional Programming:
    • Pure Functions
    • High Order Function
    • Closures

Function

// Compute the hypotenuse between points (x1,y1) and (x2,y2).
function hypotenuse(x1, y1, x2, y2) {
    const dx = x2 - x1,
          dy = y2 - y1;

    return Math.sqrt(dx*dx + dy*dy);
}

const h1 = hypotenuse(2,3,5,6);
  • parameters
  • 'arguments' variable
  • return
  • 'this' keyword

Definition

function square(x) { return x*x; }
var square = function(x) { return x*x; }

Function definition statement

Function definition expression

Definition

const square = new Function("x", "return x*x");

square(10);

Function definition expression via Function constructor

 or eval

eval("const square = function(x) {return x*x}");

square(10);

Nested Functions

function hypotenuseOrDefault(a, b) {
    const default = 10;
    function square(x) { return x*x || default; }
    return Math.sqrt(square(a) + square(b));
}

Invocation

function square(x) {return x*x}

square(10); // 100
const o = {
    x: 10
    square: function() {return this.x * this.x}
}

o.square(); // 100
o['square'](); // 100
function square(x) {this.square = x}

const mySquare = new Square(10);

mySquare.square; // 10
const o = {
    x: 10
    square: function() {return this.x * this.x}
}

const p = {
    x: 15
}
o.square.call(p); // 225
o.square.appy(p); // 225

function

method

constructor

indirect invocation

IIFE

JavaScript design pattern which produces a lexical scope using JavaScript's function scoping.

 

Immediately-invoked function expressions can be used:

  • protect against polluting the global environment
  • protect from name clash
  • allow public access to methods while retaining privacy for variables defined within the function

Immediately-invoked function expression

(self-executing anonymous function)

IIFE

function calculateSomething(x, y, z) {
    var x1 = computeSomethingFrom(x);
    var y1 = x1 + y;
    var z1 = computeZ(y1, z);

    // some other code, maybe a lot of some other code,
    // where x1, y1 - temporary computation variables are available

    return z1;
}
function calculateSomething(x, y, z) {
    var z1 = (function(){
        var x1 = computeSomethingFrom(x);
        var y1 = x1 + y;
        
        return computeZ(y1, z);
    })();

    // some other code, maybe a lot of some other code,
    // where x1, y1 - temporary computation variables are NOT available

    return z1;
}

IIFE

var counter = (function(initial){
  var i = initial;

  return {
    get: function(){
      return i;
    },
    set: function( val ){
      i = val;
    },
    increment: function() {
      return ++i;
    }
  };
})(5);

Parameters and private variables


counter.get();
counter.set( 3 );
counter.increment();
counter.increment();
 
 // 5

 // 4
 // 5

IIFE

Try to avoid using IIFE when possible and replace them with let and const

function quotatious(names) {
    var quotes = [];
     
    for (var i = 0; i < names.length; i++) {   
        (function(index) {
            var theFunction = function() {
                return `My name is ${names[index]}!`;
            }
            quotes.push(theFunction);
        })(i);
    }
    return quotes;
}
function quotatious(names) {
    var quotes = [];
     
    for (let i = 0; i < names.length; i++) {   
        var theFunction = function() {
            return `My name is ${names[index]}!`;
        }
        quotes.push(theFunction);
    }
    return quotes;
}

ES6 Improvements

Parameters

  • Default parameters
  • Named parameters
  • rest parameters
  • Destructuring

Default Parameters

function hypotenuse(x1, y1, x2, y2) {
    const x1 = x1 || 0,
          y1 = y1 || 0,
          x2 = x2 || 1,
          y2 = y2 || 1,
          dx = x2 - x1,
          dy = y2 - y1;

    return Math.sqrt(dx*dx + dy*dy);
}

const h1 = hypotenuse();
function hypotenuse(x1 = 0, y1 = 0, x2 = 1, y2 = 1) {
    const dx = x2 - x1,
          dy = y2 - y1;

    return Math.sqrt(dx*dx + dy*dy);
}

const h1 = hypotenuse();

Default parameters

function defaultParamtersRiddle(a = 'Hello', b = 'my', c = 'name', d = 'is', e = 'Viktor') {
    return `${a} ${b} ${c} ${d} ${e}`;
}

const string = defaultParamtersRiddle(undefined, null, {}, '');
"Hello null [object Object]  Viktor"

Named Parameters

function hypotenuse(parameters) {
    const dx = parameters.x2 - parameters.x1,
          dy = parameters.y2 - parameters.y1;

    return Math.sqrt(dx*dx + dy*dy);
}

const h1 = hypotenuse({x1: 1, y2: 1, z1: 3, x2: 5, y2: 5, z2: 3});
function hypotenuse({ x1, x2, y1, y2 }) {
    const dx = x2 - x1,
          dy = y2 - y1;

    return Math.sqrt(dx*dx + dy*dy);
}

const h1 = hypotenuse({x1: 1, y2: 1, z1: 3, x2: 5, y2: 5, z2: 3});

Named Parameters

function namedParameterRiddle([start, end]) {
    return `Lenght is from ${start} to ${end}`
}

const length = namedParameterRiddle([1,5,6,8]);
"Lenght is from 1 to 5"

Arguments keyword

function logAllArguments() {
    for (var i=0; i < arguments.length; i++) {
       console.log(arguments[i]);
    }
}

logAllArguments(1, 3, 5);
function logAllArguments(...params) {
    params.forEach((p) => console.log(p))
}

logAllArguments(1, 3, 5);

Arguments keyword

function restRiddle(???) {
    ???
}

restRiddle(1, 3, 5); 
// Function was called with 3 parameters. First is 1;
function restRiddle(...rest) {
    cosnt [first] = rest;
    return `Function was called with ${rest.length} parameters. First is ${first};`
}

Scope and this

Lexical Scope

function greet(name) {
    const greeting = 'hello';

    function say(name) {
        return `${greeting} ${name}`
    }

    return say();
    
}

const greeting = 'hi';

greet();

Lexical Scope

const greeting = 'bonjour';

function greet(name) {
    const greeting = 'hello';

    const say = new Function('name', 'return `${greeting} ${name}`');

    return say(name);
}


greet('Viktor')
const greeting = 'bonjur';

function greet(name) {
    const greeting = 'hello';

    eval('var say = function(name){return `${greeting} ${name}`}')

    return say(name);
}

greet('Viktor')
// "bonjour Viktor"

// "hello Viktor"

this keyword

function getWidth() {
    return this.innerWidth;
}

getWidth();
const o = {
    innerWidth: 15,
    getWidth() {
        return this.innerWidth;
    }
};    

o.getWidth();
const button = document.querySelector('button');

button.addEventListener('click', function() {
    return this.innerWidth;
});
// window.innerWidth
// o.innerWidth 
// 15
// button element width
const gw = o.getWidth;

gw();
// window.innerWidth

this

const o = {
    innerWidth: 15,
    getWidth() {

        const button = document.querySelector('button');

        button.addEventListener('click', function() {
            return this.innerWidth;
        });
    }
};    

o.getWidth();
// click button
// button.width

How to fix:

1. const self = this;

2. pass this

3. bind(this)

4. arrow function

this

const o = {
    innerWidth: 15,
    thisRiddle(context) {

        const button = document.querySelector('button');

        button.addEventListener('click', function() {
            this = context;
            return this.innerWidth;
        });
    }
};    

o.thisRiddle(o);
// click button
// Uncaught ReferenceError: Invalid left-hand side in assignment

Arrow Functions

Shorter syntax

const arr = [1, 2, 3];

// Traditional function expression:
const squares = arr.map(function (x) { return x * x });


// arrow function
const squares = arr.map(x => x * x);

Arrow Functions

'this' is lexical scoped

const o = {
    innerWidth: 15,
    getWidth() {
        const button = document.querySelector('button');
        button.addEventListener('click', () => this.innerWidth);
    }
};    

o.getWidth();
// click button
const o = {
    innerWidth: 15,
    getWidth() {
        const button = document.querySelector('button');
        
        button.addEventListener('click', function() {
            return this.innerWidth;
        }.bind(this));
    }
};    

o.getWidth();
// click button

Arrow Functions Riddle

const o = {
    innerWidth: 15,
    getWidth: () => {
        const button = document.querySelector('button');
        
        button.addEventListener('click', () => this.innerWidth)
    }
};    

o.getWidth();
// click button
// window.innerWidth

Arrow Functions Riddle

function listNames() {
   const listName = name => console.log(arguments.length)

   listName(arguments[0])
}

listNames('Viktor', 'Vlad', 'Andrey');
// 3

function Animal(type) {
   this.type = type;

   setTimeout(() => console.log({`function was called ${new.targer ? 'as constructor' : 'as function'}`}), 1000)
}

// function was called as constructor

Arrow Functions

lexical scoped are:

  • arguments

  • super

  • this

  • new.target

Arrow Functions

Can not be used as a constructor

typeof (() => {})
'function'

() => {} instanceof Function
true

typeof function () {}
'function'

function () {} instanceof Function
true
const Rectangle = (w, h) => { 
    this.width = w;
    this.height = h; 
}

const rect = new Rectangle(10, 15);
// TypeError: Rectangle is not a constructor

typeof Rectangle.prototype // undefined

Arrow Functions

x => x*x

(x, y) => x*y

x => {
   cosnt x1 = x + 15;
   const x2 = parseInt(x1/3, 10);
   return x2 
}

(x, y) => {
   cosnt x1 = x + 15;
   const y1 = parseInt(y/3, 10);
   return x1 + y1
}

(x, y) => ({[x]: y})

syntax

Function are Objects

function uniqInteger() {

    if (!uniqInteger.counter) {
        uniqInteger.counter = 0;
    }

    return uniqInteger.counter++;
}

uniqInteger(); // 0
uniqInteger(); // 1 
uniqInteger(); // 2
// Amount of parameters
uniqInteger.length; // 0


// Prototype for objects created when invoked as a constructor function
typeof uniqInterger.prototype // object

// Function methods

uniqInteger.call
uniqInteger.apply
uniqInteger.bind

Closures

const uniqInteger = (() => {
    let counter = 0;

    return () => counter++
})();

uniqInteger(); // 0
uniqInteger(); // 1
uniqInteger(); // 2

Closures

function creatCounter(n) { // Function argument n is the private variable
    return {

        // Property getter method returns and increments private counter var.
        get count() { return n++; },

        // Property setter doesn't allow the value of n to decrease
        set count(m) {
            if (m >= n) n = m;
            else throw Error("count can only be set to a larger value");
        }
    };
}
const counter = creatCounter(1000);
counter.count // => 1000
counter.count // => 1001
counter.count = 2000
counter.count // => 2000
counter.count = 2000 // => Error!

Functional programming

First Class Functions

// We define some simple functions here
function add(x,y) { return x + y; }
function subtract(x,y) { return x - y; }
function multiply(x,y) { return x * y; }
function divide(x,y) { return x / y; }

// Here's a function that takes one of the above functions
// as an argument and invokes it on two operands
function operate(operator, operand1, operand2) {
    return operator(operand1, operand2);
}

// We could invoke this function like this to compute the value (6/3) + (4*5):
var i = operate(add, operate(divide, 6, 3), operate(multiply, 4, 5));

High Order Function

// This higher-order function returns a new function that passes its
// arguments to f and returns the logical negation of f's return value;
function not(f) {
    return function(...rest) { // Return a new function
        var result = f.apply(this, rest); // that calls f
        return !result; // and negates its result.
    };
}

var even = function(x) { // A function to determine if a number is even
    return x % 2 === 0;
};

var odd = not(even); // A new function that does the opposite

Function Composition

function multiply5(x) {
    return 5*x;
}

function add10(x) {
    return 10 + x;
}


add10AndMuldtiply5 = compose(multiply5, add10);

add10AndMuldtiply5(4); // 30

Pure Functions

A pure function is a function where the return value is only determined by its input values, without observable side effects The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices.

 

Evaluation of the result does not cause any semantically observable side effect or output

Pure Functions

function Sum(a, b){
  return a + b;
}
var a = 5;

function AddsFive(b){
  return a + b;
}
function timeSince(time){
  return Date.now() - time;
}
Math.rand
Math.max
// pure
// impure
// impure
// impure
// pure

Partial Application

Currying

vs

Partial Applications

The process of applying a function to some of its arguments. The partially applied function gets returned for later use.

function log(service, context, message) {
    console.log(`${service} ${context} ${message}`);
}


const mongoLog = log.bind(null, 'Mongo');

mongoLog('collection', 'is empty') // 'Mongo collection is empty'


const uiDashboardLog = log.bind(null, 'UI', 'Dashboard');

uiDashboardLog('please refresh') // 'UI Dashboard please refresh'

Curry

Curry: A function that takes a function with multiple parameters as input and returns a function with exactly one parameter.

function log(service, context, message) {
    console.log(`${service} ${context} ${message}`);
}


const curryiedLog = curry(log);

curryiedLog('Mongo')('collection')('is empty'); // // 'Mongo collection is empty'


const uiDashboardLog = curryiedLog('UI')('Dashboard');

uiDashboardLog('please refresh') // 'UI Dashboard please refresh'

Curry

function curry(fn) {
    const arity = fn.length;

    return (function resolver(...rest){
        let memory = [...rest];
	
	return (...rest) => {
	    const local = [...memory, ...rest];
            const next = local.length === arity ? fn : resolver;
	    return next.apply(null, local);	
        }
    })();
}
function curry( fn ) {
  var arity = fn.length;
  return (function resolver() {
    var memory = Array.prototype.slice.call( arguments );
    return function() {
      var local = memory.slice(), next;
      Array.prototype.push.apply( local, arguments );
      next = local.length >= arity ? fn : resolver;
      return next.apply( null, local );
    };
  }());
}

Memorization


function memoize(fn) {
    var cache = {}; 

    return function(...rest) {
        const key = `${rest.length}-${rest.join(',')}`;

        if (key in cache) {
            return cache[key];
        }
        
        return cache[key] = fn.apply(this, rest);
    };
}

const getUserData (name) {
    return fetch(`users/${name}`);
}

const getUserDataMemo = memorize(getUserData);

getUserDataMemo('Viktor'); // from server
getUserDataMemo('Viktor'); // from cache
getUserDataMemo('Allen'); // from server

To read

The End

[GL ProCamp] Functions

By Viktor Shevchenko

[GL ProCamp] Functions

"Functions" slides for JavaScript ProCamp in GlobalLogic

  • 1,371