[book note] Manning MEAP: JavaScript Next – Unit 1: Variables & Strings

Unit 1: Variables and & Strings

Lesson 1. Declaring Variables with let

In the history of JavaScript, variables have always been declared using the keyword var. ES6 introduces two new ways to declare variables, with the let and const keywords. Both of these work slightly different than variables with var. There are two primary differences with let:

let variables have different scoping rules

let variables have differently when hoisted

Priming Exercise:

for (var i = 0; i < 5; i++) {
  setTimeOut(function () {
    console.log(i);
  }, 1);
};

for (let n = 0; n < 5; n++) {
  setTimeOut(function () {
    console.log(n);
  }, 1);
};

1.1 How Scope Works with let

if (true) {
  let foo = 'bar';
}

console.log(foo);
// An error is thrown because foo does not exist outside the block it was declared in.

This makes variables much more predictable and won’t lead to bugs introduced because the variable leaks outside of the block is used within. A block is the body of a statement or function. It is the area between the opening and closing curly braces, { & }. You can even use curly braces to create a free standing block that isn’t tied to a statement.

There is one exception to that rule though, in a for loop a variable declared with a let inside the for loop’s clause will be in the scope of the for loop’s block:

Why The Block Scope of let is Preferred

Variables declared with var have function scope meaning they can be accessed anywhere in their containing function:

(function() {
  if (true) {
    var foo = 'bar';
  }
  console.log(foo); // foo is being referenced outside of the if it was declared in.
}())

How Hoisting Works with let

Variables declared with let and var both behavior called hoisting. What this means is that within the entire scope of where the variable is declared, being the entire block for let or the entire function for var, the variable consumes the entire scope. This happens no matter where in the scope the variable is declared.

This concept of variables being in scope before they are declared , called hosting, isn’t actually new. let hoists to the top of the block, and var hoists to the top of the function. There is however a more important distinction what happens when a variable declared with let is accessed before it is declared in contrast to var.

When a let variable is accessed in scope before it is declared it throws a reference error. Unlike var, which allows the use but the value will always be undefined. This area or zone in which the let variable can be accessed at a time before it is declared but will throw an error if it actually is, is called a temporal dead zone.

More specifically a temporal dead zone refers to the area in which the variable is in scope before it is declared. Any references to variables in a temporal dead zone will throw a reference error:

1.3 Should I use let Instead of var From Now On?

This is a bit of controversial question.


Lesson 2. Declaring constants with const

The keyword const stands for constant, as in never changing.

2.1 How Constants Work

Constants cannot be reassigned, this means that once you assign the value of a constant, any attempt to assign a new value result in an error.

const myConst = 5;

myConst = 6;

Because of the inability to reassign constants with new values they quickly became confused with being immutable. Constants are not immutable. So what is the difference between not being able to be reassigned and immutable?

  • Assignment has to do with variable bindings, binding a name to a piece of data.
  • Immutability or mutability is a property that belongs to the actual data that the binding contains.

All primitives (strings, numbers, etc.) are immutable while objects are mutable.

const mutableConstant = {};

mutableConstant.foo = 'bar';
mutableConstant.foo = 'baz';

mutableConstant = {foo: 'bat'}

Screen Shot 2017-01-19 at 4.09.38 P

Because we created a constant and assigned a mutable value to it, we were able to mutate the value. However we could not assign a new value to the constant, the only way we were able to change the value is by modifying the value itself.

If you assign an immutable value like a number to a constant, then the constant becomes immutable because it contains a value that cannot be modified and the constant cannot be reassigned so they become frozen.

let a = "Hello";
let b = a;

b += ", World!";

console.log(a);
console.log(b);

Spot Quiz: What will happen when executing the following code?

javascript

const a = "Hello";

const b = a.concat(", World!"); // a remains the same

When to Use Constants

The obvious place to use constants is when creating flags that are more about a unique identifier than the actual value they contain. Here is an example:

const ADD_ITEM = 'ADD_ITEM'
const DEL_ITEM = 'DEL_ITEM'
let items = []

function actionHandler(action) {
  switch (action.type) {
    case ADD_ITEM:
      items.push(action.item)
    break
    case DEL_ITEM:
      items.splice(items.indexOf(action.item), 1)
    break
  }
}

This ensures that the action flags and can never ADD_ITEM and DEL_ITEM be accidentally

changed. You may stop there, but if you think about it, should the items array ever be

reassigned? Probably not, so we can make that a const as well.

const items = []

But what if you later decide that you need an action to empty the list. Your instinct

