Javascript Notebook
  • Introduction
  • Intro to JavaScript
    • Section 1 - Getting Started
    • Section 2 - Execution Contexts and Lexical Environements
    • Section 3 - Types and Operators
    • Section 3 Part 2 - Closures and Callbacks
    • Section 4 - Objects and Functions
    • Section 5 - Object Oriented Javascript and Prototypal Inheritance
    • Section 6 - Building Objects
    • Section 7 - Odds and Ends
    • Section 8 - Examining Famous Frameworks and Libraries
    • Section 9 - Let's Build a Framework or Library!
  • Midterm Review
  • Final Review
  • jQuery
    • Section 1 - Selectors
    • Section 2 - Events
    • Section 3 - Effects
  • Node.js
    • The Node Core
    • Modules, Exports, and Require
    • Events and the Event Emitter
    • Databases and SQl
  • D3.js
    • Diving In
    • Bar Chart
    • Creating A Complex Bar Chart
Powered by GitBook
On this page
  • Syntax Parsers
  • Whitespace
  • IIFE's and Safe code
  • Understanding Closures
  • Understanding Closures Part II
  • Function Factories
  • Function Callbacks
  • Call(), Apply(), and Bind() Functions
  • Function borrowing and currying
  • Functional Porgramming
  • Functional Programming Part 2

Was this helpful?

  1. Intro to JavaScript

Section 3 Part 2 - Closures and Callbacks

Syntax Parsers

  • Before your code becomes executed, there is an intermediate step it goes through where your code becomes parsed to determine valid syntax.

  • It may even change your code.

    • In js, there it is not technically nessessary to have semicolons.

    • This is because javascripts engine will do this for us.

    • However, it is always recommended to be explicit with the code you write, and that includes typing down your own semicolons - because it can lead to unintentional mistakes.

    function getPerson() {
        return
        {
            name: 'dan'
        }
    }

    console.log(getPerson()); //undefined
  • Here, the syntax parser saw return, and then the carriage return. So because of this, it included and semicolon, and nothing was returned.

  • So, basically, this system enforces a particular kind of way of writing out this. So that you avoid these kinds of mistakes. I dont know if this is good or not. But this will fix it.

    return {
        name: 'dan'
    }

Whitespace

  • Javascript is very liberal in the way they allow whitespace. But their was something that I disagreed about with the instructor in this lecture. He recommended to comment out your code as much as possible. I agreed, in a way, but they way he demonstrated in the video was highly redundant, obvious and unnecessary.

  • I would only comment if it makes my code more productive. Doing something like this is not good.

    // the lastname of a person
    var lastname = 'mak';

IIFE's and Safe code

  • An IIFE is a quick way to execute functions on the fly.

    (function greet(name){
        console.log("hello " + name)
    }('dan'))


    // hello dan
  • Here, we have (), so it expects some kind of execution.

  • Just as if we were to do greet('dan'); - here greet is simply just replaced with the function.

  • Like inline in C++

  • But the main reason this is done is mainly is the safety is provides.

    var greeting = 'hola';

    (function greet(name){
        var greeting = 'hello ';
        console.log(greeting + name)
    }('dan'))
  • With this kind of immediate execution, we avoid polluting the global namespace.

  • Our code is kept clean, and our variables belong only where they need to belong.

  • And when we want to access the global namespace, we can. We just have to pass the global window object as a reference, and modify whatever we want to modify.

    var greeting = 'hola';

    (function greet(global, name){
        var greeting = 'hello ';
        console.log(greeting + name)
        global.greeting = 'hello';
    }(window, 'dan'))


    console.log(greeting); // hello
  • This is very clean because we only affect the global namespace when we want to do so intentionally.

Understanding Closures

  • Lets learning another critical part of JS.

    function greet(greeting) {
        return function greetWho(name) {
            console.log(greeting + ' ' + name);
        }
    }

    greet('hi')('dan');
  • What's happening.

    • We've invoked a function that returns an object, or function.

    • Because another function was returned, it could be invoked immediately.

    • () <-- invoke

    • greet() <--- invokes function

    • greet()() <--- invoke what was returned

  • It can also be invoked uniquely like this:

    var greeter = greet('hi');
    greeter('dan');
  • This is essentially the same thing. A function is stored in to a dynamic variable, and then invoked.

  • But how greeter remember 'hi' previously on the stack, has to do with closures.

  • Whats happening under the hood:

    • greeter is under the global namespace

    • greet('hi') creates a new execution context, with variable 'hi' stored in memory.

    • Whenever a function returns it's stack memory is removed.

    • Eventually js will clear out the memory, but in that moment, the variable 'hi' still exists there, and nothing had overwritten it.

    • We move back to the global context, where a another execution context is created with greeter(), along with allocated memory for 'dan'.

    • When the function gets executed, 'name' is found natural - as it was supplied as a parameter, but what about greeting? Because the function returned, it was removed from the stack frame.

    • However - as we've noted before, it still exists in memory, and when the function executes, it begins to look up the scope chain to find it.

  • The idea of closure, is that any function within a function has reference to it's memory, even if was released from that stack.

  • This is done by the javascript engine on its own. This idea or concept is reliable 100% of the time.

