Giter Site home page Giter Site logo

js-advanced-functions-basic-functions-review-lab-recoded-istanbul-2019's Introduction

JavaScript Advanced Functions: Basic Functions Review

Learning Goals

  • Define a function using a function declaration
  • Define hoisting
  • Define function expression
  • Define anonymous function
  • Define a function using a function expression
  • Define an IIFE: Immediately Invoked Function Expression
  • Define scope chain
  • Define closure

Introduction

This lab describes some more advanced concepts related to JavaScript functions. Be sure to take time to experiment or read up on a concept if you're not comfortable with the idea before moving on. If you're struggling here, the remainder of this module will be challenging. Fix any gaps now before moving on.

We also recommend that you complete the lab as you read through the sections. Reinforcing what you read by physically typing in the code will help make sure the concepts are locked in. We'll prompt you when it's a good time to shift modes from "reading along" to coding.

Define a Function Using Function Declaration

In JavaScript, the most common way to define functions is with a function declaration:

function razzle() {
  console.log("You've been razzled!");
}

The word razzle becomes a pointer to some stored, potential, not-yet-actually-run bit of work (the function). We use the pointer to call or invoke the function. We call the function by adding () after the pointer.

function razzle() {
  console.log("You've been razzled!");
}
razzle();
//=> "You've been razzled!"

Interestingly, you can write function declarations after you call them:

razzle(); //=> "You've been razzled!"
function razzle() {
  console.log("You've been razzled!");
}

Functions can be passed arguments, given default arguments, etc. Here's a brief code synopsis:

function razzle(lawyer="Billy", target="'em") {
  console.log(`${lawyer} razzle-dazzles ${target}!`);
}
razzle(); //=> Billy razzle-dazzles 'em!
razzle("Methuselah", "T'challah"); //=> Methuselah razzle-dazzles T'challah!

LAB: Implement a function called saturdayFun. It should return a String like "This Saturday, I want to ....!" Fill in the ... with the activity that's passed in as the first parameter. If nothing is passed in, default to "roller-skate". Run learn to verify you've gotten the first set of tests passing before continuing with the lesson.

Define Hoisting

JavaScript's ability to call functions before they appear in the code is called hoisting. For hoisting to work, the function must be defined using a function declaration.

Define Function Expression

We've learned that programming languages feature expressions: arrangements of constants, variables, and symbols that, when interpreted by the language, produce a value. To review, open up your browser console and type in these examples:

1 + 1; //=> 2
"Razzle " + "dazzle!"; //=> "Razzle dazzle!"

The examples above are expressions that return primitive values, but JavaScript also has function expressions that look like this:

function() {
  console.log("Yet more razzling");
}

The value returned by this expression is the function itself. Go ahead and enter the above into the browser console; you should see the following:

Uncaught SyntaxError: Function statements require a function name

The problem is that, when the function expression appears by itself as shown above, JavaScript does not recognize it as a function expression; it instead interprets it as a function declaration that's missing its name. One way to tell the JavaScript engine that it's a function expression is to use the grouping operator () to wrap the entire thing:

(function() {
  console.log("Yet more razzling");
})

Recall that the grouping operator is usually used in arithmetic operations to tell the JavaScript engine to evaluate the value that's inside it first. It's serving a similar purpose in this case: it's telling JavaScript to interpret what's inside the parentheses as a value. With the grouping operator in place, the JavaScript engine recognizes our function as a function expression. Enter the function into your console again, this time using the grouping operator. You should see the following:

ƒ () {
  console.log("Yet more razzling");
}

JavaScript now correctly shows us the return value of our function expression: a function (indicated by the ƒ ()) storing the work of logging our message.

Define Anonymous Function

An anonymous function is, quite simply, a function that doesn't have a name:

function() {
  console.log("Yet more razzling");
}

Unlike a function declaration, there's no function name in front of the (). Note, however, that if we don't assign a name to the function, we have no way to call it. We lose access to our function immediately after it's created. So how can we invoke an anonymous function? We've seen one way before: we can use it as a callback function. For example, you'll often see anonymous functions passed as an argument to an event listener:

const button = document.getElementById('button');
button.addEventListener('click', function() {
  console.log("Yet more razzling");
});

Our anonymous function is being passed as an argument to addEventListener. The JavaScript engine "stores it away" as work to be executed later, when the button is clicked.

Define a Function Using a Function Expression

Another way we can solve the problem of accessing an anonymous function is by declaring a variable and assigning the function as its value. Recall that any expression can be assigned to a variable; this includes function expressions:

const fn = function() {
  console.log("Yet more razzling");
}

The code above defines our function using a function expression. If we ask JavaScript what's in fn, it tells us:

fn; //=> ƒ () { console.log("Yet more razzling") }

Here, fn is a pointer to the stored block of work that hasn't yet been invoked. Just as with function declaration, to actually do the work, we need to invoke or call the function. We do this by adding () to the end of our "pointer", the variable name:

