Week 07, Day 04

What we covered today:

  • Underscore Solutions
  • Demos
  • More Underscore
  • Regular Expressions II

Here are all the slides.

Underscore.js

What is it?

Homepage

"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?

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
  • 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 or false
_.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!

Here they are!

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!

Here they are!

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!

Here they are!

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!

Here they are!

Utilities

.times(), .random(), .escape(), .unescape()

_.times( 3, function () { console.log( "Hi" ); });

_.random(0, 100); // This is inclusive!

_.escape('Curly, Larry & Moe');
// => "Curly, Larry &amp; Moe"

_.unescape('Curly, Larry &amp; 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...

Other things you might like to look at: