Author's profile photo

Aylwin's tech blog

Sprinkle a bit of curiosity in everything I do 🧪

JavaScript In Depth

2023-03-14

1. Execution Contexts and Lexical Environments


  1. Syntax Parsers vs Execution Contexts vs Lexical Environments
    1. Syntax parsers:
      • A program that reads your code and determines what is does and if its grammar is valid.
      • An interpreter that translates your code to machine readable code.
      • Sometimes, syntax parser can do automatic semicolon insertion for you.
      • Syntax parser is part of Javascript engine.
    2. Lexical environments:
      • Where something sits physically in the code you write.
      • The word ‘lexical’ means having to do with words or grammar.
      • Lexical environment is important because the way we see code written gives us an idea of where they actually sit in the computer’s memory.
    3. Execution contexts:
      • A wrapper to help manage the code that is running.
      • The execution context determines which lexical environments are running. It also contains things beyond what were written as JavaScript is a whole set of programs that someone else wrote.
  2. Name/Value Pairs vs Objects
    1. Name/value pairs:
      • The name may be defined more than once, but only can have one value in any given context. That value may be more name/value pairs.
    2. Object:
      • The simplest definition when talking about Javascript.
      • An object is a collection of name/value pairs.
        Screen Shot 2023-03-14 at 11.52.37 pm.png
  3. The global environment and the global object
    1. Whenever code is run in JavaScript. It is run inside an execution context.
    2. Execution context is a wrapper that wraps the currently executing code in an execution context.
    3. There’s more than one execution contexts run during a javascript program usually.
    4. The global execution context creates two things for you:
      • Window Global object
      • this variable
    5. The javascript engine creates two things whenever your code is run.
    6. What does global mean in JavaScript?
      • “Not inside a function”
    7. Summary: Screen Shot 2023-03-16 at 2.17.57 pm.png
  4. The execution context - Creation and hoisting
    1. Hoisting (No explanation, observe only)
      • Run 1:

        b();
        console.log(a)  ;
        
        var a = 'Hello World!';
        
        function b() {
          console.log('Called b!')
        }
        
        // OUTPUT
        Called b!
        Undefined
      • Run 2:

        b();
        console.log(a);
        
        function b() {
          console.log('Called b!')
        }
        
        // OUTPUT
        Called b!
        Uncaught ReferenceError: a is not defined
      • Run 3 (The code behave as if):

        var a
        
        function b() {
          console.log('Called b!')
        }
        
        b();
        console.log(a);
      • This happens because what is executing is not what is written, as it is translated by the JavaScript Engine. It is also not as simple as JavaScript Engine moving the code physically.

    2. To understand why it happens: First phase: The creation phase First phase: The creation phase
      • Before your code begins to be executed line by line, the JavaScript Engine has already set aside memory space for all the variables and functions you had created in the entire code. As a result, these functions and variables exists in memory.
      • For variables, JavaScript puts a placeholder, called undefined because it does not know what the value may end up. All variables in JavaScript are initially set to undefined.
      • Lesson to learn: Do not rely on hoisting, follow the practice to avoid troubles. Always write the code as followed so that you do not caught up in that trap.

        var a = 'Hello World!';
        
        function b() {
          console.log('Called b!')
        }
        
        b();
        console.log(a);
  5. Conceptual Aside: JavaScript and undefined. Screen Shot 2023-03-16 at 3.24.36 pm.png Screen Shot 2023-03-16 at 3.25.02 pm.png
    1. undefined: A special value that JavaScript has within it internally that means the variable has not been set.
      • It is an actual value that would take memory space.
      • It is the value that variables received during the creation phase, the first phase of creating an execution context sets up the memory of the variable.
    2. Never do the following: jsx a = undefined
      • It works but will cause troubles as we’re not sure undefined means you have assigned it or the JavaScript engine has set it. This will improve debugging.
      • It is preferably to let undefined means that ‘I, the programmer never set the value’.
  6. The execution context - Code execution Second phase: The execution phase. Second phase: The execution phase.
    1. creation phase: when setting up the variables, function and memory.
    2. execution phase: Runs the code written line by line, interpreting it, converting it and compiling it.
    3. Example output:

      function b() {
       console.log('Called b!')
      }
      
      b();
      
      console.log(a);
      
      var a = 'Hello World!';
      
      console.log(a);
      
      // OUTPUT
      Called b!
      undefined
      Hello World!
  7. Conceptual Aside: Single Threaded, Synchronous Execution
    1. Single threaded: One command is being executed at a time. JavaScript behaves in a single threaded manner.
    2. Synchronous execution: The code is executed one line at a time in the oder that it appears.
  8. Function Invocation and the execution stack
    1. Invocation: Running a function; calling a function.
    2. Every time you invoke a function in JavaScript, a new execution context is created (just like the global one) and placed on the execution stack, it will have its own space for variables and functions. It will go through that create phase, and then it will execute line by line the code within the function. Screen Shot 2023-03-16 at 11.12.08 pm.png
  9. Functions, Context, and Variable Environments

    1. Variable environment: Where the variables live. Screen Shot 2023-03-16 at 11.20.49 pm.png
    2. Even though myVar is declared 3 times. They are distinct, unique and they do not touch each other. These variables were defined in its own execution context.
    3. Run 1 (Example):

      function b() {
       var myVar;
       console.log(myVar);
      }
      
      function a() {
       var myVar = 2;
       console.log(myVar);
      }
      
      var myVar = 1;
      console.log(myVar);
      a();
      console.log(myVar);
      
      // OUTPUT
      1
      2
      undefined
      1
  10. The scope chain (Important)

    1. Run 1 (Example):
    function b() {
        console.log(myVar)
    }
    
    function a() {
        var myVar = 2;
        b();
    }
    
    var MyVar = 1;
    a();
    
    // OUTPUT
    1

    Screen Shot 2023-03-17 at 11.27.05 am.png

    • function b() lexically sits on top of the global environment.
    • When you ask for a variable while running a line of code inside any particular execution context, if JavaScript cannot find that variable, it will look at the outer reference and go look for variables there.
    • The execution stack can be very tall in practice. JavaScript may search for the variable all the way to the global execution context until it finds or it does not find. The whole chain is called the Scope Chain.
    1. Run 2 (Example):

       function a() {
           function b() {
               console.log(myVar);
           }
       
           var myVar = 2
           b();
       }
       
       var myVar = 1;
       a();
       
       // OUTPUT
       2
    2. Run 3 (Example):

       function a() {
           function b() {
               console.log(myVar);
           }
       
           b();
       }
       
       var myVar = 1;
       a();
       
       // OUTPUT
       1
  11. Scope, ES6, and Let

    1. Scope: where a variable is available in your code, and if it’s truly the same variable, or a new copy.
    2. var vs let

      varlet
      The value=undefined can be access without assigned any given value.allows block scoping, where you need to declare and assign a value before you can use it.
      It is only available inside a block {}. E.g. if statement and for-loop, at that period of time for the running code. If you have a loop to run the code over and over again, let will assign different variable in memory each time for the loop is running.
  12. Asynchronous callbacks

    • Even though the javascript engine, rendering engine and http request may be running asynchronously inside a browser. Javascript engine is synchronous.
      Screen Shot 2023-03-17 at 11.54.41 am.png
    • The event queue only get looked at by the JavaScript when the execution stack is empty.
      Screen Shot 2023-03-17 at 11.59.59 am.png

