Week 07, Day 04
What we covered today:
- Underscore Solutions
- Demos
- More Underscore
- Regular Expressions II
Underscore.js
Important links for these slides
What is it?
"Underscore is a JavaScript library that provides a whole mess of useful functional programming helpers"
"It’s the answer to the question: “If I sit down in front of a blank HTML page, and want to start being productive immediately, what do I need?"
It's a utility belt, over hundred functions that will help you get stuff done.
Who built it?
Jeremy Ashkenas ( @jashkenas )
He also built:
Why do we teach it?
- It's incredibly useful
- Brings a lot of Ruby's style and functionality across (as does Coffeescript)
- It is a dependency of Backbone (with one small caveat)
- There is another library called
lo-dash
- It is a fork of underscore and is a direct replacement
- There is another library called
- Saves you from repeating code
What's the approach?
- Just like jQuery has the
$
, Underscore uses the_
- All of it's functions are scoped, or nested, within that
_
- Relies heavily on predicate methods - ones that always return
true
orfalse
_.each( /* ... */ );
_.sortBy( /* ... */ );
_.where( /* ... */);
What's the approach?
It breaks the functions down into six categories:
- Collections
- Arrays
- Objects
- Utilities
- Functions
- Chaining
Because there are over a hundred of these things, we aren't going to go through them all.
Let's look at some of them though!
How do we use it?
Reference it just like any other Javascript library!
- Download the file || Get it from the CDN || Or use a gem
- Make sure you reference it before any JS that uses it!
e.g.
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Collections
_.each()
// Works on arrays...
_.each( [1, 2, 3], function (num) {
console.log( num );
});
_.each( [1, 2, 3], console.log );
// Works on objects!
_.each( { one: 1, two: 2, three: 3 }, function ( value, key ) {
console.log( value );
});
Collections
.map(), .reduce()
// Works on arrays...
_.map( [1, 2, 3], function (num) {
return num * 3;
});
// Works on objects... but returns an array
_.map( { one: 1, two: 2, three: 3 }, function ( value, key ) {
return value * 3;
});
_.reduce( [1, 2, 3], function (sum, num) {
return sum + num;
}, 0 ); // => 6
// reduce is an alias for inject
Collections
.where(), .findWhere(), .filter(), .reject(), _.find()
var data = [
{ id: 22, username: "Martin", active: true },
{ id: 23, username: "Max", active: false},
{ id: 24, username: "Linda", active: false}
];
_.where( data, { active: false } ); // => [{ id: 23, ... }, { id: 24, ... }]
_.findWhere( data, { active: false } ); // => { id: 23, ... }
var nums = [ 1, 2, 3, 4, 5, 6 ];
_.filter( nums, function ( num ) {
return num % 2 === 0;
}); // => [ 2, 4, 6 ] - _.find will return the first one of this
_.reject( nums, function ( num ) {
return num % 2 === 0;
}); // => [ 1, 3, 5 ]
Exercise Time!
Collections
_.sortBy(), .groupBy()
var stooges = [
{name: 'moe', age: 40},
{name: 'larry', age: 50},
{name: 'curly', age: 60}
];
_.sortBy(stooges, 'name'); // => [{/* Curly */}, {/* Larry */, {/* Moe */}]
_.groupBy( [1.3, 2.1, 2.4], function(num) {
return Math.floor(num);
}); // => { 1: [1.3], 2: [2.1, 2.4] }
Collections
.every(), .some(), _.contains()
var data = [ 1, 2, 3, 4, 5 ];
_.every( data, function ( num ) {
return num % 2 === 0;
}); // => false
_.some( data, function ( num ) {
return num % 2 === 0;
}); // => true
_.contains( data, 3 ); // => true
Collections
.pluck(), .max(), _.min()
var stooges = [
{name: 'moe', age: 40},
{name: 'larry', age: 50},
{name: 'curly', age: 60}
];
_.pluck( stooges, 'name' ); // => [ 'moe', 'larry', 'curly' ]
_.max( stooges, 'age' ); // => 60
_.min( stooges, 'age' ); // => 40
Collections
.countBy(), .shuffle(), .sample(), .size()
var data = [ 1, 2, 3, 4, 5 ];
_.shuffle( data ); // => [ 3, 2, 5, 4, 1 ] (random every time)
_.size( data ); // => 5
_.sample( data ); // => 3 (random every time)
_.sample( data, 3 ); //=> [ 3, 5, 1 ] (random every time)
_.countBy([1, 2, 3, 4, 5], function(num) {
return num % 2 == 0 ? 'even': 'odd';
}); // => { odd: 3, even: 2 }
Exercise Time!
Arrays
.first(), .last(), .initial(), .rest()
.compact(), .flatten()
_.first()
and_.last()
do exactly what you'd expect_.initial()
returns everything except the last element(s)_.rest()
returns everything except the first element(s)
_.compact([0, 1, false, 2, '', 3]); // => [1, 2, 3]
_.flatten([1, [2], [3, [[4]]]]); // => [1, 2, 3, 4];
_.flatten([1, [2], [3, [[4]]]], true); // => [1, 2, 3, [[4]]];
Arrays
.without(), .union(), .intersection(), .difference(), _.uniq()
_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
// => [2, 3, 4]
_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
// => [1, 2, 3, 101, 10] - all unique items
_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
// => [1, 2] - all items that are present in all arrays
_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
// => [1, 3, 4] - all items that are present in the first array and nowhere else
Arrays
.zip(), .unzip(), _.object()
_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// => [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
_.unzip([['moe', 30, true], ["larry", 40, false], ["curly", 50, false]])
// => [['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]]
_.object(['moe', 'larry', 'curly'], [30, 40, 50]);
// => {moe: 30, larry: 40, curly: 50}
_.object([['moe', 30], ['larry', 40], ['curly', 50]]);
// => {moe: 30, larry: 40, curly: 50}
Arrays
.indexOf(), .lastIndexOf(), .findIndex(), .findLastIndex(), _.sortedIndex()
_.indexOf()
and_.lastIndexOf()
find a value and return it's index (or -1)_.findIndex()
returns the index of the first thing that passes a predicate function
var stooges = [{name: 'moe', age: 40}, {name: 'curly', age: 60}];
_.sortedIndex(stooges, {name: 'larry', age: 50}, 'age'); // => 1
// _.sortedIndex() tells you where you should insert an element
Arrays
_.range()
_.range(10);
// => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_.range(1, 11);
// => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_.range(0, 30, 5);
// => [0, 5, 10, 15, 20, 25]
_.range(0, -10, -1);
// => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
Exercise Time!
Objects
.keys(), .mapObject(), .pairs(), .invert()
var data = { name: "Unknown", location: "Unknown" };
_.keys( data ); // => [ "name", "location" ]
var data = { start: 100, end: 200 };
_.mapObject( data, function (value, key) {
return value * 2;
}); // => { start: 200, end: 400 }
_.pairs(data); // => [["start", 100], ["end", 200]]
_.invert( data ); // => { "100" : "start", "200" : "end" }
Objects
.pick(), .omit(), .defaults(), .has(), .isEqual(), .isMatch()
var data = { name: "N/A", location: "N/A", description: "N/A" };
_.pick( data, "name", "location" ); // => { name: "N/A", location: "N/A" }
_.omit( data, "description" ); // => { name: "N/A", location: "N/A" }
_.defaults( { name: "N/A" }, { drinksWater: true } ); // => { name: "N/A", drinksWater: true }
_.has( { name: "N/A" }, "name" ); // => true
_.isEqual( { name: "N/A" }, { name: "N/A" } );
_.isMatch( { name: "N/A", location: "N/A" }, { location: "N/A" } ); // => true
Objects
_.is**()
- isEmpty
- isElement
- isArray
- isObject
- isArguments
- isFunction
- isString
- isNumber
- isFinite
- isBoolean
- isDate
- isRegExp
- isNaN
- isNull
- isUndefined
Exercise Time!
Utilities
.times(), .random(), .escape(), .unescape()
_.times( 3, function () { console.log( "Hi" ); });
_.random(0, 100); // This is inclusive!
_.escape('Curly, Larry & Moe');
// => "Curly, Larry & Moe"
_.unescape('Curly, Larry & Moe');
// => "Curly, Larry & Moe"
Utilities
.now(), .template()
_.now() // Returns a timestamp of the current time
var templateString = "<p> Hello <%= name %>! </p>";
var template = _.template( templateString );
var compiledTemplate = template( { name: "Jane" } );
Functions
.delay(), .once()
_.delay(function () { console.log("Hi"); }, 1000);
var createApplication = function () {
console.log("Hi");
}
var initialize = _.once(createApplication);
initialize();
initialize();
Functions
.throttle(), .debounce()
Very weird functions.
Throttling enforces a maximum number of times a function can be called. As in execute this function at most once every 100 milliseconds.
Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called.
Use cases:
- Infinite Scroll
- Double Clicks - see here
- Throttling won't filter double clicks
- Debouncing will
See here for a visual representation.
Functions
.throttle(), .debounce()
var throttled = _.throttle(updatePosition, 100);
$(window).scroll( throttled );
var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
Chaining
- Two approaches
_( [1, 2, 3] ).map( function (num) {
return num * 3;
}); // => [ 3, 6, 9 ]
// Only works for one additional method!
_.chain( ["one", "two", "three"] )
.map( function (word) {
return word + " mapped";
})
.map( function (word) {
return word.toUpperCase();
}); // => [ "ONE MAPPED", "TWO MAPPED", "THREE MAPPED" ]
// This approach is much better!
That is Underscore!
Don't expect to remember all of that, but do remember that the documentation for Underscore is really good.
Important thing is, just as with Ruby, always see if a function exists that does what you are trying to do!
Here are the final exercises!
Regular Expressions II
Metacharacters
In regular expressions, there are characters that mean far more than literal characters. This can prove problematic (but there are ways to escape metacharacters), but also the source of some of the most powerful parts.
Character Classes
These are often used to check for capitalized and uncapitalized versions, but can also be used to check for multiple letters. The metacharacters for these are []
.
"bob" =~ /[Bb]ob/ # Returns 0
"Bob" =~ /[Bb]ob/ # Returns 0
"cob" =~ /[Bbc]ob/ # Returns 0
"dog" =~ /[Bb]ob/ # Returns nil
For this sort of stuff, we can also use ranges. These are quite useful.
"A" =~ /[A-Z]/ # Returns 0
"a" =~ /[A-Z]/ # Returns nil
"a" =~ /[A-z]/ # Returns 0
There are lots of inbuilt ranges, and these are really useful to know. Some of them are...
\s
- Will match any space characters (spaces, new lines etc.)\S
- Anything other than space characters\w
- Will match any word character (i.e. actual letters or numbers)\W
- Will match any non-word character
"Wolf" =~ /[\w]/ # Returns 0
"Wolf" =~ /[\W]/ # Returns nil
"Wolf " =~ /[\W]/ # Returns 4
"Wolf " =~ /[\s]/ # Returns 4
"Wolf " =~ /[\S]/ # Returns 0
We can obviously add lots and lots of things between those square brackets. But there are other ways we can do this as well. We can use ()
and |
to check for multiple things.
"Jane" =~ /(Jane|Serge)/ # Returns 0
"Serge" =~ /(Jane|Serge)/ # Returns 0
The round brackets are metacharacters in Regexp as well! They are a way to say this or this (or this or this or this). The pipe is the delimiter for saying "or".
There are a lot more metacharacters. The .
, for example, is a wildcard, it will match anything.
"jane" =~ /.ane/ # Returns 0
"zane" =~ /.ane/ # Returns 0
"Serge and Jane" =~ /.ane/ # Returns 10
This is still relatively hardcoded though, we need to specify where the characters are and what they are. To help solve this problem, there are quantifiers.
Quantifiers
Quantifiers are a way to check in Regexp whether things exist, or exist more than once etc.
The ones that you will actually use:
+
means one or more?
means one or zero*
means zero or more
It can look like the following:
"Hi there" =~ /i+/ # Returns 1
"Hi there" =~ /the?/ # Returns 3
"Hi theeere" =~ /the*/ # Returns 3
These are regularly used for existence checks.
Capturers
These are difficult to understand. Basically, you match something in parentheses and can refer back to them. You capture something by using round brackets, and refer back to them using a \
and an integer (that mimics the order of capture).
"WolfWolf" =~ /(....)\1/
# Matches any four characters, but then needs to have the same four characters straight after. This returns zero.
"ArcticWolf ArcticWolf" =~ /(......)(....) \1\2/
# Matches "Arctic" in the first brackets, then "Wolf" in the second brackets. "Arctic" is saved as \1 and "Wolf" is saved as \2
Have a crack at these exercises.
A few resources related to Regular Expressions...
- Ruby Docs
- MDN
- Regex One
- Learn Code the Hard Way
- Regex Crossword
- Rubular
- Twitter stuff
- https://gist.github.com/wofockham/11bc121e3fef5b0075b3
- See this Gist
Other things you might like to look at: