Function declaration Function declaration is "hoisted", like "var" in variable declarations. Can be called earlier than it is defined.
function showMessage() {
alert( 'Hello everyone!' );
} // note! NO semicolon at the end
showMessage(); // 'Hello everyone!'
Function is blocked scoped and not visible outside.
console.log(fn1()); // "hi"
console.log(fn2()); // fn2 is not a function !!!
function fn1 () { return "hi"; }
{
function fn2 () { return "hi" }
}
Parameters Full article about default parameters can be found here.
function showMessage(text) {
alert(text);
}
showMessage('empty message'); // empty message
// default parameters
function showMessage(text = 'empty message') {
alert(text);
}
showMessage(); // empty message
// default parameters - old fashioned way
function showMessage(text) {
if (text === undefined) {
text = 'empty message';
}
alert(text);
}
showMessage(); // empty message
// default parameters - improved
function showMessage(text) {
text = text || 'empty message';
alert(text);
}
showMessage(); // empty message
Return Function stops at return
function checkAge(age) {
if (age >= 18) {
return 'adult'; // function stops after "return"
} else {
return 'underaged';
}
}
checkAge(20) // 'adult'
Call without parenthesis Shows string representation of the source code
function xxx() {
let a = 1+1;
}
console.log(xxx); // xxx() {let a = 1+1;}
Copy function
function sayHi() { alert( "Hello" ); }
let func = sayHi;
sayHi === func // true
func(); // Hello
sayHi(); // Hello
Callback It is a function passed to another function as an argument and will be called back later. Simple callback function example.
function func(cb) {
alert('in 1 sec callback func will be triggered')
setTimeout(() => cb(), 1000)
}
const msg = () => alert('callback func is triggered')
func(msg)
Another example.
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
ask(
"Do you agree?",
function showOk() { alert("You agreed."); },
function showCancel() { alert("You canceled the execution."); }
);
// if we extract functions from arguments we may just use function names
ask("Do you agree?", showOk, showCancel);
Recursion Recursion is when a function calls itself. JavaScript engine allows 10000 calls maximum.
function sumToRec(num) {
if (num == 1) return num
if (num > 1) return num + sumToRec(num - 1)
}
sumToRec(4) // 1 + 2 + 3 + 4 = 10
Arguments Functions have special array-like object named 'arguments' that contains all arguments by their index.
function args() {
return( [arguments.length, arguments[0],arguments[1]] );
}
args("Julius", "Caesar") // [2, 'Julius', 'Caesar']
args("Ilya") // [1, 'Ilya', undefined]
Immediately - invoked function expressions IIFE requires semicolon before They don’t pollute the global object
(function() {
alert("Parentheses around the function");
})();
(function() {
alert("Parentheses around the whole thing");
}());
!function() {
alert("Bitwise NOT operator starts the expression");
}();
+function() {
alert("Unary plus starts the expression");
}();
Function is object .name property
function sayHi() {}
sayHi.name; // "sayHi"
.length property
function func(a, b, c, d) {}
function many(a, b, ...more) {}
func.length // 4
many.length // 2 // rest parameters are not counted
Custom properties in function
function sayHi() {
return("Hi");
}
sayHi.wife = ' Kate' // initial value
sayHi() + sayHi.wife // 'Hi Kate'
Function expression
let myFunc = function(name) { alert( `Hello, ${name}` ); };
Named Function Expression Allows the function to reference itself internally
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // use func to re-call itself
}
};
sayHi(); // Hello, Guest
Function declaration vs expression Hoisting, function expression is created when the execution reaches it and is usable only from that moment (not "hoisted")
sayHi1("John"); // Hello, John
function sayHi1(name) { alert( `Hello, ${name}` ); }
sayHi2("John"); // error!
let sayHi2 = function(name) { alert( `Hello, ${name}` ); };
Function expressions can have a conditional declaration
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); };
welcome(); // "Greetings!"
Arrow function expression Convenient for simple one-line actions.
// the curly braces are needed for a multiline function
// if we use curly braces, then we need an explicit "return"
let sum = (a, b) => {
let result = a + b;
return result;
};
// same, but shorter
let sum = (a, b) => a + b;
// same, but even shorter
let sum = function(a, b) {return a + b;};
// with a single argument
let double = n => n * 2; // no parentheses
// w/o arguments
let sayHi = () => alert("Hello"); // parentheses are needed
// arrow functions works same way as function expressions,
// for ex. we can dynamically create functions
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
() => alert('Hello') :
() => alert("Greetings!");
welcome();
Don’t have own "this" & “arguments”. They are taken from outer LE.
let group = {
title: "Our Group",
students: ["John", "Pete", "Alice"],
showList() {
this.students.forEach(
student => alert(this.title + ': ' + student)
)
}
}
group.showList();
// Our Group: John // Our Group: Pete // Our Group: Alice
function xxx() {
(() => console.log(arguments))()
}
xxx(1, 2, 3)
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
new Function Function is created from a string & have access only to global, but not LE
let sum = new Function('a', 'b', 'c', 'return a + b');
sum(1, 2, 3); // 6
Decorators(call / apply / bind) Function that takes another function and alters its behavior, the main job is still carried out by the function .call() Method calls a function with a given "this" and arguments provided individually
// func.call([thisArg], [arg1, arg2, argN])
// The call() allows for a function/method belonging to one object to be assigned and called for a different object.
// With call(), you can write a method once and then inherit it in another object, w/o having to rewrite the method for the new object.
// thisArg - maybe null or undefined
function say(phrase) { alert(this.name + ' ' + phrase) }
let user = { name: "John" }
say.call( user, "hello" ) // John: Hello
// user becomes "this", and "Hello" becomes the "argument"
.apply() Method calls a function with a given "this" value, and arguments provided as an array
//apply(thisArg, [argsArray])
// difference from call() is a list of arguments taken as an array-like object
// Instead of func.call(this, ...arguments) we could use func.apply(this, arguments).
// func.call and func.apply are almost the same
// thisArg - maybe null or undefined
// func.call(context, ...args);
// same as
// func.apply(context, args);
const numbers = [5, 6, 2, 3, 7]
console.log(Math.max.apply(null, numbers)) //7
.bind() Method creates a new function with "this" keyword set to the provided value & with a given sequence of arguments
let user = {
firstName: "John",
sayHi() { alert('Hello ' + this.firstName) }
};
user.sayHi() // 'Hello, John'
setTimeout(user.sayHi, 1000); // Hello, undefined
// setTimeout in-browser is a little special: it sets this=window for the function call
let sayHi = user.sayHi.bind(user);
sayHi(); // 'Hello, John'
setTimeout(sayHi, 1000); // 'Hello, John'
// bind only arguments
function mul(a, b) {return a * b}
let double = mul.bind(null, 2);
// 2 as the first argument. Further arguments are passed “as is”
double(3) // = mul(2, 3) = 6
double(4) // = mul(2, 4) = 8
double(5) // = mul(2, 5) = 10
// bound function can not be re-bound
Lexical Environment Execution Context (EC, stack, call stack) is the internal JS engine to track execution of a function or the global code EC tracks where statement of the corresponding function is being executed New EC is created and pushed to the stack when execution of a function begins and deleted when stops For every EC a corresponding LE is created Lexical Environment (LE) is the internal JS engine that holds names of variables/functions & a reference to a parent LE Running function, block {} , and the script as a whole have an internal hidden object [[Environment]] with reference to its LE Every function tracks the LE related to the EC it was created in & its parent LE [[Environment]] stores all local variables & 'this' & other...& a reference to outer LE With [[Environment]] a function remembers where it was born Variable is a property of [[Environment]] object, associated with the executing block/function/script Variable change means a change of [[Environment]] property object On function start LE is pre-populated with variables, but their state is uninitialized Engine knows about variables, but cannot be referenced until it has been declared with with let , const When let definition appears, variable's value is undefined Variable goes to LE , but get initialized in code execution flow Function declaration is instantly fully initialized unlike function expressions LE is a specification object & exists “theoretically”, we can’t get this object in our code When the code accesses a variable the JS searches it in LE chain up to the global one LE is removed from memory with all the variables after the function call finishes But not in case of nested functions. LE object dies when it becomes unreachable (just like other objects) Closure Closure is a function that remembers its outer variables and can access them Functions in JavaScript are closures In some languages, that’s not possible Closures remember where they were created using a hidden [[Environment]] property Their code can access outer variables The new Function syntax is not a closure Function can be returned and then used somewhere else and no matter where, it still has access to the same outer variables Lexical environment is created on function initialization & it is separate for every function
function makeCounter() {
let count = 0
return function() {
return count++
}
}
let counter = makeCounter()
let counter2 = makeCounter()
counter() // 0
counter() // 1
counter2() // 0
counter2() // 1
Currying Currying is a transformation of func(a, b, c) into func(a)(b)(c) Simple example
function curry(f) {
return function(a) {
return function(b) {
return f(a, b)
}
}
}
function sum(a, b) {
return a + b
}
let curriedSum = curry(sum)
curriedSum(1)(2) // 3
Advanced example
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) return func.apply(this, args)
// Function.length - number of parameters expected by the function
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
function sum(a, b, c) {
return a + b + c
}
let curriedSum = curry(sum)
alert( curriedSum(1, 2, 3) ) // 6, still callable normally
alert( curriedSum(1)(2, 3) ) // 6, currying of 1st arg
alert( curriedSum(1)(2)(3) ) // 6, full currying
Maybe better to use _.curry function from Lodash.