const fn = function() {
  console.log("Yet more razzling");
} //=> undefined
fn; //=> ƒ () { console.log("Yet more razzling") }
fn(); // "Yet more razzling"

Also as with a function declaration, if we need to pass arguments to the function, we would include those in the parentheses when we call the function.

We now know how to define a function as a function expression. Very importantly, function expressions are not hoisted. The same is true for any variable assignment: if we assign a String or the result of an arithmetic expression to a variable, those assignments are not hoisted either.

LAB: Implement a function expression called mondayWork. The function should return a String like "This Monday, I will ... ." Fill in the ... with the activity that's passed in as the first parameter. If nothing is passed in, default to "go to the office". Run learn to verify you've gotten this set of tests passing before continuing with the lesson.

Define an IIFE: Immediately Invoked Function Expression

Another way to invoke an anonymous function is by creating what's known as an Immediately Invoked Function Expression (IIFE).

As a thought experiment, consider what happens here:

(function(baseNumber){ return baseNumber + 3 })(2); //=> ???

We recognize the first () as the grouping operator that tells the JavaScript engine to interpret the contents as a value — in this case, a function expression. What this means is that, in the IIFE statement, the value returned by the first set of parentheses is an anonymous function, which can be invoked (immediately).

The second () are the () of function invocation. When we put them immediately after the first set of parentheses, we're invoking the function that those parentheses return immediately after defining it. Try it out in the browser console:

(function(baseNumber){ return baseNumber + 3; })(2); //=> 5

Interestingly, any variables, functions, Arrays, etc. that are defined inside of the function expression's body can't be seen outside of the IIFE. To see this, check the value of baseNumber in the console. It's like opening up a micro-dimension, a bubble-universe, doing all the work you could ever want to do there, and then closing the space-time rift. We'll see some of the practical power of "hiding things" in IIFEs a little later in this lesson.

Define Scope Chain

Recall that JavaScript exhibits "function-level" scope. This means that any variable defined inside a function — either as a parameter or using a variable declaration — is only available inside that function. Thanks to the scope chain, however, if a function is defined inside another function, the inner function has access to all the variables belonging to the outer function. This works recursively: if we nest a third function inside the inner function, it will have access to all the variables of both the inner and outer enclosing functions. It does not work in the other direction, however: functions do not have access to variables defined in any functions nested inside them.

The way the scope chain works is, when a variable is used, the JavaScript engine will first look for its value inside the current scope. If it doesn't find it there, it will work its way up the chain, checking each successive enclosing scope until it either finds the variable's value or reaches the global scope.

Here's a simple example:

function demoChain(name) {
  const part1 = 'hi'
  return function() {
    const part2 = 'there'
    return function() { 
      console.log(`${part1.toUpperCase()} ${part2} ${name}`);
    }
  }
}

demoChain("Dr. Stephen Strange")()() //=> HI there Dr. Stephen Strange

When the innermost function is called, the JavaScript engine works its way up the scope chain to find the variables it's using. It finds part2 in the enclosing parent function, and name and part1 in its parent function. As a result, when the console.log() statement is run, the string includes all three values. That's awesome wormhole, space-time magic!

Let's work through a more complex example:

function outer(greeting, msg="It's a fine day to learn") { // 2
  const innerFunction =  function(name, lang="Python") { // 3
    return `${greeting}, ${name}! ${msg} ${lang}`; // 4
  }
  return innerFunction("student", "JavaScript"); // 5
}

outer("Hello"); // 1
//=> "Hello, student! It's a fine day to learn JavaScript"

Let's break this down:

  1. We call outer, passing "Hello" as an argument.
  2. The argument ("Hello") is saved in outer's greeting parameter. The other parameter, msg, is set to a default value.
  3. Here's our old friend the function expression. It expects two arguments, to be stored in the parameters name and lang, and lang is assigned the default value of "Python". The function expression itself is saved in the local variable innerFunction.
  4. Inside innerFunction we make use of its parameters, name and lang, as well as the greeting and msg parameters defined in innerFunction's containing (parent) function, outer. innerFunction has access to those variables via the scope chain.
  5. Finally, inside outer, we invoke innerFunction, passing arguments that get stored in innerFunction's name and lang parameters.

This might look a little bit weird, but it generally makes sense to our intuition about scopes: inner things can see their parent outer things.

Note that currently, the values of the arguments being passed to innerFunction are part of the definition of outer. In order to change those values we have to modify the outer function. This is not ideal.

With a simple change, something miraculous can happen. Rather than having outer return the result of calling innerFunction, let's have it return the function itself:

function outer(greeting, msg="It's a fine day to learn") {
  const innerFunction =  function(name, lang="Python") {
    return `${greeting}, ${name}! ${msg} ${lang}`;
  }
  return innerFunction;
}

The return value of outer is now an anonymous function. To invoke it, we update the function call as follows:

outer("Hello")("student", "JavaScript");
//=> "Hello, student! It's a fine day to learn JavaScript"

