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.