Understanding Closures Part II

  • There is a classic example of a demonstration of closures.

    function classic(){
        var array = [];

        for (var i = 0; i < 3; i++) {
            array.push(
                function() {
                    console.log(i);
                }
            )
        }

        return array;
    }

    var storedArray = classic();
    storedArray[0](); //3
    storedArray[1](); //3
    storedArray[2](); //3
  • Initially, I expected 1 2 and 3 respectively in this output, but soon I've realized why the output is the way it is.

  • Here are my thoughts:

    • storedArray = global variable

    • classic() <-- new execution context

      • Stack frame becomes terminated, but variables remain in memory.

    • New execution context for storedArray()[] invoked calls.

    • What tricked me was the console.log(i) part. Here, the function is not being executed. We are ONLY supplying the array with a new function object. The variable i is being passed to it as well. 'i' is incremented, but the way the code executes to begin with is that it looks for that variable 'i'.

    • When we then invoke storedArray; what we are going is creating a new execution context.

      • The variables are essentially lost, but they still remain in memory.

      • When we actually execute the function, we look up the scope chain to find 'i' in order to console.log it.

      • In its process, i had been rewritten 3 times, and when the function call finished, we've ended up with the latest 'i'. So the console prints 3.

      • The execution context was the same for all of the object creations. Thats why when it referred back up the scope chain, it found i as 3, each time.

  • How can we fix this?

    function classic(){
        var array = [];

        for (var i = 0; i < 3; i++) {
            array.push(
                (function (j) {
                    return function(){
                        console.log(j);
                    }
                })(i)
            )
        }

        return array;
    }

    var storedArray = classic();
    storedArray[0]();
    storedArray[1]();
    storedArray[2]();
  • The way to do with create a seperate execution context for call. We can do this through IIFE's, which execute the function immediately.

  • We push to an array that executes its own execution context. But the execution of this function returns an object, so we are able to invoke it whenever we want.

  • The main reason this works is because we create a new variable each time in memory, and when we invoke it, will look back to that unique memory spot in memory. No more looking at the same place, like before.

Function Factories

  • Another great demonstration of function closures

    function factoryGreeting(language) {
        return function(name) {
            if (language == 'en') {
                    console.log("hello " + name);
            }


            if (language == 'es') {
                    console.log("hola " + name);
            }

        }

    }


    var greetEng = factoryGreeting('en');
    var greetSpa = factoryGreeting('es');

    greetEng('sam'); // hello sam
    greetSpa('dan'); // hola dan
  • The take away from this is that we've defined a function that creates two seperate contexts depending on the input given.

  • Because we have two different return scenarios, there are 2 different contexts in which we can use closure to search up the scope chain.

  • When we execute var greetEng = factoryGreetign for both spanish and english, we create two different memory spaces.

  • Both, however, where returned from the same anonymous function.

  • When we executed it a second time with the returned object the language variable remained remembered.

Function Callbacks

  • A functional callback is when you supply one function another function that runs after the first execution.

    function sayHiLater() {
        var greeting = 'hello';
        setTimeout(function(){
            console.log(greeting);
        }, 3000)
    }

    sayHiLater();
  • setTimeout demonstrates what ever after 3 seconds, we still maintain access to variable out the immediate scope, (greeting).

Call(), Apply(), and Bind() Functions

  • All functions have access to these methods.

  • First bind.

    var person = {
        first: 'dan',
        last: 'mak',
        getfullname: function() {
            var fullname = this.first + ' ' + this.last;
            return fullname;
        }
    }

    var binder = function logName(){
        console.log(this.getfullname());
    }

    binder();
  • We can immediately see that this.getfullname doesnt make sense because this is not defined under the person method.

  • What if we had a way to change this? This is what the bind keyword does.

  • Remember, this is an object that changes on context method.

  • Certainly we can use

    console.log(person.getfullname());
  • But I've suspect binder is clearer in large circumstances.

    var person = {
        first: 'dan',
        last: 'mak',
        getfullname: function() {
            var fullname = this.first + ' ' + this.last;
            return fullname;
        }
    }

    var binder = function logName(){
        console.log(this.getfullname());
    }.bind(person);

    binder();
  • bind creates a copy of the function

  • Call preforms something similiar. Rather than making a copy, like bind does, it executes the function on the spot.

    var person = {
        first: 'dan',
        last: 'mak',
        getfullname: function() {
            var fullname = this.first + ' ' + this.last;
            return fullname;
        }
    }

    var binder = function logName(){
        console.log(this.getfullname());
    }.call(person, //more optional parameters);


    //output
    //dan mak
  • In addition, because it contains the ability to execute, you can supply it with parameters.

  • Apply performs that same exact this as call, except for a single difference.

    var binder = function logName(){
        console.log(this.getfullname());
    }.call(person, //[more, optional, parameters,]);
  • That, is after supplier your this point, the parametes are references in array, which can be beneficial under mathematical circumstances.

  • So call and apply can be used interchangable depending on how we want to use it.

Function borrowing and currying

  • What if we had two similiar object methods, but the objects are explicitly different. Call, bind, and apply can also help us do this, and remove redudent code.

    console.log(person.getfullname.call(person2));
  • 1). We access person.getfullname with full rights

  • 2). We invoke that function and bind 'this' to person 2

  • 3). Now a stranger function has access to a method its not defined under.

  • Function currying is when you present some default parameters to a function. This is what happens when we pass in parameters with the bind keyword.

  • This is different from before when we 've used coersion. This method would specify a variable if it did not exist. Function currying copies and function, and then specifies a perminate default value.

    var currying = function multiply(a,b) {
        console.log(a*b);
    }.bind(this, 2);

    currying(2);
    currying(3);
    currying(4);
  • This is essentially just a shortcut, but it also demonstrates the effectiveness of bind. Because a copy is created, and not executed, the copy can be strictly used in situations were we need them.

