by Kiril Knysh
function as a function
function add(left, right) {
return left + right;
}
function as a method
const obj = {
left: 10,
add: function(right) {
this.left += right;
return this.left;
},
};
function as a constructor
function Pie(radius, height) {
this.radius = radius;
this.height = height;
}
const pie = new Pie(20, 5);
function is an object (first-class function)
function logPrice(price) {
console.log(price);
}
function currencize(logger, currency) {
function currencized(price) {
return logger(currency + price);
}
currencized.currency = currency;
return currencized;
}
const logPriceEuro = currencize(logPrice, '€');
logPriceEuro(42);
define function:
function declaration
function functionName([parameters]) {
// function body
}
add(1, 1);
function add(left, right) {
return left + right;
}
function declaration
console.log(pie);
var pie = 'lemon'; // can be fixed by var -> let
console.log(pie);
pie = 'choco';
function pie() {}
console.log(pie);
function expression
variableName = function([parameters]) {
// function body
};
const add = function(left, right) {
return left + right;
}
add(1, 1);
named function expression
variableName = function functionName([parameters]) {
// function body
};
const getFibonacci = function fibonacci(n) {
if (n <= 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
getFibonacci(5);
fibonacci(10); // ReferenceError: fibonacci is not defined
Function constructor
const add = new Function('left', 'right', 'return left + right;');
add(1, 41);
Every JavaScript function is actually a Function object.
passing arguments to function
passing primitives to function
function cookPie(type, radius, slice) {
console.log('#cookPie:0', type, radius, slice);
type = 'choco';
radius = 42;
slice = false;
console.log('#cookPie:1', type, radius, slice);
}
const type = 'lemon';
const radius = 30;
const slice = true;
cookPie(type, radius, slice);
console.log('#global:0', type, radius, slice);
passing objects to function
function cookPie(pieOptions) {
console.log('#cookPie:0', pieOptions);
pieOptions.type = 'choco';
console.log('#cookPie:1', pieOptions);
pieOptions = {};
console.log('#cookPie:2', pieOptions);
}
const options = { type: 'lemon', radius: 30, slice: true };
cookPie(options);
console.log('#global:0', options);
passing arguments to function
extra arguments are ignored
function cookPie(type, radius, slice) {
console.log('#cookPie:0', type, radius, slice);
}
cookPie('lemon', 21, true, 'extra', 'args');
missing arguments set to undefined
function cookPie(type, radius, slice) {
console.log('#cookPie:0', type, radius, slice);
}
cookPie('lemon');
passing arguments to function
function cookPie() {
console.log('#cookPie:0', arguments[0], arguments[1], arguments[2]);
console.log('#cookPie:1', arguments.length);
console.log('#cookPie:2', arguments.callee);
console.log('#cookPie:3', Array.isArray(arguments));
}
cookPie('lemon', 14, true);
passing ...arguments to function
function cookPie(...args) {
console.log('#cookPie:0', args[0], args[1], args[2]);
console.log('#cookPie:1', args.length);
console.log('#cookPie:2', Array.isArray(args));
}
cookPie('lemon', 14, true);
function sharePie(type, ...friends) {
console.log('#cookPie:0', type, friends);
}
sharePie('lemon', 'Albert', 'Alan', 'Stephen');
returning value from function
return value;
return; // return undefined;
except constructors whose default return value is this
returning value from function
function returnPieType(n) {
if (n === 0) {
return 'lemon';
}
if (n === 1) {
return;
}
if (n === 2) {}
if (n === 3) {
this.type = 'choco';
}
}
console.log(returnPieType(0));
console.log(returnPieType(1));
console.log(returnPieType(2));
console.log(new returnPieType(3));
function as data
function cookPie(type, radius, slice) {
console.log('#cookPie:0', type, radius, slice);
}
const pieFlow = [mixIngredients, cookPie];
pieFlow[1]('lemon', 42, false);
cookPie.oven = 'oven#1';
const lemonPie = {
type: 'lemon',
cook: cookPie,
};
function as data
function callCounter(func) {
func.callCounter = 0;
return function(...args) {
func.callCounter += 1;
return func(...args);
};
}
function cookPie(type, radius, slice) {
console.log('#cookPie:0', type, radius, slice);
}
const countedCookPie = callCounter(cookPie);
this
this
this in global context
function cookPie() {
console.log(this);
}
cookPie();
value of this is determined by the invocation form, not context of definition
function cookPie() {
console.log(this.type);
}
const lemonPie = { type: 'lemon' };
const chocoPie = { type: 'choco' };
lemonPie.cookPie = cookPie;
chocoPie.cookPie = cookPie;
lemonPie.cookPie(); // 'lemon'
chocoPie.cookPie(); // 'choco'
cookPie(); // undefined
new cookPie(); // undefined
value of this
function cookPie() {
console.log(this); // window
}
function cookStrictPie() {
'use strict';
console.log(this); // undefined
}
const lemonPie = {
type: 'lemon',
cookPie: function () { console.log(this); },
};
lemonPie.cookPie();
const cookPie = lemonPie.cookPie;
cookPie();
value of this
function Pie(type, radius) {
this.type = type;
this.radius = radius;
this.cook = function () {
console.log(this);
};
}
const lemonPie = new Pie('lemon', 38);
lemonPie.cook();
const chocoPie = Pie('choco', 24);
chocoPie.cook();
value of deep this
function Pie(type, radius) {
this.type = type;
this.radius = radius;
this.cook = function () {
const that = this;
console.log(that); // pie
const innerCook = function() {
console.log(this); // window
console.log(that); // pie
};
innerCook();
};
}
const lemonPie = new Pie('lemon', 38);
lemonPie.cook();
change function's context:
change function's context: call
function cookPie() {
console.log('#cookPie', this.type, this.radius);
}
const lemonPie = { type: 'lemon', radius: 9, cook: cookPie };
lemonPie.cook();
const chocoPie = { type: 'choco', radius: 17 };
lemonPie.cook.call(chocoPie);
function cookPie(radius, slice) {
console.log('#cookPie', this.type, radius, slice);
}
const lemonPie = { type: 'lemon', radius: 9 };
cookPie.call(lemonPie, 14, false);
change function's context: apply
function cookPie() {
console.log('#cookPie', this.type, this.radius);
}
const lemonPie = { type: 'lemon', radius: 9, cook: cookPie };
lemonPie.cook();
const chocoPie = { type: 'choco', radius: 17 };
lemonPie.cook.apply(chocoPie);
function cookPie(radius, slice) {
console.log('#cookPie', this.type, radius, slice);
}
const lemonPie = { type: 'lemon', radius: 9 };
cookPie.apply(lemonPie, [14, false]);
call vs apply
function.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [argsArray])
const numbers = [1, 13, 8, 5, 3];
console.log(Math.max.apply(Math, numbers));
console.log(Math.max.call(Math, ...numbers));
console.log(Math.max(...numbers));
bind
fun.bind(thisArg, arg1, arg2, ...)
function cookPie(radius, slice) {
console.log('#cookPie', this.type, radius, slice);
}
const lemonPie = { type: 'lemon', radius: 9 };
const cookLemonPie = cookPie.bind(lemonPie);
cookLemonPie(47, true);
const cookHugeLemonPie = cookPie.bind(lemonPie, Number.MAX_SAFE_INTEGER);
cookHugeLemonPie(false);
IIFE (Immediately Invoking Function Expressions)
function () {
// code
}
(function () {
// code
})
(function () {
// code
})();
(function (arg0, arg1) {
// code
})('arg', 1);
const lemonPie = {
radius: 9,
cook: (function () {
const type = 'lemon';
return function() {
console.log('#cook', type, this.radius);
};
})(),
};
scope: static (lexical) vs dynamic
static (lexical) scope - if only by looking at the source code one can determine in which environment a binding is resolved
dynamic scope - if a caller defines an activation environment of a callee
JS implements static scope for all except this
static (lexical) scope
const pie = { type: 'lemon', radius: 91 };
function cookPie() {
console.log('#cookPie', pie);
}
function cookMyPie() {
const pie = { type: 'choco', radius: 87 };
cookPie();
}
cookMyPie();
what's wrong with this? Or dynamic scope
function cook() {
console.log('#cookPie', this.type, this.radius);
}
const lemonPie = { cook, type: 'lemon', radius: 46 };
const chocoPie = { cook, type: 'choco', radius: 44 };
lemonPie.cook();
chocoPie.cook();
fat arrow functions
(...args) => {
return args;
}
() => {} this
const cook = () => {
console.log('#cookPie', this.type, this.radius);
}
const lemonPie = { cook, type: 'lemon', radius: 46 };
const chocoPie = { cook, type: 'choco', radius: 44 };
lemonPie.cook();
chocoPie.cook();
const lemonPie = {
type: 'lemon',
radius: 46,
cook: function() {
const fatCook = () => { console.log('#fatCookPie', this); }
fatCook();
}
};
lemonPie.cook();
() => {} arguments
const cookPie = () => {
console.log('#cookPie', arguments); // ReferenceError: arguments is not defined
}
cookPie('lemon', '55');
const cookPie = (...args) => {
console.log('#cookPie', args);
}
cookPie('lemon', '55');
() => {} ctor
const Pie = (radius, height) => {
this.radius = radius;
this.height = height;
}
const pie = new Pie(20, 5); // TypeError: Pie is not a constructor
() => {} short syntax
const cookPie = (pie) => {
return console.log('#cookPie', pie);
};
const cookPie = pie => console.log('#cookPie', pie);
const cookPie = (pie, factory) => factory.cook(pie);
const cookPie = pie => ({ error: 'Cannot cook 👨🍳' });
() => {} perfect fit
const pies = [
{ type: 'lemon', radius: 1 },
{ type: 'choco', radius: 4 },
{ type: 'lemon', radius: 2 },
];
pies.filter(function (pie) {
return pie.radius > 2;
}).map(function(pie) {
return { type: pie.type, crashed: true };
});
pies
.filter(pie => pie.radius > 2)
.map(pie => ({ type: pie.type, crashed: true }));
closure
A closure is the combination of a function and the lexical environment within which that function was declared
function makeCooker() {
const name = 'Isaac';
return function () {
console.log(name);
};
}
const cooker = makeCooker();
cooker();
function makeCooker(name) {
return function (pie) {
console.log(name, 'is going to cook', pie);
};
}
const cooker = makeCooker('Albert');
cooker('lemon pie');
private members via closure
const lemonPie = (function() {
const type = 'lemon';
let radius = 0;
return {
inc: () => radius += 1,
dec: () => radius -= 1,
rad: () => radius,
};
})();
closure in a loop
function cookPie(index) {
console.log('#cookPie', index);
}
for (var i = 0; i < 5; i++) {
setTimeout(() => {
cookPie(i);
}, i);
}
for (var i = 0; i < 5; i++) {
setTimeout(((index) => {
cookPie(i);
})(i), i);
}
for (var i = 0; i < 5; i++) {
let index = i;
setTimeout(() => {
cookPie(index);
}, i);
}
closure in a loop
for (let i = 0; i < 5; i++) {
setTimeout(() => {
cookPie(index);
}, i);
}
chaining
lemonPie.inc().inc().dec().rad();
const lemonPie = {
type: 'lemon',
radius: 6,
inc: function () {
this.radius += 1;
return this;
},
dec: function () {
this.radius -= 1;
return this;
},
rad: function () {
return this.radius;
},
};
self-overwriting functions
getRadius(); // 2
getRadius(); // 4
getRadius(); // 6
function getRadius() {
let radius = 0;
getRadius = function() {
return radius += 2;
};
return getRadius();
}
singleton
const factory1 = new PieFactory();
const factory2 = new PieFactory();
factory1 === factory2; // true
function PieFactory() {
const instance = this;
PieFactory = function() {
return instance;
}
}
decorator
function cookPie(type, radius, slice) {
console.log('#cookPie', type, radius, slice);
}
function sendStats(func) {
return function(...args) {
console.log('#stats:', args);
return func(...args);
};
}
cookPie = sendStats(cookPie);
cookPie('lemon', 71, true);
decorator memoization
function cookPie(type, radius, slice) {
console.log('#cookPie', type, radius, slice);
return type;
}
function memoize(fun) {
return function(...args) {
const key = JSON.stringify(args);
fun.memory = fun.memory || {};
if (fun.memory[key]) {
return fun.memory[key];
}
return fun.memory[key] = fun(...args);
}
}
const memCookPie = memoize(cookPie);
console.log(memCookPie('lemon', 38, true));
console.log(memCookPie('choco', 38, true));
console.log(memCookPie('lemon', 38, true));
decorator spy
function cookPie(type, radius, slice) {
console.log('#cookPie', type, radius, slice);
}
function spy(fun) {
function spied(...args) {
spied.callCount += 1;
spied.args.push(args);
return fun(...args);
}
spied.callCount = 0;
spied.args = [];
return spied;
}
const spyCookPie = spy(cookPie);
spyCookPie('lemon', 73, true);
spyCookPie('choco', 71, false);
console.log(spyCookPie.callCount, spyCookPie.args);