2. Types and operators


  1. Conceptual aside: Types and javascript.
    1. Dynamic typing: You do not tell the engine what type of data a variable holds, it figures it out while your code is running. Variables can hold different types of values because it is all figured out during execution. It is different from static typing. Screen Shot 2023-03-17 at 12.17.36 pm.png
    2. Primitive types: A type of data that represents a single value (That is not an object). There are 6 primitive types in javascript:
      1. Undefined: It represents lack of existence (Note: you should not set a variable to this).
      2. null: It represents lack of existence (Note: you can set a variable to this).
      3. boolean: true of false;
      4. number: There is only 1 number type in JavaScript. It is a floating point number (There is always some decimals). The drawback is sometimes it makes math a little weird.
      5. string: A sequence of characters (inside ‘’ or “”).
      6. symbol: Used in ES6 only. (Note: Not fully supported by all browsers yet.)
  2. Conceptual aside: Operators

    1. Operator: A special function that is syntactically (written) differently. Generally, operators take 2 parameters and return 1 result. Operator can be viewed as a function that JavaScript provides using infix notation.

      Types of notations:

      infixprefixpostfix
      3 + 4+3 43 4+
  3. Operator precedence and associativity
    1. Operator precedence: which operator function gets called first. Functions are called in order of precedence where higher precedence wins.
    2. Associativity: When multiple operators have the same precedence, associativity decides which one to run first.
      1. Left-to-right: || && > <
      2. Right-to-left: = += -=
    3. More details: Operator precedence - JavaScript | MDN
  4. Conceptual aside: Coercion

    1. Coercion: Converting a value from one type to another. It is often in Javascript because it is dynamically typed.
    2. Keep in mind that JavaScript will make a decision when there are different types of data.

       var a = 1 + '2';
       console.log(a);
       
       // OUTPUT
       12
  5. Comparison operators

    1. Number(null) == 0.
    2. Coercion is powerful and dangerous at the same time.
    3. null does not coerced to 0 when in comparison.

       null == 0
       // Output
       False
       
       null < 1
       // Output
       True
    4. Equality == vs strict equality === vs object.is

      1. Strict equality === does not coerce the value.
      2. In real world it is better to use strict equality ===.
      3. For more info, refer to: Equality comparisons and sameness - JavaScript | MDN
  6. Existence and Booleans
    1. Use coercion == to check if a value exists. However, if the value is 0, it is false and can cause problems.
  7. Default values

    1. name is undefined type, but it will coerced to ‘undefined’ string in concatenation.

       function greet(name) {
           console.log(name);
           console.log('Hello ' + name);
       }
       
       greet();
       
       // OUTPUT
       undefined (type)
       Hello undefined (str)
    2. Remember operators are functions that return values. In the examples below, the operators will return the value that can be coerced to true.

       undefined || "hello"
       // OUTPUT
       "hello"
       
       null || "hello"
       "hello"
       
       "" || "hello"
       "hello"
  8. Framework aside: Default values

    1. These 3 script tags (lib1.js, lib2.js, app.js) are not creating new execution context. The run from top to bottom just like a single javascript file. It is important that these files are not colliding with one another. Lib 2 was printed because they are all treated as global variables sitting inside the global execution context, and thus attached to the Window object. The second variable just replaced it.

       <html>
           <head>
           </head>
           <body>
               <script src="lib1.js"></script>
               <script src="lib2.js"></script>
               <script src="app.js"></script>
           </body>
       </html>
       // lib1.js
       var libraryName = "Lib 1";
       
       // lib2.js
       var libraryName = "Lib 2";
       
       // app.js
       console.log(libraryName);
       
       // OUTPUT
       Lib 2
    2. How to solve this behaviour? The line below check to see if there’s already a libraryName in the global execution context, which is window. If this was not set previously, it will be undefined and the second value will be returned.

       // lib2.js
       window.libraryName = window.libraryName || "Lib 2";
       
       // OUTPUT
       Lib 1

