Viktor Shevchenko
Web developer from Kyiv
JavaScript ProCamp GlobalLogic
// 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);
function square(x) { return x*x; }
var square = function(x) { return x*x; }
Function definition statement
Function definition expression
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);
function hypotenuseOrDefault(a, b) {
const default = 10;
function square(x) { return x*x || default; }
return Math.sqrt(square(a) + square(b));
}
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
JavaScript design pattern which produces a lexical scope using JavaScript's function scoping.
Immediately-invoked function expressions can be used:
Immediately-invoked function expression
(self-executing anonymous function)
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;
}
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
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;
}
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();
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"
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});
function namedParameterRiddle([start, end]) {
return `Lenght is from ${start} to ${end}`
}
const length = namedParameterRiddle([1,5,6,8]);
"Lenght is from 1 to 5"
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);
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};`
}
function greet(name) {
const greeting = 'hello';
function say(name) {
return `${greeting} ${name}`
}
return say();
}
const greeting = 'hi';
greet();
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"
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
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
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
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);
'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
const o = {
innerWidth: 15,
getWidth: () => {
const button = document.querySelector('button');
button.addEventListener('click', () => this.innerWidth)
}
};
o.getWidth();
// click button
// window.innerWidth
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
lexical scoped are:
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
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 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
const uniqInteger = (() => {
let counter = 0;
return () => counter++
})();
uniqInteger(); // 0
uniqInteger(); // 1
uniqInteger(); // 2
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!
// 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));
// 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 multiply5(x) {
return 5*x;
}
function add10(x) {
return 10 + x;
}
add10AndMuldtiply5 = compose(multiply5, add10);
add10AndMuldtiply5(4); // 30
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
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
vs
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: 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'
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 );
};
}());
}
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
By Viktor Shevchenko
"Functions" slides for JavaScript ProCamp in GlobalLogic