The function call is processed by the JavaScript engine from left to right. First, outer is called with the argument "Hello." The return value of calling outer("Hello") is itself a function and, therefore, can itself be called. We do this by chaining on the second set of parentheses. This is basically the same concept as assigning a function expression to a variable and using the variable name followed by () to invoke the function. You can almost think of outer("Hello") as the "name" of the function that's returned by outer. It's the same as if we did this:

const storedFunction = outer("Hello");
storedFunction("student", "JavaScript");
//=> "Hello, student! It's a fine day to learn JavaScript"

Note that we are no longer calling innerFunction from inside outer. Amazingly, the code works exactly the same: it still has access to those parent function's variables. It's like a little wormhole in space-time to the outer's scope!

We can tighten this code up a bit more: instead of assigning the function expression to innerFunction and returning that, let's just return the function expression.

function outer(greeting, msg="It's a fine day to learn") {
  return function(name, lang="Python") {
    return `${greeting}, ${name}! ${msg} ${lang};`
  }
}

outer("Hello")("student", "JavaScript")
//=> "Hello, student! It's a fine day to learn JavaScript"

To review: we first called outer, passing in the argument "Hello". outer returned an anonymous function inside which the default value of msg and the passed-in value of greeting have now been set. It's almost as if outer returned:

function(name, lang="Python") { // The "inner" function
  return `Hello, ${name}! It's a fine day to learn ${lang}`
}

We invoked this returned inner function by adding the second set of parentheses and passing the arguments "student" and "JavaScript", which were stored in name and lang. This filled in the final two values inside of the template string and returned:

"Hello, student! It's a fine day to learn JavaScript"

Define Closure

In the previous example, we could call the "inner" function, the anonymous function, a "closure." It "encloses" the function-level scope of its parent. And, like a backpack, it can carry out the knowledge that it saw — even when you're out of the parent's scope.

Recall the IIFE discussion. Since what's inside an IIFE can't be seen, if we wanted to let just tiny bits of information leak back out, we might want to pass that information back out, through a closure.

Note: We're also using destructuring assignment, don't forget your ES6 tools!

const [answer, theBase] = (
  function(thingToAdd) {
    const base = 3;
    return [
      function() { return base + thingToAdd; },
      function() { return base; }
    ];
  }
)(2)

Note that the value on the right of the = in the first line is a function expression. That function takes a single argument and returns an array that contains two functions. The (2) after the function expression executes that function (immediately), and the two inner functions are stored in the variables answer and theBase by the destructuring assignment.

Go ahead and copy the code above into your browser console and take a look at the values of answer and theBase. You should see the following:

answer; // => ƒ () { return base + thingToAdd; }
theBase; // => ƒ () { return base; }

However, if you try looking at the value of base in the console you'll get a reference error: the value of base is not accessible outside the function it's defined in. Now go ahead and call answer and theBase; you should see the following:

answer(); // => 5
theBase(); // => 3

The answer and theBase functions are closures; they have access to the base variable because it's defined in their parent function. When they're executed, they "let out" the values of the sum and the original base number, allowing us to see them.

LAB:

Implement a function called wrapAdjective:

  • It should return a function
    • This "inner" function should:
      • take a single parameter that should default to "special". Name it however you wish.
      • return a String of the form "You are ..." where ... should be the adjective this function received wrapped in visual flair
  • It should take as parameter a String that will be used to create visual flair
  • You may call the parameter whatever you like, but its default value should be "*"
  • Call example: const encouragingPromptFunction = wrapAdjective("!!!")

Thus a total call should be:

wrapAdjective("%")("a dedicated programmer") //=> "You are %a dedicated programmer%!"

Run learn to verify you've gotten this set of tests passing.

Additional Practice in Lab-Driven Development for JavaScript Basics

Whew! That's a lot of recap with a lot of mind-bending stuff. Let's make sure that we review some of our easier basics.

The remainder of the tests are not new material. They're here to make sure you remember how to work with Objects and Arrays full of functions. Use the tests to guide you in feeling confident working with functions.

Conclusion

In this lesson, we've covered the basics of function declaration, invocation, and function scope. As a refresher on your skills, we've provided a simple lab to make sure that you're set for the new information coming up in the rest of this module.

Resources

js-advanced-functions-basic-functions-review-lab-recoded-istanbul-2019's People

Contributors

lizbur10 avatar maxwellbenton avatar dependabot[bot] avatar thuyanduong-flatiron avatar ihollander avatar szib avatar rrcobb avatar vibraniumforge avatar

Watchers

James Cloos avatar Kevin McAlear avatar  avatar Mohawk Greene avatar Victoria Thevenot avatar Belinda Black avatar Bernard Mordan avatar raza jafri avatar  avatar Joe Cardarelli avatar The Learn Team avatar Sophie DeBenedetto avatar  avatar Antoin avatar Alex Griffith avatar  avatar Amanda D'Avria avatar  avatar Nicole Kroese  avatar Kaeland Chatman avatar Joseph Kholodenko avatar Lisa Jiang avatar Vicki Aubin avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.