3. Objects and Functions


  1. Objects and the Dot .

    1. Remember an object is a collection of name-value pairs. Screen Shot 2023-03-21 at 4.29.46 pm.png
    2. How does an object live in your computer’s memory
      • A collection of values that are given names.
      • Object can contain properties and methods
        Screen Shot 2023-03-21 at 4.35.32 pm.png
      • The value of the object can be the following:
        • Primitive property
        • Another object property
        • A function method
      • It will have references to these different properties and methods
      • It is important to think about an object sitting in memory has references to other things sitting in memory.
    3. Different ways to create an object and access their properties.

      • Computed member access operator []:

          // Method 1
          var person = new Object();
          
          person[] // Computer member access operator []
          
          person["firstname"] = "Tony";
          person["lastname"] = "Alicea";
      • Member access .

          person.address = new Object()
          person.address.street = '111 Main St.';
      • Rule of thumb: Always use . operator unless you need to access a property using dynamic string.

  2. Objects and Objects Literals {}

    1. There are 2 ways to create an object:

       // Method 1
       var person = { firstname: 'Tony', lastname: 'Alicea' };
       
       // Method 2
       person = new Object();
       person.firstname = 'Tony'
       person.lastname = 'Alicea'
    2. Object literal syntax {} is a powerful syntax to create object in Javascript, it is clean looking and easy to write.

  3. Framework aside: Faking Namespaces

    1. Namespace: A container for variables and functions. It it typically to keep variables and functions with the same name separate.
    2. How to prevent overriding with the same name variables?

       var greet = 'Hello';
       var greet = 'Hola';
       
       console.log(greet);
    3. Solutions: Create a container/namespace object

       var english = {};
       var spanish = {};
       
       english.greet = 'Hello!';
       spanish.greet = 'Hola!';
    4. This is often seen in a lot of JavaScript source code to separate objects sitting in the global namespace from the global object that someone else might have written.

  4. JSON and Object Literals

    1. Misconception: A lot of people think JSON and object literal syntax are the same thing.
    2. JSON vs Object literal syntax

      JSONObject Literal Syntax
      { "firstName": "Mary", "isAProgrammer": true }var objectLiteral = { firstName: "Mary", isAProgrammer: true }
      • Anything that is JSON valid is also a valid JavaScript object literal syntax. However, not all object literal syntax is a valid JSON. In other words, JSON properties need be wrapped in quotes “”. JSON is technically a subset of the object literal syntax.
    3. Although JSON is not a part of JavaScript. It is so popular that JavaScript does come with built-in functionalities to transfer between the two.

      • Example 1:

        var objectLiteral = {
          firstName: "Mary",
          isAProgrammer: true
        }
        
        console.log(JSON.stringify(objectLiteral));

        Output1

      • Example 2:

        var jsonValue = JSON.parse('{ "firstname": "Mary", "isAProgrammer": true }');
        
        console.log(jsonValue)

        Output2

  5. Functions are objects [IMPORTANT]
    1. First class functions: Everything you can do with other types you can do with functions.
    2. A function is a special type of object. Just like any object, it resides in memory and has all the features of a normal object and other special properties.
    3. These special properties are:

      1. A function does not have to have a name. It can be anonymous.
      2. Code property, where the code written were placed into a special property of the function object. It’s invocable, codes sitting on the property only runs when you invoke it.

      e. You can attach a property to any function, just like an object:

      function greet() {
       console.log('hi');
      }
      
      greet.language = 'english';
      console.log(greet.language);
      
      //OUTPUT
      english

      f. An overview

      Screen Shot 2023-03-21 at 5.51.47 pm.png

      g. The function written has

      • A name property
      • A code property
  6. Function statements vs function expressions

    1. Expression: A unit of code that results in a value.

      • Example:

        a = 3;
        // Return
        3
        
        1 + 2;
        // Return
        3
        
        a = { greeting: 'hi' };
        // Return
        *object {greeting: 'hi'}*
    2. Statement: A unit of code that does not return any value

      • Example:

        if (a === 3) { }
    3. Statement vs Expression in JavaScript

      • Function statement

        Screen Shot 2023-03-21 at 9.09.25 pm.png

          greet()
          
          // Function statement
          function greet() {
              console.log('hi');
          }
          
          // OUTPUT
          hi
      • Function expression

        Screen Shot 2023-03-21 at 9.13.04 pm.png

          // Function expression
          var anonymousGreet = function() {
              console.log('hi'); 
          }
          
          anonymousGreet()
          
          // OUTPUT
          hi
      • Example of printing object and function

          function log(a) {
              console.log(a);
          }
          
          // (1) Print an non-function object
          log({ greeting: 'hi });
          // OUTPUT
          *Object* {greeting: 'hi'}
          
          // (2) Print a function object
          log(function() {
              console.log('hi')
          });
          // OUTPUT
          function () {
              console.log('hi);
          }
      • How to run a function inside a function? Passing a function as a parameter to use it, called functional programming.

        function log(a) {
          a();
        }
        
        log(function() {
          console.log('hi');
        });
        
        // OUTPUT
        hi
  7. Conceptual aside: by value vs by reference

    1. Primitives (non-objects) vs objects

      // Primitive
      // By value
      var a = 3;
      var b;
      
      b = a;
      a = 2;
      
      console.log(a);
      console.log(b);
      
      // OUTPUT
      2
      3
      
      // Objects
      // By reference
      var c = { greeting: 'hi' };
      var d;
      
      d = c;
      c.greeting = "HELLO"; // mutate
      
      console.log(c);
      console.log(d);
      
      //OUTPUT
      HELLO
      HELLO
    2. Applied to parameters

      // by reference (even as parameters)
      function changeGreeting(obj) {
       obj.greeting = 'Hola'; // mutate
      }
      
      changeGreeting(d);
      console.log(c);
      console.log(d);
      
      // OUTPUT
      Hola
      Hola
    3. Equal operator

      1. Since { gretting: ‘howdy’ } did not exist in the memory, it will be added in new memory location and c object will be pointed to the new memory address.

        c = { greeting: 'howdy' } // Because this did not exist in memory yet
        console.log(c);
        console.log(d);
        
        //OUTPUT
        howdy
        Hola
    4. In other programming languages, you can decide whether a value is passed by value or by reference. In JavaScript, there is no such option.

      • All primitive types are passed by value.
      • All objects are passed by reference.
  8. Objects, Functions and this

    1. When an execution context is created (every time a function is run), it gives us a variable called this, which can be useful. this will be pointing to different objects depending on how the function is invoked. Screen Shot 2023-03-22 at 2.46.14 pm.png
    2. Example of this pointing to the global window object.
    3. When does this point to non global object?
    4. How to solve the javascript “bug”?

      • Let’s observe the bug

        var c = {
          name: 'The c object',
          log: function() {
              this.name = 'Updated c object';
              console.log(this);
        
              var setname = function(newname) {
                  this.name = newname; // Pointing to global object !
              }
        
              setname('Updated again! The c object');
              console.log(this);
        
          }
        }
          
        c.log()
        
        // OUTPUT
        *Object* {name: "Updated c object", ...}
        *Object* {name: "Updated c object", ...}
      • How to fix this ‘bug’ in javascript?

        var c = {
          name: 'The c object',
          log: function() {
              var self = this; // Step 1: Make self pointing to the same location memory as the this keyword
        
              this.name = 'Updated c object';
              console.log(self); 
        
              var setname = function(newname) {
                  self.name = newname; // Pointing to global object !
              }
        
              setname('Updated again! The c object');
              console.log(self);
        
          }
        }
        
        c.log()
        
        // OUTPUT
        *Object* {name: "Updated c object", ...}
        *Object* {name: "Updated again! The c object", ...}
  9. Conceptual aside: Arrays - collections of anything

    1. Javascript can store different types of data in an array. It can store primitives, objects and functions.

       var arr = [1, 
                   false, 
                   "Hello world", 
                   { name: "Tony" },  
                   function(name) {
                       var greeting = "Hello ";
                       console.log(greeting + name);
                   }
       ];
    2. We can perform different operations with the array

      arr[3](arr[2].name);
      
      // OUTPUT
      Hello Tony
    3. The advantage comes from its dynamic typing nature.

  10. Objects and Functions

    1. In addition to these things, the JavaScript engine also sets up arguments.
    2. Arguments is a keyword of JavaScript that contains all the parameters you pass to a function.
    3. Example 1:

       function greet(firstname, lastname, language){
           language = language || 'en';
       
           if(arguments.length === 0) {
               console.log('Missing parameters!');
               return;
           }
       
       }
    4. parameter wraps up the rest of parameters in an array:

       function greet(firstname, lastname, language, ...other){
           language = language || 'en';
       
           if(arguments.length === 0) {
               console.log('Missing parameters!');
               return;
           }
       
       }
  11. Framework aside: function overloading

    1. Example 1:

       function greet(firstname, lastname, language) {
           language = language || 'en';
       
           if (language === 'en') {
               console.log('Hello' + firstname + ' ' + lastname);
           }
       
           if (language === 'en') {
               console.log('Hola' + firstname + ' ' + lastname);
           }
       }
       
       function greetEnglish(firstname, lastname) {
           greet(firstname, lastname, 'en');
       }
       
       function greetSpanish(firstname, lastname) {
           greet(firstname, lastname, 'es')
       }
       
       greetEnglish('John', 'Doe')
       greetSpanish('John', 'Doe')
  12. Conceptual aside: Syntax Parsers

  13. Dangerous aside: automatic semicolon insertion (by syntax parsers) ‼️

    1. The automatic semicolon insertion in writing return can cause big problem:

       function getPerson() {
           return // Syntax parser will insert ; for you (return ;)
           {
               firstname: 'Tony'
           } // <--- Without ';'
       }
       
       console.log(getPerson());
       
       // OUTPUT
       undefined
       // Solution 1: Insert { in the same line after the return keyword
       function getPerson() {
           return { 
               firstname: 'Tony'
           } 
       }
       
       // Solution 2: Always insert ;
       function getPerson() {
           return { 
               firstname: 'Tony'
           };
       }
    2. Always write your own semicolon!

  14. Framework aside: Whitespace 🗽

    1. JavaScript is very liberal on whitespace. Take as much space as you needed to comment the code and make it readable. It is also often seen in a lot of source code. There is also no disadvantage of doing this.

       var 
               // comment 1
               firstname, 
               
               // comment 2
               lastname,
       
               // comment 3
               language;
       
       var person = {
               // the first name
               firstname: 'John';
       
               // the lastname
               // always required
               lastname: 'Doe'
       
       }
  15. Immediately invoked functions expressions (IIFEs)

    1. What is IIFEs (Comparison code)

       // Function statement
       // Put in memory initially
       function greet(name) {
           console.log("Hello " + name);    
       }
       
       greet();
       
       // Function expression
       // Does not put in memory initially
       var greetFunc = function() {
           console.log("Hello " + name);    
       };
       
       greenFunc();
       
       // Using an Immediately Invoked Function Expression (IIFE)
       // It is a function expression
       var greeting = function(name) {
           console.log('Hello ' + name)
       }();
    2. Make sure you invoke the function with ().

       // Given a function expression
       var greeting = function(name) {
           return 'Hello ' + name;    
       }
       
       // (1) DID NOT invoke the function object
       console.log(greeting);
       
       // OUTPUT
       function (name) {
           return 'Hello ' + name;
       }
       
       // (2) Invoke the function object
       console.log(greeting('John'));
       
       // OUTPUT
       Hello John
    3. Immediately Invoked Function Expression (IIFE) examples

       // Example 1: Without parameter
       var greeting = function(name) {
           return 'Hello ' + name;
       }();
       
       console.log(greeting)
       
       // OUTPUT
       Hello undefined
       
       // Example (2): With parameter
       var greeting = function(name) {
           return 'Hello ' + name;
       }('John');
       
       console.log(greeting) // <-- Greeting contains a string now
       
       // OUTPUT
       Hello John
    4. There are different ways to run an expression without storing in a variable as followed:

       // (1) Run an expression without storing in a variable
       3; 
       
       // (2) 
       "I am a string"
       
       // (3)
       {
           name: 'John'
       };
    5. However, when it comes to function. The writing below is not valid:

       // (4) This is not valid
       function(name) {
           return 'Hello ' + name;
       }
       
       // OUTPUT
       Uncaught SyntaxError: Unexpected token (
      This is because there is no name given to the function. However, if a name is given to the function. It becomes a function statement (we do not want that).
    6. There are a few methods to trick the syntax parser in to understanding that we don’t intend to write a function statement as followed:

       // METHOD 1: Wrap the function in a parenthesis (), so that 'f' is the first word
       (function(name) {
           return 'Hello ' + name;
       });
    7. How to creating a function and running it on-the-fly (without storing it):

       var firstname = 'John'
       
       // Using () to trick the syntax parser into thinking it is an expression
       // IIFE
       (function(name) {
           var greeting = 'Hello';
           console.log(greeting + ' ' + name);
       }(firstname));
       
       // OUTPUT
       Hello John
    8. In summary, we learnt how to write a classic IIFE as followed:

       (function() {
           var greeting = 'Hello';
           console.log(greeting + ' ' + name);
       }());
      As a javascript developer, you see `IIFE` form/style in almost every major framework and library that’s out there today. Get familiar with it.
  16. Framework aside: IIFEs and Safe Code

    1. Let’s understand the IIFE step-by-step below: Since the variable greeting declared inside the function is sitting on its own execution context. It is not touching the global execution context and it is useful.

       // IIFE
       (function(name)) {
           var greeting = 'Hello';
           console.log(greeting + ' ' + name);
       }('John'));
      ![Screen Shot 2023-03-23 at 9.40.06 pm.png](/images/js/Screen_Shot_2023-03-23_at_9.40.06_pm.png)
    2. This feature allows us to write crash-less code. The greeting variable below exists in a separate execution context (separate addresses). Screen Shot 2023-03-23 at 9.44.31 pm.png
    3. What if we want to access the global variable? Just pass by reference.

       greeting = 'Hola'
       
       (function(global, name) {
           var greeting = 'Hello';
           global.greeting = 'Hello'; // Override by reference
           console.log(greeting + ' ' + name);
       }(window, 'John'));
       
       console.log(greeting); 
       
       // OUTPUT
       Hello
    4. In summary, we insured that our code, through wrapping it in an immediately invoked function, does not interfere with, crash into, or be interfered by any other code that might included in the application. It is safe. This pattern makes it difficult to affect the global object.

  17. Understanding closures (Notoriously difficult to understand):

    1. Examples (This is only possible with closures):

       function greet(whattosay) {
           return function(name) {
               console.log(whattosay + ' ' + name);
           }
       }
       
       // Example 1
       greet('Hi')('Tony'); //Invoke a function that returns a function.
       
       // Example 1 - OUTPUT
       Hi Tony
       
       // Example 2
       var sayHi = greet('Hi')
       sayHi('Tony')
       
       // Example 2 - OUTPUT
       Hi Tony
    2. In the example 2 (refer to the code). Here’s the step by step explaining the process:

      Screen Shot 2023-03-24 at 11.29.29 pm.png

      Screen Shot 2023-03-24 at 11.38.57 pm.png

      Step 1: Global execution context is created.

      Step 2: When the code reaches var sayHi = greet(’Hi’), a new execution context is created.

      Step 3: The string ‘hi’ gets passed into the greet()variable environment.

      Step 4: When the code reached return, the greet()'s execution context is popped off the stack (The variables and functions created inside of it still live in the space in memory)

      Step 5: Back to global context, now sayHi(’Tony’) function is invoked and a new execution context is created.

      Step 6: The string ‘Tony’ gets passed into the anonymous function() and is now sitting inside its variable environment.

      Step 7: When the code reaches the line console.log(whattosay + ‘ ‘ + name);. The javascript engine goes up the scope chain (since it could not find it inside the function) and look for the variable in the outer environment (greet()'s function memory)

  18. Understanding closures - Part 2

    1. Why does the function output 3?

       function buildFunctions() {
           var arr = [];
      
           for (var i = 0; i < 3; i++) {
               arr.push(
                   function() {
                       console.log(i);
                   }
               )
           }
           
           return arr;
       }
      
       var fs = buildFunctions();
      
       fs[0]();
       fs[1]();
       fs[2]();
      
       // OUTPUT
       3
       3
       3

      Untitled

    2. How to change the function so that it prints 0,1,2 instead?

       // Method
       function buildFunctions() {
           var arr = [];
       
           for (var i = 0; i < 3; i++) {
               arr.push(
                   (function(value){ // (1) Closure
                       return function() {
                           console.log(j);
                       }
                   }(i))  // (2) IIFE
               )
           }
           
           return arr;
       }
       
       // OUTPUT
       0
       1
       2
  19. Framework Aside: function Factories

    1. Function closures allow us to declare the function only once, without passing the same argument every time.
    2. Example:

       function makeGreeting(language) {
           return function(name) {
               
               if (language === 'en') { console.log('Hello ' + name) }
       
               if (language === 'es') { console.log('Hola ' + name) }
           }
       }
       
       var greetEnglish = makeGreeting('en')
       var greetSpanish = makeGreeting('es')
       
       greetEnglish('Tony')
       greetSpanish('es')
  20. Closures and Callbacks:

    1. A classic function that using function expression:

       function sayHiLater() {
           var greeting = 'Hi!';
       
           setTimeout(function() { // 1. Make use of first-class function. We're passing an (1) function object and (2) a time parameter to the setTimeout function
       
                   console.log(greeting); // 2. The power of closures
       
           }, 3000);
       }
       
       sayHiLater();
       
       // OUTPUT
       Hi! // Showed after 3 seconds
    2. JQuery uses function expressions and first-class functions:

       #('button').click(function() {
           // Your code
       });
    3. Callback function: A function you give to another function, to be run when the other function is finished. (So the function you call, ‘calls back’ by calling the function you gave it when it finishes.)
    4. A callback function example:

       function tellMeWhenDone(callback) {
           var a = 1000; // work A
           var b = 2000; // work B
       
           callback(); // the 'callback', it runs the function I give it!
       }
       
       tellMeWhenDone(function() {
           console.log('I am done'); 
       });
  21. call() vs apply() vs bind()

    1. bind(): It does not execute/invoke the function immediately.

       var person = {
           firstname: 'John',
           lastname: 'Doe',
           getFullName: function() {
               var fullname = this.firstname + ' ' + this.lastname;
               return fullname;
           }
       }
       
       var logName = function(lang1, lang2) {
           console.log('Logged: ' + this.getFullName());
           console.log('Arguments: ' + lang1 + ' ' + lang2);
           console.log('------------');
       }
       
       var logPersonName = logName.bind(person);
       
       logPersonName('en');
       
       // OUTPUT
       Logged: John Doe
       Arguments: en undefined
       ------------
    2. call()

       logName.call(person, 'en', 'es');
       
       // OUTPUT
       Logged: John Doe
       Arguments: en es
       ------------
    3. apply()

       logName.apply(person, ['en', 'es']);
       
       // OUTPUT
       Logged: John Doe
       Arguments: en es
       ------------
    4. Do it on-the-fly:

       (function(lang1, lang2) {
           console.log('Logged: ' + this.getFullName());
           console.log('Arguments: ' + lang1 + ' ' + lang2);
           console.log('------------');
       }).apply(person, ['en', 'es']);
    5. function borrowing: Borrow a function but use on another object.

       var person2 = {
           firstname = 'Jane';
           lastname = 'Doe';
       }
       
       console.log(person.getFullName.apply(person2));
       
       // OUTPUT
       Jane Doe
    6. Function currying: Creating a copy of a function but with some preset parameters. It is very useful in mathematical situations. The idea is to have some fundamental functions that we can then build on with some other default parameters. The examples shown as bind() method.

      1. Example 1 (pass in 1 parameter): It is setting a permanent value for the first parameter. Whatever pass will be that second parameter.

         function multiply(a, b) {
             return a*b;
         }
         
         var multipleByTwo = multiply.bind(this, 2);
         console.log(multipleByTwo(4))
         
         // OUTPUT
         8
        It is equivalent to the following:
         function multipleByTwo(b) {
             var a = 2;
             return a * b;
         }
      2. Example 2 (pass in 2 parameters): The output will be 2*2=4 and ignored the passed in value 5.

         var multipleByTwo = multiply.bind(this, 2, 2);
         console.log(multipleByTwo(5));
         
         // OUTPUT
         4
      3. Example 3 (multiple functions):

         var multipleByTwo = multiply.bind(this, 2);
         console.log(multipleByTwo(4));
         
         var multipleByThree = multiple.bind(this, 3);
         console.log(multipleByThree(4));
         
         // OUTPUT
         8
         12
  22. Functional programming [IMPORTANT]

    1. JavaScript has many common with other programming languages such as Lisp, Scheme or ML. These languages have first class functions as discussed.
    2. Unlike c++, c# and java. Let’s examine the beauty of functional programming in javascript:

      • The problem:

          var arr1 = [1,2,3];
          console.log(arr1);
          
          var arr2 = [];
          for (var i = 0; i < arr1.length; i++) {
              arr2.push(arr1[i] * 2)
          }
          
          console.log(arr2);
      • The solution with functional programming:

        Example 1

          function mapForEach(arr, fn){
          
              var newArr = [];
              for(var i = 0; i < arr.length; i++){
                  newArr.push(fn(arr[i]));
              }
          
              return newArr;
          
          }
          
          /***************************************/
          
          var arr1 = [1,2,3];
          
          // Work 1
          var arr2 = mapForEach(arr, function(item) {
              return item*2;
          }); 
          
          console.log(arr2);
          
          // Work 2
          var arr3 = mapForEach(arr, function(item) {
              return item > 2;
          });
          
          console.log(arr3); 
          
          // OUTPUT
          [2,4,6]
          [false, false, true]
        Example 2:
          var checkPassLimit = function(limiter, item) {
              return item > limiter;
          }
          
          var arr4 = mapForEach(arr1, checkPastLimit.bind(this, 1));
          
          console.log(arr4)
          
          // OUTPUT
          [false, true, true]
        Example 3 (simplified example 2 without repeating `bind()`):
          var checkPassLimit = function(limiter){
              return function(limiter, item) {
                  return item > limiter;
              }.bind(this, limiter);
          }
          
          var arr5 = mapForEach(arr1, checkPassLimit(1));
          
          console.log(arr5);
          
          // OUTPUT
          [false, true, true]
  23. Functional programming - Part 2

    Underscore.js

    Lodash

    Go through these documentations on how they write modular JavaScript code.

4: Object-Oriented JavaScript and Prototypal Inheritance


  1. Conceptual Aside: Classical vs Prototypical Inheritance

    1. Inheritance: One object gets access to the properties and methods of another object.
    2. Classical vs prototypal inheritance

      Classical inheritancePrototypal inheritance
      LanguagesC#, JavaJavascript
      VersatilityVerboseSimple, extensible, easy to understand
      Keywordsfriend, protected, private, interface
  2. Understanding the prototype.

    1. All objects in JavaScript, including functions have a prototype property.
    2. Example 1 of the prototype chain:

       var person = {
           firstname: 'Default',
           lastname: 'Default',
           getFullName: function() {
               return this.firstname + ' ' + this.lastname;
           }
       }
       
       var john = {
           firstname: 'John',
           lastname: 'Doe'
       }
       
       // You don't want to use it 
       // It can cause slow-down performance issue
       // __ make sure you don't accidentally type it
       john.__proto__ = person; // John now inherits from the person
       console.log(john.getFullName());
       console.log(john.firstname());
       
       // OUTPUT
       John Doe
       John
    3. Example 2 of the prototype chain:

       var jane = {
           firstname: 'Jane'
       }
       
       jane.__proto__ = person;
       
       console.log(jane.getFullName());
       
       // OUTPUT
       Jane Default
  3. Everything is an Object (or a primitive)

    1. Example 1:

       var a = {};
       var b = function() { };
       var c = [];
       
       a.__proto__ 
       b.__proto__
       c.__proto__
       
       // OUTPUT
       *Object* { } // toString
       
       *function* Empty() { } // call, bind, apply functions, it goes to the prototype and find it
       
       [] // indexOf, push, length functions
    2. Example 2:

       b.__proto__.__proto__
       c.__proto__.__proto__
       
       // OUTPUT
       *Object* { }
       *Object* { }
  4. Reflection and Extend

    1. Reflection: An object can look at itself, listing and changing its properties and methods.
    2. This allows us to implement a useful pattern called extend.
    3. Example 1: for-in access every property and method not just on the object but also on the object’s prototype.

       // Suppose we have the code in this block
       var person = {
           firstname: 'Default',
           lastname: 'Default',
           getFullName: function() {
               return this.firstname + ' ' + this.lastname;
           }
       }
       
       var john = {
           firstname: 'John',
           lastname: 'Doe'
       }
       
       john.__proto__ = person;
       // Let's take a look at 'for-in'
       // This allows us to loop over every property and method in this object
       for (var prop in john) {
           console.log(prop + ': ' + john[prop]);
       }

      Output of the code

    4. Example 2 (What if we only want to access what’s on the object itself?):

       for (var prop in john) {
           if (john.hasOwnProperty(prop)) {
               console.log(prop + ': ' + john[prop]); // <-- reflection: 
                                                                                            // set john's properties and methods 
                                                                                            // with the brackets operator
           }
       }

      Output of the code above

    5. Shown in the above code, this concept can let us implement a useful idea as followed:
    6. Example 1: If I output john, he now has the address from the jane object and getFirstName() function from the jim object. John also has his prototype (the person object). Unlike using __proto__, these functions and properties sit physically inside the john object.

       var jane = {
           address: '111 Main St.',
           getFormalFullName: function() {
               return this.lastname + ', ' + this.firstname;
           }
       }
       
       var jim = {
           getFirstname = function() {
               return firstname;
           }
       }
       
       // NOTE: using _underscore.js
       // This method takes all the properties and methods of these other objects
       // that I give it and adds them to 'john' directly. 
       _.extend(john, jane, jim); 
       
       console.log(john);

      Output of the code above

    7. Refer to underscore.js library. The code loops through individual properties of jim and jane and adds them to John. It is copying over a reference to those properties and methods.
    8. It is useful to understand how to use reflection to have this extend pattern, not just the prototype pattern to combine and compose objects!

5: Building Objects


  1. Function Constructors, new, and the History of JavaScript.

    1. Besides object literal syntax { }, there are different ways to construct an object.
    2. Unlike Java, JavaScript does not have classes although there is a class keyword.
    3. Let’s see function constructors and the keyword new.
    4. Example 1:

       function Person() {
           this.firstname = 'John';
           this.lastname = 'Doe';
       }
       
       var john = new Person();
       
       console.log(john);
       
       // OUTPUT
       *Person* {firstname: "John", lastname: "Doe" } // an object
    5. In previous lessons, we learnt the wrong way of setting the prototype.
    6. In this lesson, we will examine various ways that are accepted to create objects in JavaScript. This includes adding properties, adding methods, and setting the prototype.
    7. The new keyword was basically introduced to make Java and similar language developer to feel comfortable. It is an operator.
    8. Example 1 (function constructor):

       function Person(firstname, lastname) {
           console.log(this);
           this.firstname = firstname;
           this.lastname = lastname;
           console.log('This function is invoked.');
       }
       
       var john = new Person('John', 'Doe');
       console.log(john);
       
       var jane = new Person('Jane', 'Doe');
       console.log(jane);

      Output of the code

    9. Function constructor: A normal function that is used to construct objects. The this variable points a new empty object, and that object is returned from the function automatically.

  2. Function Constructors and .prototype

    1. How do we set the prototype, which is an important part of creating objects in JavaScript.
    2. When you use a function constructor, it already set the prototype for you.
    3. All function in JavaScript has a prototype property. However, It’s never used unless you use the new operator to invoke your function. In other words, it is used only when you’re using a function as a function constructor. Specifically, when you’re using a function to build objects in this special way.

    4. Important: The prototype property on a function is not the prototype of the function, it is the prototype of any objects created.
    5. Example 1 (.prototype): This allows us to add something to the prototype property on the fly.

       function Person(firstname, lastname) {
           console.log(this);
           this.firstname = firstname;
           this.lastname = lastname;
           console.log('This function is invoked.');
       }
       
       Person.prototype.getFullName = function() {      // Prototype
           return this.firstname + ' ' + this.lastname;
       }
           
       var john = new Person('John', 'Doe');
       console.log(john);
       
       var jane = new Person('Jane', 'Doe');
       console.log(jane);

      Output of the code. John and Jane objects now have getFullName() function.

    6. Example 2 (Why are we not adding it inside the person’s object?):

       function Person(firstname, lastname) {
           console.log(this);
           this.firstname = firstname;
           this.lastname = lastname;
           this.getFullName = function() {      // Why not doing this ?
               return this.firstname + ' ' + this.lastname;
           }
           console.log('This function is invoked.');
       }
      **Answer**: Efficiency standpoint: Functions in JavaScript are objects and they take up memory space. We do not want every object has its own getFullName property. If we add it to the prototype, we only have one (solved the problem!).
  3. Dangerous aside: ‘new’ and functions

    1. What if we forgot to put the new keyword when creating objects?
    2. Example 1:

       var john = Person('John', 'Doe');
    3. It will return undefined.
    4. Solution 1 : Any function that is intended to be a function constructor has a capital first letter.
    5. Solution 2: Some programs, known as Linters warn you when the function did not start with a capital letter when new keyword is used (Not a great solution).
    6. In summary, always write your function name starting with a capital letter when you’re using it as function constructor.
  4. Conceptual aside: Built-in function constructors

    1. What are the function constructors that are ready-to-use inside the JavaScript engine.
    2. Example 1: The output is an object (not a primitive value). Because it is an object, there’s a prototype property which all number objects will have access to.

       var a = new Number(3)
       
       a
    3. Example 2: There are cases where the JavaScript engine wraps up the primitive in its object for you so that you can use properties and methods you might want to.

       "John".length
       
       // OUTPUT
       4
    4. Example 3:

       var a = new Date("3/1/2015")
       
       a

      Output of the code

    5. We can access those built-in functions when writing the following code. Note that these functions do not live in object a, but rather on the date’s .prototype property.

       Date.prototype.*{ getSeconds(), getTime(), getDate(), getMilliseconds() }*
    6. We can add our own method to the prototype as followed:

       String.prototype.isLengthGreaterThan = function(limit) {
           return this.length > limit;
       }
       
       console.log('John'.isLengthGreaterThan(3));
    7. This is the power of prototype inheritance in JavaScript.
    8. Many libraries and frameworks used this technique to add features.
    9. Be careful, you do not want to override the existing property and method.
  5. Dangerous aside: Built-in function constructors.

    1. Let’s see why built-in constructors for primitive types (boolean, number, string) are dangerous.
    2. Example 1: You are not creating primitives when using built-in function constructors.

       // DANGER 1
       var a = 3
       var b = new Number(3)
       
       a == b    // b is coerced to primitive
       // OUTPUT
       true
       
       a === b   // a is a primitive whereas b is an object
       // OUTPUT
       false
    3. In general, it is better to not use built-in function constructors when creating primitives.
    4. What about dates? It is recommended that you use momentjs.com library. This helps out with some problems within that built-in constructor.
  6. Dangerous aside: Arrays and for…in.

    1. Remember that array is an object.
    2. Example 1: Arrays are object and it can iterate down into the prototype.

       Array.prototype.myCustomFeature = 'cool';
       
       var arr = ['John', 'Jane', 'Jim'];
      Let’s say we iterate the object with `for…in`:
       for (var prop in arr) {
           console.log(prop + ': ' + arr[prop]);
       }

      Output of using for…in

      Problem: We get that extra property when using for…in.

    3. Solution: In arrays, it is recommended that we do not use for…in. Use the standard for loop to iterate through the length of the array as followed to avoid such problem:

       for (var i = 0; i < arr.length; i++) {
           console.log(i + ': ' + arr[i]);
       }
  7. Object.create and Pure Prototypal inheritance.

    1. Example 1: The code below illustrates pure prototypal inheritance. We create an object john that inherits from the person object. We then change the values of those properties by overriding them.

       // Note that objects do not create new execution context
       var person = {
           firstname: 'Default',
           lastname: 'Default',
           greet: function() {
               return 'Hi ' + this.firstname;
           }
       }
       
       var john = Object.create(person)
       john.firstname = 'John';
       john.lastname = 'Doe';
       console.log(john);

      Output of the code

    2. When the JavaScript engine (older one) does not support Object.create?
      1. Answer: Polyfill.
    3. Polyfill: A code that adds a feature which the engine may lack.
    4. Example 1 (Polyfill code):

       if (!Object.create) {
           Object.create = function (o) {
               if(arguments.length > 1) {
                   throw new Error('Object.create implementation' + ' only accepts the first parameter.');
               }    
       
               function F() { }
               F.prototype = o;
               return new F();
           }
       }
    5. The summary of Object.create() (Prototypal inheritance):
      1. Create an object that forms the basis of my constructing all other objects. In our case, it is the person object.
      2. Use Object.create to create other versions of it.
      3. Override and hide the newly created object’s properties.
    6. This is a powerful concept as you can mutate or change the prototype on the fly at any point in the application.
  8. ES6 and Classes.

    1. We had seen that JavaScript does not classes. In ES6, it has class keyword in a different way.
    2. Class in ES6:

      Screen Shot 2023-04-07 at 3.30.59 pm.png

      1. A Javascript class defined an object.
      2. It has a constructor that acts like the constructor function so that we can preset its value.
    3. Problems:
      • For people coming from other programming languages, they might think it’s like a template.
      • However, the class person is actually just an object.
      • We’re just creating new objects from that object.
    4. Worries:
      • People coming from these programming languages might not appreciate the beauty of prototypal inheritance and start design object structures they way they do in C#, Java or C++. This can be a huge mistake.
    5. In this case, how do we set the prototype?

      The use of `extends` keyword of setting the prototype properties.

      The use of extends keyword of setting the prototype properties.

    6. In summary, the create of objects are the same despite there are new syntax in ES6 for creating these objects. These syntax are just syntactic sugar.

6. Odds and Ends


  1. Initialization: Don’t be intimidated by the initialization like the following code.

     var people = [
         {
             // the john's object
             firstname: 'John',
             lastname: 'Doe',
             addresses: [
                 '111 Main St.',
                 '222 Third Str.'
             ]
         },
         {
             // the jane's object
             firstname: 'Jane',
             lastname: 'Doe',
             addresses: [
                 '333 Main St.',
                 '444 Fifth St.'
             ],
             greet: function() {
                 return 'Hello!';
             }
         }
     ]
    If you’re missing a `,`, you can expect errors as followed:
    • Unexpected errors.

7. References


JavaScript: Understanding the Weird Parts