may be to reassign items to a new empty array but that cannot be done using a constant.

case CLEAR_ALL_ITEMS:
  item = []
break

You can still empty the array though, you just need to figure out a way that modifies the actual value without assigning a new value. In this case we can again use splice for such a task:

case CLEAR_ALL_ITEMS:
  items.splice(0, items.length)
break

OK, so our complete code now looks like this:

const ADD_ITEM = 'ADD_ITEM';
const DEL_ITEM = 'DEL_ITEM';
const CLEAR_ALL_ITEMS = 'CLEAR_ALL_ITEMS';
const items = [];

function actionHandler(action) {
  switch (action.type) {
    case ADD_ITEM:
      items.push(action.item);
    break;
    case DEL_ITEM:
      items.splice(items.indexOf(action.item), 1);
    break;
    case CLEAR_ALL_ITEMS:
      items.splice(0, items.length);
    break;
  };
}

Protecting a binding from reassignment isn’t the only reason to use a constant. Because constants are known to never be reassigned, certain optimizations can be made by the JavaScript engine to improve performance. Because of this, it makes sense to use const anytime a variable never needs to be reassigned and fall back to let when they do.


Lesson 3. New String Methods

In this lesson we are going to cover some of the new methods being added to the string prototype. The methods we will cover are:

  • String.prototype.startsWith
  • String.prototype.endsWith
  • String.prototype.includes
  • String.prototype.repeat
  • String.prototype.padStart
  • String.prototype.padEnd

None of these methods would be extremely difficult to implement but they are tasks that

are used enough to warrant inclusion in the standard library.

3.1 Searching Strings

Problem: Checking if a given string starts with a given value.

With ES6 we can solve this problem in the same self documenting way:

if( price.startsWith('$') ) {
  // ...
}
if( phone.startsWith(user.areaCode) ) {
  // ...
}

Not only are these now more consistent but aren’t they much easier to understand

exactly what they are doing at a glance?

With the additions of the includes, startsWith and endsWith methods, searching

for strings within strings has gotten much simpler. startsWith checks if a string starts

with a specified value and likewise endsWith checks if the string ends with the value.

includes checks the entire string if it contains the specified value.

If your needs are more complex than what is achievable with these methods you can still fall back to using regular expressions for more custom string searching.

3.2 Padding Strings

The concept of padding a string is specifying how long you want the string to be, and

filling the string with a pad character until it is that length.

function binaryIP(decimalIPStr) {
  return decimalIPStr.split('.').map(function(octet) {
    return Number(octet).toString(2)
  }).join('.')
}

To fix this, you need to pad each octet with zeros until each one is eight digits. To accomplish this we can use a new feature introduced in ES2015, String.prototype.repeat. We could also use padStart but that’s a bit more flexible as you’ll see in a minute and repeat is also useful to know.

function binaryIP(decimalIPStr) {
  return decimalIPStr.split('.').map(function(octet) {
    let bin = Number(octet).toString(2)
    return '0'.repeat(8 - bin.length) + bin
  }).join('.')
}

binaryIP('192.168.2.1')

This works because the repeat function simply repeats the function it is invoked on the amount of times specified in the argument:

X.repeat(4)

If the number specified is not a round number, it will first be floored (not rounded):

X.repeat(4.9)

Using String.prototype.repeat works for our binary example. But what if we wanted to pad with a string that is more than one character? That would be tricker to achieve with jus the repeat function but we can easily achieve it with String.prototype.padStart and String.prototype.padEnd.

The padStart and padEnd methods take two arguments. The max length of the returned string, and the filter string. The filler defaults to a space " " character:

'abc'.padStart(6) // " abc"
'abc'.padEnd(6)   // "abc "

The max length property specifies the max length of the string after the filter has been repeated. If the filler is multiple chars and cannot evenly be added, it will be truncated:

If the max length is less than the original string, the original string will not be truncated by returned without any padding applied:

'abcdef'.padStart(4, '123') // "abcdef"

Using this, our binaryIP function could then be rewritten as so:

function binaryIP(decimalIPStr) {
  return decimalIPStr.split('.').map(function(octet) {
    return Number(octet).toString(2).padStart(8, '0')
  }).join('.')
}

binaryIP('192.168.2.1');

That’s pretty concise implementation that is easy to read and understand. Using pure ES5 code would have been much more verbose to achieve the same thing. Of course

we could have used a third party string pad function or written our own but

String.prototype.padStart and String.prototype.padEnd eliminates that need.


