Understanding By Example
Functions:
# Function Context Cheatsheet
## Types of Invocation
- Applies to only **named** and **unnamed** functions
- **Doesn't matter** for fat arrow functions
### Function-style invocation
- Context of the function will be global context **UNLESS** binded
### Method-style invocation
- Context of the function will be the object which the function is called on **UNLESS** binded
```javascript
const obj = {
name: 'Example Object',
unnamedFunc: function() {
console.log(this.name);
}
};
// Method-style invocation
obj.unnamedFunc(); // 'Example Object'
// Function-style invocation
const unnamedFunc = obj.unnamedFunc;
unnamedFunc(); // Global context
Types of Functions
Named Function
explicit
return
keyword requiredcurly braces
{}
around function bodyNOT saved to a variable
parameters must be surrounded by parentheses
()
context is defined by how it's invoked or called
function-style: global context
method-style: context is object that function is being called on
calling
bind
on the function will return a function binded to the context of thebind
argument
function namedFunc(params) {
return 'named function'
}
// bindedNamedFunc will have the context of obj
const bindedNamedFunc = namedFunc.bind(obj);
Unnamed Function
explicit
return
keyword requiredcurly braces
{}
around function bodyMUST be saved to a variable
parameters must be surrounded by parentheses
()
context is defined by how it's invoked or called
function-style: global context
method-style: context is object that function is being called on
calling
bind
on the function will return a function binded to the context of thebind
argument
const unnamedFunc = function(params) {
return 'unnamed function'
}
// bindedUnnamedFunc will have the context of obj
const bindedUnnamedFunc = unnamedFunc.bind(obj);
Explicit Fat Arrow Function
explicit
return
keyword requiredcurly braces
{}
around function bodyMUST be saved to a variable
parameters must be surrounded by parentheses
()
IF more than one parametertakes the context of where it's defined
CANNOT be binded using
bind
const explicitFatArrow = params => {
return 'explicit fat arrow function'
};
Implicit Fat Arrow Function
NO
return
keywordfunction body can only consist of what is being returned
Optional parentheses
()
around function bodyNOTE: Parentheses needs to be used if returning an object
ex: ({ key: value })
MUST be saved to a variable
parameters must be surrounded by parentheses
()
IF more than one parametertakes the context of where it's defined
CANNOT be binded using
bind
const implicitFatArrow = (params) => 'implicit fat arrow function';
const implicitFatArrow = (params) => ('implicit fat arrow function');
const implicitFatArrow = (params) => ({
function: 'implicit fat arrow'
});
Bind
bind
accepts multiple argumentsfirst argument is the context that you want to bind the function to
any arguments that come afterwards will be passed in when the bound function is called BEFORE the call time arguments
```
// Arrow Functions
// 1. Syntax
// 2. Scoping with Arrow Functions
function logger(title, body) {
console.log("\x1b[31m%s\x1b[0m", title);
console.log(' ', body);
}
// Named Function
function sayHelloNamed(name) {
return 'hello ' + name;
}
logger('Named Function', sayHelloNamed('Justin'));
// Unnamed Function
const sayHelloUnnamed = function(name) {
return 'hello ' + name;
};
logger('Unnamed Function', sayHelloUnnamed('Soon-Mi'));
// Fat Arrow Function with EXPLICIT return
// curly braces around body of fat arrow functions need explicit return keyword
// const sayHelloExplicit = (name) => {
// return 'hello ' + name;
// };
// No parentheses surrounding parameter needed if ONLY one parameter
const sayHelloExplicit = name => {
return 'hello ' + name;
};
logger('Fat Arrow Function with Explicit Return', sayHelloExplicit('Gordon'));
// Fat Arrow Function with IMPLICIT return
// Also called a One-Liner Fat Arrow function
// should only use this when the function's body is NOT multi-line
const sayHelloImplicit = (name) => 'hello' + name;
// can use parentheses around function body as well
// const sayHelloImplicit = (name) => ({
// hello: 'Angela'
// });
// notice the semicolon ; at the end
logger('Fat Arrow Function with Implicit Return', sayHelloImplicit('Angela'));
console.log('\n--------------\n');
// Example using fat arrow functions
const arr = [1, 2, 3];
const newArr = arr.map(el => {
return el + 2;
});
// const newArr = arr.map(function(el) {
// return el + 2;
// });
logger('Original Array, `arr`', arr);
logger('New Mapped Array, `newArr`', newArr);
console.log('\n--------------\n');
// Context using Fat Arrow functions
const pony = {
name: 'Lucy',
wrappedSayName: function() {
console.log(this.name);
return function() {
console.log(this.name);
console.log('Hello my name is ' + this.name);
}
},
wrappedArrowSayName: function() {
console.log(this.name);
return () => {
console.log('Hello my name is ' + this.name);
}
}
};
pony.wrappedSayName = pony.wrappedSayName.bind(pony);
let wrap = pony.wrappedSayName().bind(pony); // method-style invocation
wrap(); // function-style invocation
console.log('-----------');
wrap = pony.wrappedArrowSayName(); // method-style invocation
wrap();
console.log('-----------');
const arrowSayName = pony.wrappedArrowSayName; // not invoking
wrap = arrowSayName(); // function-style invocation
wrap();
// bound to the context of wherever it's defined
console.log('-----------');
const zoomMeeting = {
students: ['Christian', 'Ronald','Wren'],
listStudent: function(studentName) {
console.log(this.students);
// console.log(studentName);
},
listStudents: function() {
const listStudent = this.listStudent;
listStudent(); // function-style
console.log('***');
this.students.forEach(listStudent);
}
};
zoomMeeting.listStudents();
Scope:
// Global Scope
let cities = ["NYC", "SF"];
//console.log(cities);
// Local / Function Scope
function sayThings() {
// let cities = ['Bogota', 'Madrid'];
// console.log(cities);
let word = 'dinosaur';
console.log(word);
}
// console.log(cities); // prints NYC & SF
// console.log(word); // ReferenceError
//sayThings();
// Block Scope
if (true) {
let cats = ['Roma', 'Luigi'];
//console.log(cats); // prints Roma & Luigi
}
//console.log(cats) // ReferenceError
// This example was extended to show how scope within nested blocks works
// As Justin pointed out, it also leads directly into closures
let carrot = 'snake';
if (true){
let carrot = 'doggie!';
//console.log(carrot);
if (true) {
carrot = 'carrot';
console.log(carrot); // prints carrot
}
for(let i = 1; i < 5; i++){
console.log(`i is ${i}`);
};
//console.log(`i is ${i}`); // reference error
console.log("line 57:" ,'george' ,carrot);
}
console.log(carrot); // prints snake
Closure
// Closure
// When an inner function uses, or changes,
// variables defined in an outer scope.
// NOT for declaring a variable of the same name in an inner scope.
function sayHi() {
let name = 'Bryan Guner';
function greeting() {
// here greeting function closes over, or captures, the name variable
// to read it's value
return "Hi there, " + name + "!";
}
// Here, we return the return value of the greeting function
return greeting();
}
// console.log(sayHi());
function nameAndCity() {
let person = {
name: 'Sergey',
city: 'Moscow'
};
function changeCity() {
// here changeCity function closes over the person variable
// and reassigns a value on an existing key
person.city = 'Toronto';
}
changeCity();
// the person variable will show the changes from the changeCity function
return person;
}
// console.log(nameAndCity());
function smoothieMaker() {
let ingredients = [];
function addIngredient( ingredient ) {
// Here addIngredient function closes over the ingredients variable
// to push new elements into the ingredients variable.
// We have created a private state where we cannot access
// the ingredients array from the outside and can only access
// the variable from the inner function.
ingredients.push( ingredient );
return ingredients;
}
// Here the return value for smoothiemaker is the return value
// is the function addIngredient, NOT addIngredient's return value
return addIngredient;
}
// Here we initialize we return a new addIngredient function
// which has closed over the ingredients array
const makeSmoothie = smoothieMaker();
console.log( makeSmoothie );
console.log( makeSmoothie( 'spinach' ) ); // prints [ spinach ]
console.log( makeSmoothie( 'turmeric' ) ); // prints [ spinach, turmeric ]
// let mySmoothie = makeSmoothie();
// Here we return a new and different addIngredient function
// which has closed over a new a different ingredients array
const makeSmoothie2 = smoothieMaker();
console.log( makeSmoothie2( 'kale' ) ); // prints [ kale ] -- does not include spinach and turmeric
function createCounter() {
let count = 0;
return function () {
count++;
return count;
}
}
let counter1 = createCounter();
let counter2 = createCounter();
// console.log(counter1());
// console.log(counter1());
// console.log(counter1());
// console.log(counter1());
// What will this print out?
// console.log(counter2());
// Brief talk of scope and redeclaring a const variable in a loop
for ( let i = 0; i < 5; i++ ) {
const num = i + 2;
}
// In the following examples we will predict what will
// be printed to the terminal
// 1
function dinerBreakfast(food) {
let order = "I'd like cheesy scrambled eggs and ";
function finishOrder() {
return order + food;
}
return finishOrder();
}
// console.log(dinerBreakfast('green tea'));
// 2
function dinerBreakfast2(food) {
let order = "I'd like a(n) " + food;
function withEggs() {
order = order + ' and cheesy scrambled eggs, please!'
};
withEggs();
return order;
}
// console.log(dinerBreakfast2('avocado toast'));
// 3
function dinerBreakfast3() {
let order = "I'd like cheesy scrambled eggs";
return function (food) {
order = order + " and " + food;
return order;
}
}
let breakfastOrder = dinerBreakfast3();
console.log(breakfastOrder);
console.log(breakfastOrder('cappuccino'));
console.log(breakfastOrder('pancakes'));
Context:
// In the following examples we will predict what will
// be printed to the terminal
function whatIsThis() {
console.log(this);
}
const pony = {
name: "Lucy",
whatIsThis: function () {
console.log(this);
},
sayName: function () {
console.log('Hello my name is ' + this.name);
},
changeName: function (newName) {
this.name = newName;
this.sayName();
}
}
// 1.
// whatIsThis();
// 2.
// pony.whatIsThis();
// 3.
// pony.sayName();
// 4.
// pony.changeName("Layla");
// 5.
// const sayNameFunc = pony.sayName;
// sayNameFunc();
// 6.
// const boundSayName = pony.sayName.bind(pony);
// boundSayName();
// 7.
const bart = {
name: 'Bart'
}
const boundToBart = pony.sayName.bind(bart);
// boundToBart();
// 8.
const changeBartsName = pony.changeName.bind(bart);
changeBartsName('Sergey');
// Context
// What does the keyword 'this' refer to?
// The context is determined by HOW a function is invoked
// Different ways of invoking a function
// function style
// context is set to the global object
// 'this' refers to the global object
// method style
// context is set to the object on which the method is called
// 'this' refers to the object on which the method is called
// How to ensure bind never change the context of a function
// no matter how it is invoked.
// .bind()
// By adding .bind() to the end of a function we set the context
// to equal the argument passed to .bind()
// 'this' will refer to the argument passed to .bind()
// Scope VS Context
// VERY DIFFERENT THINGS!!
// Scope:
// Availability of variables at a line in your application
// Context:
// The value of this
// Determined by how a function has been invoked or the .bind() method
const cat = {
name: 'Luigi',
age: 2,
whatIsThis: function () {
console.log(this);
},
nameAndAge: function () {
console.log(this.name + " is " + this.age + " years old.")
}
};
// cat.whatIsThis();
// cat.nameAndAge();
const nameAndAgeFunc = cat.nameAndAge;
nameAndAgeFunc();
const boundNameAndAge = cat.nameAndAge.bind(cat);
// boundNameAndAge();
const cat2 = {
name: 'Roma',
age: 3
}
function nameAndAge() {
console.log(this.name + " is " + this.age + " years old.");
}
const dog = {
name: 'Napo',
age: 5
}
const catNameAge = nameAndAge.bind(cat2);
const dogNameAge = nameAndAge.bind(dog);
catNameAge();
dogNameAge();
const obj = {
name: 'Example Object',
unnamedFunc: function() {
console.log(this.name);
}
};
// Method-style invocation
obj.unnamedFunc(); // 'Example Object'
// Function-style invocation
const unnamedFunc = obj.unnamedFunc;
unnamedFunc(); // `undefined` because Global context
console.log('-------------------');
// Unnamed Func
const dog = {
name: 'Digby'
};
const boundFunc = obj.unnamedFunc.bind(dog);
boundFunc(); // Digby
obj.unnamedFunc(); // Example Object
console.log('-------------------');
// Bind Time and Call Time Arguments
// bind time arguments passed in first, then call time arguments
function printAge(...args) {
const age = args[0];
const year = args[1];
console.log(args);
console.log(this.name + ' is ' + age + ' years old. Born in ' + year);
}
const otherArgs = [2005];
const printDigby = printAge.bind(dog, 12, ...otherArgs);
printDigby(2000);
printDigby(2008);
// const printRealDigby = printAge.bind(dog, 4);
// printRealDigby(2002);
Last updated