Repository navigation
Functions
JavaScript makes this love clear from our first encounter with functions. It makes functions first-class data. Functions, that is to say, "frozen" work, can be passed around like "primitive" data (Strings or Integers, etc.). JavaScript passes around its functions, shoves them to Arrays, makes them accessible via a key in an Object, and more. Few other programming languages share this point of view.
When a function in JavaScript is called, it is provided an execution context.
When JavaScript functions run, they have an associated JavaScript Object that goes along with them which they can access by the keyword this which is the Object that represents the function's execution context.
The execution context is a JavaScript Object that is either implicitly or explicitly passed at the time of the function's call.
The implicit way of passing a context with a function is something we have to memorize and accept as part of the nature of JavaScript.
The tools for explicitly passing a context at function call-time are the methods call, apply, and bind.
// examples
Calculator.sum.call(multilingualMessages, 1, 2)
Calculator.sum.apply(multilingualMessages, [1, 2])
sayHello.bind(greenFrog)("Hello") //=> "Mr. GreenFrog says *Hello* to you all."- Execution context is set at function call-time, implicitly or explicitly.
- In "bare" function calls, the context is automatically set to the global object unless prevented by "use strict".
- In "non-bare" function calls, the context is automatically set to the "object to the left of the dot."
- (For Object-Oriented JavaScript) Execution context defaults to the new thing being created in a class's constructor
this returns the current execution context while the function is being run. Whether that context was passed explicitly or implicitly, this returns it.
When a function is called, it gets an execution context passed in. That context will be whatever the function was 'called on' - the object to the left of the . where it's called.
let byronPoodle = {
name: "Byron",
sonicAttack: "ear-rupturing atomic bark",
mostHatedThing: "noises in the apartment hallway",
warn: function() {
console.log(`${this.name} issues an ${this.sonicAttack} when he hears ${this.mostHatedThing}`)
}
}
byronPoodle.warn()
// LOG: Byron issues an ear-rupturing atomic bark when he hears noises in the apartment hallwaylet speak = function() { return `It ain't easy being ${this.name}`}
let frog = { name: "Kermit" }
let pig = { name: "Miss Piggy" }
frog.speak = speak
pig.speak = speak
frog.speak === pig.speak //=> true
frog.speak() //=> "It ain't easy being Kermit"
pig.speak() //=> "It ain't easy being Miss Piggy"The crucial realization is that the context used inside the function speak is defined by what's "left of the dot."
What happens if we invoke a function and it's not defined inside an Object:
let contextReturner = function() {
return this
}
contextReturner() //=> window
contextReturner() === window //=> trueWhen no object is to the left of the function, JavaScript invisibly adds the global object.
In browser-based JavaScript environment (or "JavaScript runtime"), the global object is called window. In NodeJS, it's called global.
in JavaScript, if the engine sees the String "use strict" inside a function, it will stop passing the implicit execution context of the global object. If JavaScript sees "use strict" at the top of a JavaScript code file, it will apply this rule (and other strict behaviours) to all functions.
function looseyGoosey() {
return this
}
function noInferringAllowed() {
"use strict"
return this
}
looseyGoosey() === window; //=> true
noInferringAllowed() === undefined //=> trueclass Poodle{
constructor(name, pronoun){
this.name = name;
this.pronoun = pronoun
this.sonicAttack = "ear-rupturing atomic bark"
this.mostHatedThing = "noises in the apartment hallway"
}
warn() {
console.log(`${this.name} issues an ${this.sonicAttack} when ${this.pronoun} hears ${this.mostHatedThing}`)
}
}
let b = new Poodle("Byron", "he")
b.warn() //=> Byron issues an ear-rupturing atomic bark when he hears noises in the apartment hallwayIn the previous lesson, we learned that when we invoke functions JavaScript will make a context available inside the function. That context will be set to "whatever's to the left of the dot" or, when there's nothing to the left of the dot, the global object.
JavaScript provides other means for specifying what we want the context to be. These are the explicit methods of setting execution context: call, apply, and bind.
let asgardianBrothers = [
{
firstName: "Thor",
familyName: "Odinsson"
},
{
firstName: "Loki",
familyName: "Laufeysson-Odinsson"
}
]
function intro(person, line) {
return `${person.firstName} ${person.familyName} says: ${line}`
}
function introWithContext(line){
return `${this.firstName} ${this.familyName} says: ${line}`
}
let phrase = "I like this brown drink very much, bring me another!"
intro(asgardianBrothers[0], phrase) //=> Thor Odinsson says: I like this brown drink very much, bring me another!
intro(asgardianBrothers[0], phrase) === introWithContext.call(asgardianBrothers[0], phrase) //=> true
intro(asgardianBrothers[0], phrase) === introWithContext.apply(asgardianBrothers[0], [phrase]) //=> true
let complaint = "I was falling for thirty minutes!"
intro(asgardianBrothers[1], complaint) === introWithContext.call(asgardianBrothers[1], complaint) //=> true
intro(asgardianBrothers[1], complaint) === introWithContext.apply(asgardianBrothers[1], [complaint]) //=> trueBoth call and apply take a thisArg argument as their first argument (see their documentation for further clarification): that argument becomes the this inside the function.
In the case of call, anything after the thisArg gets passed to the function like arguments inside of a ().
In the case of apply, the contents in the Array get destructured and passed to the function like arguments inside of a ().
this is useful in case we have multiple functions using the same argument:
function createTimeInEvent(employee, dateStamp){ /* */ }
function createTimeOutEvent(employee, dateStamp){ /* */ }
function hoursWorkedOnDate(employee, soughtDate){ /* */ }that argument can become the context (this)
The bind method returns a function that needs to be called, but wherever the function that bind was called on had a this reference, the this is "hard set" to what was passed into bind.
let asgardianBrothers = [
{
firstName: "Thor",
familyName: "Odinsson"
},
{
firstName: "Loki",
familyName: "Laufeysson-Odinsson"
}
]
function introWithContext(line){
return `${this.firstName} ${this.familyName} says: ${line}`
}
let thorIntro = introWithContext.bind(asgardianBrothers[0])
thorIntro("Hi, Jane") //=> Thor Odinsson says: Hi, Jane
thorIntro("I love snakes") //=> Thor Odinsson says: I love snakesExecution context can be locked in a function by invoking bind on it and passing it a thisArg. The bind function makes a copy of the functionality of its function but with all the this stuff locked in place and returns that function. That new function can have arguments passed to it during its call with () as usual.
let configuration = {
frontContent: "Happy Birthday, Odin One-Eye!",
insideContent: "From Asgard to Nifelheim, you're the best all-father ever.\n\nLove,",
closing: {
"Thor": "Admiration, respect, and love",
"Loki": "Your son"
},
signatories: [
"Thor",
"Loki"
]
}
let printCard = function() {
console.log(this.frontContent)
console.log(this.insideContent)
this.signatories.forEach(function(signatory){
let message = `${this.closing[signatory]}, ${signatory}`
console.log(message)
})
}
printCard.call(configuration)
///=> execution:
Happy Birthday, Odin One-Eye!
From Asgard to Nifelheim, you're the best all-father ever.
Love,
/Users/heimdall/git_checkouts/fi/jscontext/unnamed/card.js:20
let message = `${this.closing[signatory]}, ${signatory}`
^
TypeError: Cannot read property 'Thor' of undefined
at /Users/heimdall/git_checkouts/fi/jscontext/unnamed/card.js:20:38
at Array.forEach (<anonymous>)
....Function expressions and declarations inside of other functions do not automatically use the same context as the outer function.
Remember the rules of function invocation. A function defaults to getting the global scope as execution context when it is called without "anything to the left of a dot.
in the forEach function this becomes the Global Context [object window] and not the Object pass from .call().
How to fix the problem:
// not working bit:
this.signatories.forEach(function(signatory){
let message = `${this.closing[signatory]}, ${signatory}`
console.log(message)
})
// can be fixed with a second argument (works with map, reduce and so forth..):
this.signatories.forEach(function(signatory){
let message = `${this.closing[signatory]}, ${signatory}`
console.log(message)
}, this)
// can be fixed by using the .bind method
let contextBoundForEachExpr = function(signatory){
let message = `${this.closing[signatory]}, ${signatory}`
console.log(message)
}.bind(this)
this.signatories.forEach(contextBoundForEachExpr)
// leverage function-level scope and closures
let outerContext = this
this.signatories.forEach(function(signatory){
let message = `${outerContext.closing[signatory]}, ${signatory}`
console.log(message)
})
// use an arrow function
let printCard = function() {
console.log(this.frontContent)
console.log(this.insideContent)
// Wow! Elegant! And notice the arrow function's `this` is the same
// this that printCard has by virtue of configuration being passed
// in as a thisArg
this.signatories.forEach(s => console.log(`${this.closing[s]}, ${s}`)
)
}arrow function is automatically bound to its parent's context and does not create a context of its own. Many programmers think arrow functions are much more predictable since they do not create their own this during execution and instead "absorb" the context of their enclosing environment. Since the whole point of an arrow function is to not have its own execution context, we should not use call, bind, or apply when executing them. Most of the time, you'll see them used like anonymous functions passed as first-class data into another function
function declaration
function foo() {
return 'bar';
}_function expression
let foo = function() {
return 'bar';
}(function() { console.log("I print straight away!") })()Around 2015, developers got tired of typing f-u-n-c-t-i-o-n over and over. They lobbied the ECMAScript body for a way to write functions in a very short way. Here is the result, the "Arrow Function." Using arrow functions often appears when using JavaScript's iterator methods.
The arrow function expression is a very important piece of syntax. While it lets us type less, its most important feature is that it carries its parent's context as its own.
shortcuts:
- If you pass only one argument, you don't have to wrap the single parameter in ()
- If there is only one expression, you don't need to wrap it in {} and the result of that expression is automatically returned.
- Anti-Shortcut: If you DO use {}, you must explicitly return the return value
let add = (parameter1, parameter2) => parameter1 + parameter2
add(2,3) //=> 5
let twoAdder = x => x + 2;
// is the same as
let twoAdder = (x) => x + 2;
let sum = (parameter1, parameter2) => {
console.log(`Adding ${parameter1}`);
console.log(`Adding ${parameter2}`);
return parameter1 + parameter2;
}
sum(1,2) //=> 3rest:
function muppetLab(a, b, ...muppets) {
console.log(a, ' ', b); // Dr. Bunson Beaker
console.log(muppets); // ["Miss Piggy", "Kermit", "Animal"]
console.log(muppets[0]); // Miss Piggy
console.log(muppets.length); // 3
}
muppetLab("Dr. Bunson", "Beaker", "Miss Piggy", "Kermit", "Animal")spread:
function add(a, b, c) {
return a + b + c ;
}
const arr = [1, 2, 3];
add(...arr); // returns 6It's all about context. If the three dots occur when you are calling the function, then it's the spread operator. If they happen when you're defining the function, it's the rest parameter.
IIFE: Instantly-Invoked Function Expression
(function(x){ return x + 3 })(2) //=> 5let fn = function(x){ return x + 3 }(2)
fn //=> 5const Calculator = {
add: function(a,b) {
return a + b;
},
subtract: function(a,b) {
return a - b
},
multiply: function(a,b) {
return a * b
},
divide: function(a,b) {
return a / b;
}
}function outer(greeting, msg="It's a fine day to learn") { // 2
let innerFunction = function(name, lang="Python") { // 3
return `${greeting}, ${name}! ${msg} ${lang}` // 4
}
return innerFunction
}
outer("Hello")("student", "JavaScript") // 1, 5
//=> "Hello, student! It's a fine day to learn JavaScript"Keep in mind, it's not the case that we have to invoke functions like this:
outer("Hello")("student", "JavaScript")We could:
let storedFunction = outer("Hello")
// ... lots of other code
storedFunction("student", "JavaScript")
//=> "Hello, student! It's a fine day to learn JavaScript"let fn = function(multiplier) {
function(fare) { return fare * multiplier }
}
fn(3)(4) //=> 12
let fn_custom = fn(3)
fn_custom(4) //=> 12function sayHello(name="") {
console.log(`Hello${name}`)
}
let sayHola = function(name="") {
console.log(`Hola${name}`)
}
functionUsingCallback(sayHello, sayHola, function(name="") {
console.log(`Ni Hao${name}`)
})
function functionUsingCallback(en, es, zh, name){
en()
es()
zh()
}
/* Prints */
//=> Hello
//=> Hola
//=> Ni Haolet [answer, theBase] = (
function(thingToAdd) {
let base = 3
return [
function() { return base + thingToAdd },
function() { return base }
]
}
)(2)
answer() //=> 5
console.log(`The base was ${theBase()}`)
// OUTPUT: The base was 3const fi = (function() {
return {
libraryMethod: function() {
return 'Start by reading this'
},
each: function(collection, iteratee) {
const newCollection = (collection instanceof Array) ? collection.slice() : Object.values(collection)
for (let idx = 0; idx < newCollection.length; idx++)
iteratee(newCollection[idx])
return collection
},
map: function(collection, iteratee) {
if (!(collection instanceof Array))
collection = Object.values(collection)
const newArr = []
for (let idx = 0; idx < collection.length; idx++)
newArr.push(iteratee(collection[idx]))
return newArr
},
reduce: function(c = [], callback = () => {}, acc) {
let collection = c.slice(0)
if (!acc) {
acc = collection[0]
collection = collection.slice(1)
}
let len = collection.length;
for (let i = 0; i < len; i++) {
acc = callback(acc, collection[i], collection)
}
return acc;
}
}
})()
fi.map(testArr, callback)
fi.reduce(testArr, callback, 10)