Lesson 4. Template Literals

In JavaScript, one of the biggest annoyances has always been the lack of support for multiline strings. Having to break each line into a separate string and glue them all

together is a very tedious process. With the addition of template literals that pain is gone along with some others. Template literals introduce multiline strings to JavaScript as well as interpolation and tagging.

4.1 What are Template Literals?

A template literal is a new literal syntax for creating strings. It uses back-ticks (`) for

demarcation instead of quotes (‘) or (“) like string literals do and supports new features

and functionality. However, template literals evaluate to a string just like string literals

do:

let str1 = 'Hello, World'
let str2 = "Hello, World"

let str3 = `Hello, World`

console.log(str1 === str3) // return True

console.log(str2 === str3) // return True

The difference is that template literals support three new features that regular string

literals do not: Interpolation, Multiline, and Tagging. Let’s define what each of those are.

String Interpolation This is the concept of placing a dynamic value into the creation

of a string. Many other languages support this but up until now, JavaScript could only put

dynamic values into larger strings by concatenation.

let interpolated = `You have ${cart.length} items in your cart`

let concatenated = 'You have ' + cart.length + ' items in your cart';

Multiline Strings This is not the concept of creating a string that is multiline.

JavaScript has always been able to do that (e.g. “Line One\nLine Two”). This is the

concept of the literal itself spanning multiple lines. Regular strings in JavaScript must be defined in a single line meaning the opening quote and the ending quote must be on the same line. With Template literals this is not the case.

let multiline = `line one
                 lines two`

Tagging Template Literals A tagged template literal is a template literal that is with a tagged function.

  • The function is given all the raw string parts as well as the interpolated values separately so it can do custom preprocessing of the string.
  • It can return an entirely different string or a custom value that is not even a string.

This is especially useful if you want to be able to do some sort of preprocessing on

the interpolated values before merging them into the final value.

  • For example you could have a tagging function that converts the template literal into DOM nodes but escapes the interpolated values to protect from HTML injection. Likewise you could also prevent SQL injection by treating the string parts as safe and the interpolated values as potentially dangerous.
let html = domTag`
${userInput}

`

4.1.1 String Interpolation with Template Literals

The syntax for interpolating value into template literals is to wrap the value to be

interpolated with curly braces { and } and to prefix the opening curly brace with the

dollar sign $.

function fa(icon) {
  return `fa-${icon} fa`
}

fa('check-square') // return "fa-check-square fa"

This takes the value from the icon variable and injects it into the template string.

This function generates the Font Awesome8 CSS class needed for a given icon. The more interpolation required, the more obvious it becomes how nice a feature this is:

function greetUserA(user, cart) {
  const greet = `Welcome back, ${user.name}.`
  const cart = `You have ${cart.length} items in your cart.`
  return `${greet} ${cart}`
}

function greetUserB(user, cart) {
  const greet = 'Welcome back, ' + user.name + '.'
  const cart = 'You have ' + cart.length + ' items in your cart.'
  return greet + ' ' + cart
}

function madlibA(adjective, noun, verb) {
  return `The ${adjective} brown ${noun} ${verb}s`
}

function madlibB(adjective, noun, verb) {
  return 'The ' + adjective + ' brown ' + noun + ' ' + verb + 's'
}

madlibA('quick', 'fox', 'jump');
madlibB('quick', 'fox', 'jump');

In each example, both these functions are equivalent in functionality.

Remarks:

  1. you can escape the interpolation using the normal escape character.
  2. If you are just adding a dollar sign that isn’t followed by an opening brace then there is no need to escape anything.
  3. Any value you interpolate that is not already a string will be converted to a string and its string representation will be used.
let obj = { foo: 'bar' }
let str = `foo: ${foo}`

console.log(str) // return "foo:[object object]"

4.1.2 Multiline Strings with Template Literals

  1. Unlike string literals, an unterminated template literal will continue to the following line until terminated:
  2. One thing to definitely keep in mind is that multiline template literals preserve all of their white space characters:

4.2 Template Literals are not Reusable Templates

I think the name a bit misleading because they template literal create strings not templates. When developers think of templates they usually think of something that is reusable. Template literals are not reusable templates but one time templates that get evaluated and turned into a string all at once; so there is no reusability to them.

let name = 'Talan';

// Using underscore
let greetA = _.template('Hello, <%= name %>');
greetA(name);
greetA('Jonathon');

// Using template literals
let greetB = `Hello, ${name}`;
greetB;
greetB('Jonathon');
  • When you create a template from a library like underscore, you specify placeholders for values and you get back a function that you can invoke multiple times with different
    values to be interpolated.
  • A template literal on the other hand interpolates the values at the time it is created, so there is no re-evaluating it with new values.
    • You can however, create a reusable template by simply wrapping the template literal in a function:
let name = 'Talan';

function greet(name) {
  return `Hello, ${name}`;
}

greet(name); // Hello, Talan
greet('Jonathon'); // Hello, Jonathon

4.3 Custom Processing with Tagged Template Literals


A tagged template literal is a template literal that is tagged with a function by prefixing the template literal with the name of a function. The basic syntax is as follows:

myTag`my string`

By doing this, you are tagging my string with the function myTag. The myTag function will be invoked and given the template as arguments. If the template has any interpolated values, they are passed as a separate arguments. The function is then responsible for performing some type of processing of the template and returning a new value.

There is currently only one built in tagging function, String.raw which processes a template without interpreting special characters:

const a = `my\tstring` // Return "my string"
const b = String.raw`my\tstring` // Return "my\tstring"

This may be the only built in tagging function, but you can use ones by library authors, or write your own!

When the template literal is tagged with a function, it will be broken apart into the string parts and the interpolated values.

  • The tagged function will be given these values as separate arguments
  • The first argument will be an array of all the string parts, then for
    every interpolated value, it will be given as an additional argument to the function.
function tagFunction(stringPartsArray, interpolatedValue1, ..., interpolatedValueN)
{
}

To get a better understanding of how these pieces are broken apart and passed, let’s write a function that simply logs the parts:

function logParts() {
  let stringParts = arguments[0]
  
  let values = [].slice.call(arguments, 1)
  console.log('String:', stringParts) // Return Strings: ["1", "3", "", ""]
  console.log('Values:', values) // Return Vales: [2, 4, 5]
}

logParts`1${2}3${4}${5}`

reference: Array.prototype.slice()

You should have expected the strings 1 and 3 but why is there two additional empty strings? If values being interpolated are next to each other without any string parts in between, such as the 4 and the 5 there will be an implicit empty string between them. This is the same for the start and end of the string as well.

Screen Shot 2017-01-20 at 6.14.53 P

Because of this, the first piece and last piece will always be strings and the values will be what glues those strings together. This also means we can easily process these values with a simple reduce function:

function noop() {
  let stringParts = arguments[0]
  let values = [].slice.call(arguments, 1)
  return stringParts.reduce(function(memo, nextPart) {
    return memo + String(values.shift()) + nextPart
  })
  
  noop`1{2}3${4}${5}` // Return "12345"
}

reference: Array.prototype.reduce()

In this function, we just processed the template literal the same as it would have been had it not been tagged. However the point is to allow custom processing. This will likely be a feature that is mostly implemented by library authors but we can still use it to create some custom processing. Let’s write a function that strips the whitespace between our markup:

function stripWS() {
  let stringParts = arguments[0];
  let values = [].slice.call(arguments, 1);
  let str = stringParts.reduce(function(memo, nextPart) {
    return memo + String(values.shift()) + nextPart;
  });
  return str.replace(/\n\s*/g, '');
}

function getProductHTML(product) {
  return stripWS`

${product.name}

 

${product.desc}

</div> `; }

We have only just scratched the surface of what you can do with template literals.Because they allow for preprocessing of the interpolated values separately from the string parts of the literal and can return any data type; they are perfect for creating abstractions known as DSLs (Domain Specific Languages) which we will be diving into next in our first capstone project.


C1 Building a Domain Specific Language

Before we build a DSL, let’s first define what one is. A domain specific language is the idea of using a programming language’s features and syntax in a clever way to make solving a particular task appear as if it has first class support by the programming language itself.

  • For example, a common DSL seen in JavaScript are those used by unit test libraries.
describe('push', function() {
  it('should add new values to the end of an array', function() {
    // perform test
  })
})

This is a DSL because it looks like you are using natural language to state that you are describing push, and then giving the actual description it should add new values to the end of an array. In reality, the describe, and it parts of the sentences are functions, and the rest of the sentences are parameters to those functions.

Other languages such as Ruby have a lot of DSLs. Ruby in particular is so malleable and has so much syntactic sugar that is makes creating DSLs extremely easy. JavaScript on other hand has not historically lent itself to easy DSL creation. But with tagged templates that may change.

  • For example, a clever programmer might be able to utilize tagged templates to make the above unit testing DSL to have a syntax like so:
describe`push: it should add new values to the end of an array${
  // perform test
}`

We are not going to implement the above DSL but we are going to create a couple of our own. In the following sections we will:

  • Create Some Helper Functions for string processing
  • Create An HTML-Escaping DSL
  • Create a DSL for Converting Arrays Into HTML

C1.1 Create Some Helper Functions

Before begin lets create a couple helper functions. In the last chapter when creating a template tagging function we started the function with some code that resembled the following.

function stripWS() {
  let stringParts = arguments[0]
  let values = [].slice.call(arguments, 1)
  // do something with stringParts and values
}

These first 2 lines are for the purpose of getting the string values and the interpolated values of the template literal. They are a bit obscure, though, and detract from the rest of the logic.

  • We will soon discover much better ways of collecting these values but for now lets abstract this step away in a layer that hides this part from the rest of our logic.
  • How? We will create a separate function to do this and then pass the collected values to our tag function.
// Listing C1.1 Abstracting the process of collecting interpolated values
function createTag(func) {
  return function() {
    const strs = arguments[0]
    const vals = [].slice.call(arguments, 1)
    return func(strs, vals)
  }
}

Now when we create a tagging function we can do this which is far neater

const stripWS = createTag(function(strs, vals){
  // do something with strs and vals
})

Another redundant step was when we had to use reduce to interlace the given strings and interpolated values. We can abstract that step away as well.

function interlace(strs, vals) {
  vals = vals.slice(0) // This line is duplicating the array. A good practice before invoking methods that mutate the array in place like shift
  return strs.reduce(function(all, str) {
    return all + String(vals.shift()) + str
  })
}

We can now use the combination of createTag and interlace to process a template literal in the standard implementation as shown in Listing C1-x

// Listing C1.3 capstone-1.3
const processNormally = createTag(interlace);
const text = 'Click Me'
const link = processNormally`<a>${text}</a>` // Return: <a>Click Me</a>

Now that he have the these parts abstracted away, we can focus on writing a tagging function that just focuses on the business at hand – escaping an HTML string.

C1.2 Create An HTML Escaping DSL

// Listing C1.4 A simple HTML Escaping function
function htmlEscape (str) {
  str = str.replace(/</g, '&lt;')
  str = str.replace(/>/g, '&gt;')
  return str
}

reference: Array.prototype.map()

The function in figure Capstone-1.4 just uses regular expressions to replace the < and > characters with < and > respectively. Now that we have this, writing our tagging function is going to be super simple.

const htmlSafe = createTag(function(str, vals) {
 return interlace(strs, vals.map(htmlEscape)) // Calls htmlEscape on just the values to be interpolated.
}

The reason why this is beneficial as a tagging function as opposed to just using the htmlEscape function directly is that it allows us to target only the interpolated values for escaping as seen in Listing c1-6.

// Listing C1.6 Our basic HTML-Escaping DSL in action
const userInput = 'I<3 ES6!'

const a = htmlEscape(`<strong>${userInput}</strong>`)
const b = htmlSafe`<strong>${usrInput}</strong>`

Notice how the string a has all of the HTML pieces escaped but the string b correctly only escaped the <3.

C1.3 Create a DSL for Converting Arrays into HTML

Take a look at the following code. What would you expect the value list to be?

const fruits = ['apple', 'orange', 'banana']
const list = expand`<li>${fruits}</li>`

Wouldn’t it be great to have a utility that could expand the template by repeating it around the array of values to produce the following HTML?

<li>apple</li>
<li>orange</li>
<li>banana</li>

Let’s build it! It is actually really simple. We just need to

  • grab the first and last part of the template
  • grab the interpolated array
  • map over the items in the array wrapping each item with the parts of the template.

All of which translates to the following code.

const expand = function(parts, items) {
  const start = parts[0]
  const end = parts[1]
  const mapped = items.map(function(item){
    return start + item + end
  })
  
  return mapped.join('')
}

Now let’s see it in action:

// Listing C1.7 Our DSL interpolates an array of values, not just one.

const lessons = [
  'Declaring Variables with let',
  'Declaring constants with const',
  'New String Methods',
  'Template Literals'
]

const html = `<ul>${
  expand`<li>${lessons}</li>`
}<ul>`

The html variable now looks like so:

<ul>
  <li></li>
  <li></li>
  <li>New String Methods</li>
  <li>Template literals</li>
</ul>

Currently the output looks like HTML but it still merely a string. You could take this even further by modifying expand or making another template tag that converts the string to actual DOM nodes.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.