Functional Porgramming

  • Think and code in functions

  • Like with all programming languages, are are sloppy ways of doing things. The more we write clean and maintainable code, the fewer bugs, and better chance we have at maintainer large scale programs.

  • With js, we want to take advantage of its functional programming capabilities.

  • Consider the following bad code

    myArray = [1,2,3];
    myArray2 = [];

    for (var i = 0; i < myArray.length; i++) {
        myArray2.push(myArray[i] * 2);
    }

    console.log(myArray); //1,2,3
    console.log(myArray2); //2,4,6
  • Lets see if we can improve this

    datArrayDoe = [1,2,3];

    function doubleArray(array, funct) {
        array2 = [];
        for (var i = 0; i < myArray.length; i++) {
            array2.push(funct(array[i]));
        }

        return array2;
    }

    var doubleIt = doubleArray(datArrayDoe, function(i) {
        return i * 2;
    })

    console.log(datArrayDoe); //1,2,3
    console.log(doubleIt); //2,4,6
  • Much better. Lets track our improvements.

    • Created a generalized method for doubling any array

    • Ultilizaed functional programming to defined a parameter as a function.

      • Let allows use to more dynamically get to our goals.

      • Functional parameters can change whenever we want them to, and it cleans up a bit work.

  • For example, by simply modifying our external function, we can define a whole new functionality.

    var greaterCheck = doubleArray(datArrayDoe, function(i) {
        return i > 2;
    })

    console.log(greaterCheck); //[false, false, true]
  • One of the powers that comes with functional programming is the ability to take any complex, large scaled function, and break it down into as many pieces as you'd like.

    var dynamicCheck = function(item , limiter) {
        return item > limiter;
    }
  • For example, to avoid smart numbers, we can write our own function like this.

  • Then we can call it like this.

    var bindDynamiCheck = doubleArray(datArrayDoe, dynamicCheck.bind(this, 1));
    console.log(bindDynamiCheck);
  • Albiet, I dont really see this as a strong improvement.. What we've done is bind the number 1 as a perminate parameter into a function call, and then called it.

  • Really, we've just moved 1 number to a different location.

  • I suppose its a bit nicer. We have our function methods, and our calls are very organized.

  • One more thing we can do. Suppose that we wanted to avoid writing 'this' all the time. To solve this, we can bind 'this' to the function call.

    var dynamicCheckSimplified = function(limiter) {
        return function(limiter, item) {
            return item > limiter;
        }.bind(this, limiter)
    }

    var bindDynamiCheckSimplified = doubleArray(datArrayDoe, dynamicCheckSimplified(1));
    console.log(bindDynamiCheck);
  • Key to functional programming: Using functions as parameters, and defining those functions as a set of smaller functions.

    • With the end goal in mind being that you call the function in a single line of code, without any added complexity.

    • Notice how dynamicCheckSimplified was called with a simple parameter. It became simply defined, and reusable.

  • It is also important to know that in the process of breaking down your code more and more it best not to mutate or change any of the code in the process. That is, we want to avoid using smart numbers as much as possible.

    • So it is important to keep in mind that we are simply writing out functions that transform it from one form into a next that suits are need.

    • If we we're to use smart numbers, we may find ourselves moving back up, and change things around whenever we need them.

    • So essentially, we want the functions to be the same, but simply change shape to fit as a key to our puzzle.

Functional Programming Part 2

  • Underscore.js <-- open source js library.

    • Functions and varibles are invoked with an underscore.

  • At a glance

    var array = [1,2,3];
    var mult3 = _.map(array, function(item) { return item * 3}); 
    console.log(mult3); //[3.6.9]


    var evenArray = _.filter(array, function(item) {
        if (item % 2 === 0) {
            return item;
        }
    })

    console.log(evenArray);
PreviousSection 3 - Types and OperatorsNextSection 4 - Objects and Functions

Last updated 5 years ago

Was this helpful?