What's abstraction again? →
Abstraction is the process of hiding away necessary, yet immaterial details so that the programmer can focus on solving the actual higher-level problem…
What are some features in JavaScript that facilitate abstraction? →
Greeeaaaat. What's a higher order function, though? →
A higher order function is a function that does at least one of the following things: →
We learned about (and even re-implemented) four array methods that accepted functions as arguments (specifically as callback functions). What were these array methods, and what do they do? →
An array's forEach method executes a callback on every array element.
// log double the value of every element in the array
const numbers = [3, 4, 5];
numbers.forEach(function(element) {
console.log(element);
});
Also, our homegrown implementation as a standalone function.
function forEach(arr, action) {
for (let i = 0; i < arr.length; i++) {
action(arr[i]);
}
}
An array's filter method returns a new array of elements that each pass some test/callback.
indexOf
returns the index of a substring in a string… and it returns -1 if the substring is not found)
// only give back the strings that start with 'ba'
const words = ['foo', 'bar', 'baz', 'qux'];
const filtered_words = words.filter(function(word) {
return word.indexOf('ba') !== -1;
});
console.log(filtered_words);
We implemented filter
as a standalone function as well. The algorithm was fairly compact; what was it? →
And the implementation… →
function filter(arr, test) {
const filtered = [];
arr.forEach(function(element) {
if(test(element)) {
filtered.push(element)
}
});
return filtered;
}
Create a function called map that creates a new array based on calling a function on each element of an array passed in: →
What would the algorithm be?→
Here's a potential implementation, along with example usage, of our own map
implementation. →
function map(arr, transform) {
const transformed = [];
arr.forEach(function(element) {
transformed.push(transform(element));
});
return transformed;
}
const result = map(['hello', 'hey', 'hi'], function(greeting) {return greeting + '!!'});
console.log(result);
Again, JavaScript arrays already have a map method…
Try using it to change every word in the list ['hey','yo','sup']
to uppercase with an exclamation point. →
words = ['hey', 'yo', 'sup']
const shoutedWords = words.map(function(word) {
return word.toUpperCase() + '!';
});
console.log(shoutedWords);
Create a function called reduce… that repeatedly calls a function to reduce an array to a single value. →
// result is 20
console.log(reduce([4, 12, 5], function(accum, ele) {
return accum + ele;
}, 0));
What do you think the algorithm for reduce would look like? →
function reduce(arr, combine, start) {
const accum = start;
arr.forEach(function(ele){
accum = combine(accum, ele);
});
return accum;
}
console.log(reduce([4, 12, 5], function(accum, ele) {
return accum + ele;
}, 0));
Here's an example of finding the minimum (uses first element as initial min) with reduce:
const numbers = [-5, -2, -1, -10, -3];
console.log(reduce(numbers, function(accum, ele) {
if(accum < ele) {
return accum;
} else {
return ele;
}
}, numbers[0]));
JavaScript arrays have a built-in reduce method. (Note that in other functional languages, reduce is sometimes called fold.)
Try using it to calculate the product of all of the elements in [2, 5, 4, 3,]
. →
[2, 5, 4, 3,].reduce(function(product, currentNumber ){
return product * currentNumber;
}, 1);
Using forEach
, filter
, map
, and/or reduce
, can you try to count all of the face cards in the following array? →
const cards = [{'suit':'♦', 'face':'4'},
{'suit':'♠', 'face':'J'},
{'suit':'♠', 'face':'Q'},
{'suit':'♣', 'face':'Q'},
{'suit':'♠', 'face':'2'},
{'suit':'♦', 'face':'7'},
{'suit':'♥', 'face':'K'}];
Let's try the most convential way… together.
let count = 0;
cards.forEach(function(card) {
if (['K', 'Q', 'J'].indexOf(card.face) !== -1) {
count += 1;
}
});
console.log(count);
Now write your own versions using the following two methods… →
Array
's filter
methodArray
's reduce
methodTry using all 3 methods to run an analysis on some stats from game 5 of the 2014 NBA finals. →
"team"
property"FGM"
) divided by field goals attempted ("FGA"
)
const players = [
{"lastName":"Duncan", "team":"Spurs", "FGM":5, "FGA":10},
{"lastName":"Parker", "team":"Spurs", "FGM":7, "FGA":18},
{"lastName":"Ginobili", "team":"Spurs", "FGM":6, "FGA":11},
{"lastName":"James", "team":"Heat", "FGM":10, "FGA":21},
{"lastName":"Wade", "team":"Heat", "FGM":4, "FGA":12},
{"lastName":"Bosh", "team":"Heat", "FGM":6, "FGA":14}
];
Note that you can continue to chain calls to map
, filter
, and reduce
as long as you get an array back.
Here's a potential solution. There are a few utility functions declared for clarity and readability. →
function heat(player) { return player.team === 'Heat'; }
function spurs(player) { return player.team === 'Spurs'; }
function shootingPercentage(player) { return player.FGM / player.FGA; }
function sum(curTotal, num) { return curTotal + num}
function average(arr) { return arr.reduce(sum, 0) / arr.length; }
console.log(average(players.filter(heat).map(shootingPercentage)).toFixed(2));
console.log(average(players.filter(spurs).map(shootingPercentage)).toFixed(2));
Seems to map more closely to what we're actually trying to do rather than creating a bunch of for loops.
Maybe having a function that checks against a hardcoded string doesn't sit well with you. Admittedly, heat(player)
and spurs(player)
do seem like throw away functions.
Is there a way that we can replace both of those functions with one? →
function inTeam(teamName) {
return function(player) { return player.team === teamName; }
}
console.log(average(players.filter(inTeam('Heat')).map(shootingPercentage)).toFixed(2));
console.log(average(players.filter(inTeam('Spurs')).map(shootingPercentage)).toFixed(2));
A quick side note: that guy Ginobili is an Argentinian basketball player that once hit a flying bat out of the air during a game.
(BTW, I don't condone hurting animals, but I do love animated gifs a lot)
We make that trade-off all the time, which is why we're not writing with super-fast machine code, but with slower high-level languages, like JavaScript or Python.
for
loop will likely outperform forEach
, map
, filter
and reduce
For now, we'll use arrow functions as: →
const nums = [1, 2, 3, 4, 5];
console.log(nums.filter(x => x % 2 === 0));
const isEven = (x) => {return x % 2 === 0;};
this
value within its body is the same as this
in the scope that it was created in (this will make more sense when we discuss this
!)Continuing on with the notion that functions are just values or objects… do you think functions also have properties and methods?
Why yes - functions have both properties and methods! For example:
const f = function(x, y, z) {
return x - y + z;
}
console.log(f.length);
Let's check out some methods that you can call on function objects:
bind
apply
call
So, what do these methods do?
this
and individual argumentsthis
and array as argumentsthis
, and optionally with set argument values
(we'll talk about this in a moment)
Both call
and apply
immediately execute the function that they're called on.
call
passes each argument individually, while apply
passes an array)this
to null - we'll see more about this
later
function areaTriangle(base, height) {
return (1 / 2 ) * base * height;
}
const result1 = areaTriangle.call(null, 10, 20);
console.log(result1);
const result2 = areaTriangle.apply(null, [10, 20]);
console.log(result2);
Unlike the previous methods, bind
doesn't execute the function that it's called on. Instead, bind
:
this
argument
It's actually an implementation of partial application…
Fixing the first parameter, called base
, of our function.
const areaTriangleBase100 = areaTriangle.bind(null, 100);
// call with only one argument now
console.log(areaTriangleBase100(3));
Note that you'll also see bind
used to fix/set a function or method's this
.
Hey… so remember the rest operator, ...args
? What was it? →
...
before it (the rest
operator)Array
function f(a, b, ...args) { console.log(args); }
f('foo', 'bar', 'baz', 'qux'); // prints out ['baz', 'qux']
An operator that shares the same syntax but does the opposite of the rest operator is the spread operator.
f(...someArray)
Array
literals: [1, 2, ...someArray]
The parameters for res are value and radix… and in this case, we're able to expand the incoming Array to fit the arguments by using the spread operator:
const stuff = ['101', 2];
const res = parseInt(...stuff);
console.log(res);
parseInt
, the second becomes the last argumentArray
, JavaScript will behave as if there were too few or too many argumentsWhat does the following code print out? →
const words = ['foo', 'bar'];
function logThreeThings(a, b, c) {
console.log(a, b, c);
}
logThreeThings(...words);
foo bar undefined
The spread operator can also be used to expand Arrays within Array literals:
const words = ['foo', 'bar', 'baz'];
const moreWords = ['start', ...words, 'end']
console.log(moreWords);
// [ 'start', 'foo', 'bar', 'baz', 'end']
You can also use the spread operator to make a shallow copy of an Array:
const arrs = [[1, 2], [3, 4]];
const newArrs = [...arrs];
console.log(newArrs); // yay copied!
arrs[0].push('surprise!');
console.log(newArrs); // beware, shallow!
How about creating a function that logs how much time a function takes to execute? →
logExecutionTime
, that takes a function, f
, as an argumentf
that logs how much time it took to execute (in addition to doing whatever it usually does as a function, of course!)console.time
and console.timeEnd
to take the timingsHere's one possible way to do it. →
function logExecutionTime(f) {
return function(arg) {
console.time('function timing');
const val = f(arg);
console.timeEnd('function timing');
return val;
};
}
function wasteTime(limit) { for(let i=0;i < limit; i++) { }}
wasteTime = logExecutionTime(wasteTime);
wasteTime(5000000);
Hm. So… there's a limitation with regards to the kinds of functions that we can time. Can you spot it? →
Hint: How would it handle the following function? →
function wasteTime2(start, limit) {
for(let i = start; i < limit; i++) { }
}
What if the timed function needs more than one argument? →
function logExecutionTime(f) {
return function(...args) {
console.time('function timing');
// use spread and rest
val = f(...args);
// old way with es5, with apply
// const val = f.apply(null, arguments);
console.timeEnd('function timing');
return val;
};
}
wasteTime = logExecutionTime(wasteTime);
wasteTime(-5000000, 5000000);
A function that accepts another function as a parameter, wraps its call in another function, and returns that wrapper as a new function… is called a function decorator.
Why might function decorators be a useful? When would it make sense to implement a decorator rather than to modify an actual function? →
Decorators might come in handy whenever you want to do something before or after a function runs. →
We just saw a bunch of higher order functions in JavaScript… and we even created our own.