JavaScript Notes

		
// ----------------------------------------
// JAVASCRIPT UNDERSTANDING THE WEIRD PARTS
// ----------------------------------------

/* Syntax Parsers, Execution Contexts and Lexical Environments

    Syntax Parser: a program that reads your code and determines what it does and if its grammar is valid
    Your Code -> Computer Instructions (By a Compiler or Interpreter)

    Lexical Environment: Where something sits physically in the code you write
    The variable sits lexically inside a function 

    Execution Context: A wrapper to help manage the code that is running
    Contains your running code and can contain other things beyond this

    Name/Value Pairs and Objects
    name/value pair: a name which maps to a unique value [name is assigned to a value in any given context]
    address = '100 Main St' [name is address value is "100 Main St"]
    Object is a collection of name value pairs 
*/

/* The Global environment and the global Object
        Global = "not inside a function"
        The global execution context [the thing thats accessible to everything and everywhere in your code]
        JS engine creates Global Object and this
        blank.js file still creates an execution context [this defaults to Window]
        There's always a global object in JS execution context
        An execution context was created at the globa level [available to all code running inside that lexical environment]
        Special variable [global object] called this = window was created
*/

    var a = "Hello World";
    function b() { }

/* a and b are added to the global object [window in browsers] */

/* The Execution Context and Hoisting 
*/

    b();
    console.log(a);

    var a = 'Hello World';
    function b() {
        console.log('Called b!');
    }

    /*  Called b!
        undefined
        Function still ran even though it was called before it was declared 
    */

    /* Execution Context is created in 2 phases
        CREATION phase [this is always created in an execution context] 
        It recognizes where you created variables and function
        It sets up the memory space for variables and functions "Hoisting" variables = undefined functions exist in their entirety
        Before your code begins to be executed line by line the JS engine has already put them in memory space [name and code]
        Before the execution phase it adds an undefined placeholder
        Functions are put into memory in their entirety
        We have access to var/funcs because they are sitting in memory before they are declared in your code
    */

    var a;
    console.log(a); // undefined
    if (a === undefined) {
        console.log('a is undefined');
    }
    else {
        console.log('a is defined');
    }

    /* Javascript and undefined
        Undefined a special value keyword that JS has internally that proves the value has not been set
        if you don't define A you wil get an Uncaught Reference error
        Never do a = undefined; its a little dangerous
        Makes it hard to tell if you set it or if JS did
    */

    /* Single threaded: one command at a time
        JS isnt the only thing in the browsers
        Synchronous: one at a time and in order
        AJAX A=Asynchronous
        JS is synchronous        
    */

    /* Function invocation and the execution stack
        Invocation: Running or calling a function by using ()
    */

    function b() {

    }

    function a() {
        b();
    }
    a();

    /*
        Global execution context is created 
        Creation phase create this [global object]
        Attach functions in the memory space a and b will be in memory
        once it hits a() it creates execution context and puts it on the execution stack
        Whichever one is on top is the one that is currently running
        It goes through the create phase, and creates execution context as they are found
        Once it finishes its popped off of the stack        
    */

    /* Variable Environment: Where the variables live and how they relate to each other in memory 
    */

    function b() {
        var myVar;
    }

    function a() {
        var myVar = 2;
        b();
    }

    var myVar = 1;
    a();

    /* Global execution context is created
        myVar = 1
        invocation of a -> new execution context is created for a
        myVar = 2
        invocation of b -> new execution context is created for b
        myVar = undefined
    */

/* The Scope Chain 
    JS engine looks in more than the current execution context
    B's and A's outer environment is global execution context
    If it cant find that variable it will look at the outer reference 
    b sits lexically in global
    a sits lexically in global
    it couldnt find myVar so it went down the scope chain to outer reference [global]
*/

    function b() {  // lexically in the global environment object
        console.log(myVar);
    }

    function a() {
        var myVar = 2;
        b();
    }

    var myVar = 1;
    a();
    // outputs 1 
    function a() {
        function b() {
            console.log(myVar);
        }

        var myVar = 2;
        b()
    }
    var myVar = 1;
    a();    

/* because b is inside a, it changed the outer reference to a [down the scope chain instead of global go to a] */

/* Scope, ES6 and let
    Scope: where a variable is available in your code and if it is truly the same variable or a new copy
    let = var with benefits [block scoping]
*/    

    if (a > b) {
        let c = true; // its still in memory but cant be used outside the block {}
    }


/* Asynchronous callbacks 
    Asynchronous: more than one a time
    Browser 
        Javascript engine hooks into other parts of the browser
        Rendering engine
        HTTP request
    When the stack is empty then JS looks at the Event Queue 
    Event queue wont be processed until the execution stack is empty
*/

// long running function
(function() {
    function waitThreeSeconds() {
        var ms = 3000 + new Date().getTime();
        while (new Date() < ms) {}
        console.log('finished function');
    }
    function clickHandler() {
        console.log('click event');
    }
    // listen for the click event
    document.addEventListener('click', clickHandler);

    waitThreeSeconds();
    console.log('finished execution');
})();

/*
    Types and Javascript
    Dynamic Typing: You dont tell the engine what type of data a var holds, it figures it out while your code is running
    variables can hold different types of values because its all figured out during execution
*/
(function() {
    // Static Typing
    // bool isNew = 'hello'; // an error
    // Dynamic Typing
    var isNew = true; // no errs
    isNew = 'yup!';
    isNew = 1;
})();

/* 6 Primitive types in JS: a type of data that represents a single value, that is not an object.
    undefined represents a lack of existence (dont set to undefined let engine use it)
    null represents lack of existence (you can set a variable to this)
    boolean true or false
    number floating point number (there's always some decimals) unlike other languages theres only one number type can make math WEIRD
    string a sequence of characters (' and " can be used)
    symbol used in es6 (constructed and not fully supported)
*/

/* Operators: are special functions that are syntactically (written) differently, generally they take 2 parameters and return one result
    + addition operator (function)
    - subtraction operator (function)
    > greater than operator (function) boolean
    < less than operator (function) boolean
    * multiply operator (function)
    / divide operator (function)
    % modulus (remainder) operator (function)
    = assignment operator (function)
    == equality operator (function) boolean
    === strict equality operator (function) boolean
    != not equal operator (function) boolean
    !== strict not equal operator (function) boolean

    Uses In fix notation (operator sits between parameters) instead of prefix notation or postfix notation 
*/
(function(){
    var a = 3 + 4;
    console.log(a);
})();

/* Operator precendence and associativity
    Precedence: Which operator function gets called first when functions are called in order of precendence (higher precedence wins)
    Associativity: What order operator functions get called in: left to right or right to left when functions have same precedence 

    Precedence            Operator type              AssociativIndividual operator
    20                    Grouping                   n/a       ( … )
    19                    Member Access              left-to-ri… . …
                        Computed Member Access     left-to-ri… [ … ]
                        new (with argument list)   n/a       new … ( … )
    18                    Function Call              left-to-ri… ( … )
                        new (without argument list)right-to-lnew …
    17                    Postfix Increment          n/a       … ++
                        Postfix Decrement          n/a       … --
    16                    Logical NOT                right-to-l! …
                        Bitwise NOT                right-to-l~ …
                        Unary Plus                 right-to-l+ …
                        Unary Negation             right-to-l- …
                        Prefix Increment           right-to-l++ …
                        Prefix Decrement           right-to-l-- …
                        typeof                     right-to-ltypeof …
                        void                       right-to-lvoid …
                        delete                     right-to-ldelete …
    15                    Exponentiation             right-to-l… ** …
    14                    Multiplication             left-to-ri… * …
                        Division                   left-to-ri… / …
                        Remainder                  left-to-ri… % …
    13                    Addition                   left-to-ri… + …
                        Subtraction                left-to-ri… - …
    12                    Bitwise Left Shift         left-to-ri… << …
                        Bitwise Right Shift        left-to-ri… >> …
                        Bitwise Unsigned Right Shifleft-to-ri… >>> …
    11                    Less Than                  left-to-ri… < …
                        Less Than Or Equal         left-to-ri… <= …
                        Greater Than               left-to-ri… > …
                        Greater Than Or Equal      left-to-ri… >= …
                        in                         left-to-ri… in …
                        instanceof                 left-to-ri… instanceof …
    10                    Equality                   left-to-ri… == …
                        Inequality                 left-to-ri… != …
                        Strict Equality            left-to-ri… === …
                        Strict Inequality          left-to-ri… !== …
    9                     Bitwise AND                left-to-ri… & …
    8                     Bitwise XOR                left-to-ri… ^ …
    7                     Bitwise OR                 left-to-ri… | …
    6                     Logical AND                left-to-ri… && …
    5                     Logical OR                 left-to-ri… || …
    4                     Conditional                right-to-l… ? … : …
    3                     Assignment                 right-to-l… = …
                                                            … += …
                                                            … -= …
                                                            … **= …
                                                            … *= …
                                                            … /= …
                                                            … %= …
                                                            … <<= …
                                                            … >>= …
                                                            … >>>= …
                                                            … &= …
                                                            … ^= …
                                                            … |= …
    2                     yield                      right-to-lyield …
                        yield*                     right-to-lyield* …
    1                     Spread                     n/a       ... …
    0                     Comma / Sequence           left-to-ri… , …

*/ 
(function(){
    var a = 3 + 4 * 5;
    console.log(a); // 23
})();

(function() {
    var a = 2, b = 3, c = 4;
    a = b = c;
    console.log(a); // 4
    console.log(b); // 4
    console.log(c); // 4
})();
// The associativity of the assignment operator is right to left (starts with c)
/* Grouping () gets run first because it has highest precedence (20) */
(function() {
    var a = (3 + 4) * 5; // 35
})();

/* Coercion: converting a value from one type to another
*/
(function() {
        var a = 1 + '2'; // 12
        console.log(a);
})();

/* Comparison Operators
    Less than: left to right
*/
(function() {
    console.log(3 < 2 < 1); // 3 is less than 2 is false become coerced to 0 is less than 1
    console.log(false < 1); // true
    console.log(1 < 2 < 3); // 1<2 true, true=1 < 3 returns true
    false == 0; // true
    null == 0; // false
    null < 1; // true
    "" == 0; // true
    "" == false; // true
})();
// coerces the boolean to a number (0)
(function() {
    Number(false); // 0
    Number(true); // 1
    Number(undefined); // NaN no way to convert to number
    Number(null); // 0

    var a = 0;
    var b = false;
    if (a == b) {
        console.log('They are equal');
    } else {
        console.log('Nope not equal');
    } // they are equal

    if (a === b) {
        console.log('They are equal');
    } else {
        console.log('Nope not equal');
    } // nope not equal    
})();
// 99% of the time try to use === strict equality operator (function)

/* Existence and Booleans*/
(function(){
    Boolean(undefined); // false
    Boolean(null); // false
    Boolean(""); // false
    Boolean(0); // false
    false || true; // true
    var a;
    // goes out to the internet and looks for a value
    if (a || a === 0) {
        console.log('something is there');
    } // if a is undefined, null or "" it will, using coercion to check if a is nothing
})();

/* Default Values 
    ES6 is going to let you set default value
*/
(function(){
    function greet(name) {
        name = name || ''; // set default value
        console.log(name);
        console.log('Hello ' + name);
    }
    greet('Aaron');
    greet(); // name is undefined
})();
// OR is special, it will return the first one that coerces to true
(function() {
    undefined || "hello"; // hello
    true || false; // true
})();

lib1.js
    var libraryName = 'Lib 1';
lib2.js 
    var libraryName = 'Lib 2';
app.js
    console.log(libraryName); // Lib 2
lib2.js
    window.libraryName = window.libraryName || 'Lib 2'; // lets Lib 1 exist or takes it if it doesnt

/* Object and Functions
    Objects and the Dot
    A collection of names and values (properties and methods)
    Primitive "property"
    Object "property"
    Function is a "method" -- when its sitting on an object
*/
(function(){
    var person = new Object(); // var person = {};
    person["firstname"] = "Aaron"; // [] computed member access operator. string primitive property
    person["lastname"] = "Rosario";

    var firstNameProperty = "firstname";
    console.log(person); // Object {firstname: "Aaron", lastname: "Rosario"}
    console.log(person[firstNameProperty]); // Aaron

    // . member access operator is more common  
    console.log(person.firstname);
    console.log(person.lastname);

    person.address = new Object(); // object inside object
    person.address.street = '111 Main St.'; // the associativity of the member access operator is left to right
    person.address.city = 'Raleigh';
    person.address.state = 'NC';
    console.log(person.address.street);
    console.log(person["address"]["state"]); // same thing
})();
// computed member access is better for dynamically named string

/* Objects and Object Literals 
*/
(function(){
    var person = {
        firstname: 'Aaron',
        lastname: 'Rosario',
        address: { // address property that is an object itself
            street: '120 St Albans Dr',
            unit: 'Apt 585',
            city: 'Raleigh',
            state: 'NC'
        } 
    };

    function greet(person) {
        console.log('Hi ' + person.firstname);
    }

    greet(person);
    greet({ firstname: 'John', lastname: 'Doe' }); // creating an object on the fly
    greet({ 
        firstname: 'John', 
        lastname: 'Doe'
    });
    console.log(person); // Object: { firstname: "Aaron", lastname: "Rosario" , address: Object }
})();

/* Faking Namespaces 
   Namespace: a container for variables and functions typically to keep vars and functions with same name separate 
*/

(function(){
    var greet = 'Hello!';
    var greet = 'Hola!';
    console.log(greet); // Hola!
    var english = {};
    var spanish = {};
    english.greet = 'Hello!'; // these dont collide, kept separate, using an object as a namespace
    spanish.greet = 'Hola!'; 
})();

/* JSON vs Object Literals
    JavaScript Object Notation is inspired by JS object literal
    XML (wasted download bandwidth):
    
        Mary
        true
    
    JSON is more strict, properties have to wrapped in quotes:
    {
        "firstname": "Mary",
        "isAProgrammer": true
    }
*/
(function(){
    var objectLiteral = {
        firstName: 'Mary',
        isAProgrammer: true
    };

    var jsonValue = JSON.parse('{ "firstName": "Mary", "isAProgammer": true}');
    console.log(objectLiteral);
    console.log(JSON.stringify(objectLiteral));
    console.log(jsonValue);
})();

/* Functions are objects 
    First class functions: Everything you can do with other types you can do with functions. (assign them to vars pass them around create them on the fly)
    Function is a special type of object it has hidden special properties
    You can attach properties and methods to a function (primitives, objects, functions)
    Name [optional, can be anonymous]
    Code property [actual code of the function] Invocable with ()
*/
(function(){
    function greet() { // name property greet, code property below invocable with ()
        console.log('hi');
    }
    greet.language = 'english'; // added a property to a function
    console.log(greet); // the code property of the function
    console.log(greet.language); // english
})();

/* function statements and function expressions 
    expression: a unit of code that results in a value (it doesnt have to save to a value)
    statement just does work expression returns a value
*/
(function(){
    var a;
    a = 3; // valid expression 
    1 + 2; // valid expression returns 3 but isnt saved to a var
    a = { greeting: "hi" }; // valid expression
    if (a == 3) { // if statement with expression a == 3

    } 
    greet(); // its hoisted so its available before it is declared in a statement
    function greet() { // function statment -- doesnt result in a value, its in memory but it doesnt return a value until it is executed
        console.log('hi');
    }
    //anonymousGreet(); // uncaught type error undefined is not a function, ReferenceError wont get hoisted because its a function expression
    var anonymousGreet = function() { // function expression -- because it results in an object its name is anonymous its referenced by variable name, code property is the same
        console.log('hi');
    }
    anonymousGreet();
    function log(z) {
        console.log(z);
        z(); // runs the z function that was passed in
    }

    log(function() {   // passes a function to a function
        console.log('hi');
    });
})();

/* by value vs by reference
    by value
    a // 0x001 location in memory
    b = a // 0x002 copy of primitive value
    All primitive values are by value

    by reference
    a = {} // 0x001
    b = a  // 0x001
    All objects interact by reference when setting them equal or passing through functions

    Mutate: to change something 
    "immutable" means it cant be changed 
*/

(function(){
    // by value (primitives)
    var a = 3;
    var b;
    b = a; // by value because integret primitive type, new spot in memory for b, copies
    a = 2; // doesnt affect b because they are in different memory spots
    console.log(a);
    console.log(b);

    // by reference
    var c = { greeting: 'hi' };
    var d;
    d = c; // these are objs rather than set up d in a memory space use c's memory address
    c.greeting = 'hello'; // mutate
    console.log(c); // Object { greeting: "hello" }
    console.log(d); // Object { greeting: "hello" }

    // by reference (even as parameters)
    function changeGreeting(obj) {
        obj.greeting = 'Hola'; // mutate
    }
    changeGreeting(d);
    console.log(c); // object { greeting: "Hola" }
    console.log(d); // object { greeting: "Hola" }
    // equals operator sets up new memory space (new address)
    c = { greeting: 'howdy' }; // brand new object by this object literal syntax (new memory address)
    console.log(c); // object { greeting: "howdy" }
    console.log(d); // object { greeting: "Hola" }
})();

/* Object, functions and 'this'
    When a function is invoked an execution context is created and put on the execution stack (CREATION phase)
        Variable environment
        Outer lexical environment
        this -- points at a different object depending on how the function is invoked 
*/
(function(){
    console.log(this); // window object

    function a() {
        console.log(this);
        this.newvariable = 'hello'; // window.newvariable
    }
    var b = function() {
        console.log(this);
    }
    a(); // global window object
    console.log(newvariable); // newvariable went crashing into the global namespace
    b(); // global window object

    var c = {
        name: 'The c object',
        log: function() {
            var self = this; // some people use var that = this;
            self.name = "Updated c object"; // mutated the object that contains me
            console.log(self); // Object { name: "The c object" }, youre attached to an object so im going to point to the object that contains you

            var setname = function(newname){
                self.name = newname;
            }
            setname('Updated again! the c object'); // Some people think this is a bug when you use this, because name was added to the global window object, instead setup self var
            console.log(self);
        }
    }

    c.log();
})();

/* Arrays collections of anything 
    0 based
    Dynamically typed
*/
(function() {
    var arr = new Array();
    var arr = [1, false, 3];
    var arr = [
        1,
        false,
        {
            name: 'Aaron',
            address: '120 St Albans'
        },
        function(name) {
            var greeting = 'Hello ';
            console.log(greeting + name);
        },
        "hello"
    ]; 
    console.log(arr);
    arr[3](arr[2].name); // neat thing about dynamic typing
})();

/* arguments and Spread
    Execution Context is created (FUNCTION)
    sets up a variable environment 
    sets up this
    sets up outer environment
    sets up arguments
    Arguments: the parameters you pass to a function
    Javascript gives you a keyword of the same name which contains them all
    Arguments will become deprecated, the new thing is called a spread parameter
        ...other  separate array of misc args
*/
(function() {
    // in es6: function greet(firstname = 'john', lastname = 'doe', language = 'en')
    function greet(firstname, lastname, language) { // arguments werent passed in sets them to undefined
        language = language || 'en'; // default value if undefined

        if (arguments.length === 0) {
            console.log('Missing parameters!');
            return;
        }

        console.log(firstname);
        console.log(lastname);
        console.log(language);
        console.log(arguments); // array like
    }
    greet(); // returns 3 undefined; no parameters, js dont care, other languages do
    greet('John'); // returns John and 2x undefined because of hoisting
    greet('John', 'Doe', 'es'); // returns John, Doe and es
})();

/* function overloading - JS doesnt have */
(function() {
    function greet(firstname, lastname, language) {
        language = language || 'en';
        if (language === 'en') {
            console.log('Hello ' + firstname + ' ' + lastname);
        }

        if (language === 'es') {
            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');    
})();

/* Syntax Parsers
    Reads your code and determines if its valid and what it is trying to do
    r,e,t,u,r,n  character by chracter 
    ; terminating character
    return; valid
*/

/* Automatic semicolon insertion
    Semicolons are optional
    return\n  hey you cant use a return after this statement we will put a semicolon there for you return;
    Good practice is to always put your curly braces on the same line as your keywords to avoid this problem
*/
(function(){
    function getPerson() {
        // return  // because theres a new line it auto inserts ;
        return { // this works
            firstname: 'Aaron'
        }
    }
    console.log(getPerson()); // undefined because of automatic semicolon insertion
})();

/* Whitespace: invisible characters that create literal space in your written code (carriage returns, tabs, spaces)
*/
(function(){
    // first name of the person
    var firstname, 
    // last name of the person
    lastname, 
    // language of the person can be 'en' or 'es'
    language;

    var person = {
        // I can even add comments into object literals
        firstname: 'John',
        lastname: 'Doe'
    }

    console.log(person);
})();

/* Immediately Invoked Function Expressions (IIFE)s
    Typically uses the grouping operator () just to trick the syntax parser
    (function() {
        
    })();
*/
(function(){
    function greet(name) { // function statement
        console.log('Hello ' + name);
    }
    greet();

    var greetFunc = function(name) { // function expression
        console.log('Hello ' + name);
    };
    greetFunc();

    var greeting = function(name) {
        console.log('Hello ' + name);
    }('Aaron'); // IIFE

    var firstname = "Aaron";
    (function(name) {
        var greeting = 'Inside IIFE: Hello';
        console.log(greeting + ' ' + name);
    })(firstname);
})();

/* IIFEs and safe code
*/
(function(global, name) {
    var greeting = 'Hello'; // only exists in this IIFE
    global.greeting = 'Hello'; // if you want this available to global window scope
})(window, 'John'); // IIFE

/* Understanding closures
    A feature of the JS engine
    Global execution context
    greet() execution context
    whattosay is sitting in its variable environment
    greet() is popped off the stack
    the memory of greet()'s execution context still exists
*/
(function() {
function greet(whattosay){
    return function(name) {
        console.log(whattosay + ' ' + name);
    }
}
greet('Hi')('Aaron');

var sayHi = greet('Hi');
sayHi('Aaron');
})();

/* Understanding closures part 2
    Global execution content buildFunction(), fs
    buildFunctions() get execution context i=3 arr=[f0, f1, f2]
    popped off of stack after for loop
    i is 3 by the time you execute all these functions
*/
(function() {
    function buildFunctions() {
        var arr = [];
        for (var i = 0; i < 3; i++) {
            arr.push(
                function() {
                    console.log(i); // isnt being executed right here
                }
            )
        }

        return arr;
    }

    var fs = buildFunctions();

    fs[0](); // 3   execution context is created, uses the last execution context and outer reference 
    fs[1](); // 3   all pointing at the same point in the scope chain buildFunction   
    fs[2](); // 3   they are all executed later then when they were assigned and give parents value
})();

/* Function Factories */
function makeGreeting(language) {

    return function(firstname, lastname) {
        if (language === 'en') {
            console.log('Hello ' + firstname + ' ' + lastname);
        }

        if (language === 'es') {
            console.log('Hola ' + firstname + ' ' + lastname);
        }
    }
}
var greetEnglish = makeGreeting('en');
var greetSpanish = makeGreeting('es');

greetEnglish('John', 'Doe');
greetSpanish('John', 'Doe');

/* Closures and Callbacks */
/* setTimeout uses a closure to still have access to the variables at the time of execution */
/* Callback Function: a function you give to another function, to be run when the other funciton is finished 
so the function you call (ie invoke) 'calls back' by calling the function you gave it when it finishes */

function sayHiLater() {
    var greeting = 'Hi!';

    setTimeout(function() {
        console.log(greeting);
    }, 3000);
}

sayHiLater();
// jQuery uses function expression and first class functions
$("button").click(function() {
});

function tellMeWhenDone(callback) {
    var a = 1000;
    var b = 2000;

    callback(); // the 'callback' it runs the function I give it!
}

tellMeWhenDone(function() {
    console.log('I am done');
})

tellMeWhenDone(function() {
    console.log('All Done');
});

/* call(), apply(), bind()
    All functions have access to call() apply() and bind() methods.
    They affect the 'this' context.
*/
var person = {
    firstname: 'John',
    lastname: 'Doe',
    getFullName: function() {
        var fullname = this.firstname + ' ' + this.lastname; // this context is person
        return fullname;
    }
}

var logName = function(lang1, lang2) {
    console.log('Logged: ' + this.getFullName()); // this is on the globalobject because its not a method
    console.log('Arguments: ' + lang1 + ' ' + lang2);
    console.log('-------------')
}

var logPersonName = logName.bind(person); // bind copies logName and runs it with person context
logPersonName();  

logname.call() = logname(); // bind copies the funciton, call executes it 
logName.call(person, 'en', 'es'); // specifying your this context and function parameters
logName.apply(person, ['en', 'es']); // exact same thing except args are passed as array -- only difference between call and apply

// function borrowing 
var person2 = {
    firstname: 'Jane',
    lastname: 'Doe'
}
person.getFullName.apply(person2); // borrow the method from person object for person2

// function currying
function multiply(a,b) {
    return a*b;
}

var multiplyByTwo = multiply.bind(this, 2); // copy multiply function and set a permanent value for the first parameter
multiplyByTwo(4); // 8

/* Function Currying, creating a copy of a function by with some preset parameters, very useful in mathematical situations */

/* Functional Programming
 JS is more like LISP, Schema, ML (first class functions, functions as parameters/returns)
*/
var 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];
var arr2 = mapForEach(arr1, function(item) {
    return item * 2;
});

var checkPastLimit = function(limiter, item) {
    return item > limiter;
}

var arr4 = mapForEach(arr1, checkPastLimit.bind(this, 1));
console.log(arr4);

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

varr arr5 = mapForEach(arr1, checkPastLimitSimplified(1));
console.log(arr5);

console.log(arr2);

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

console.log(arr2); // [1,2,3][4,5,6]

/* Functional Programming Part 2 
underscore.js
*/
var arr6 = _.map(arr1, function(item) { return item * 3});
var arr7 = _.filter([2,3,4,5,6,7], function(item){ return item % 2 === 0});

/* Object-oriented Javascript and Prototypal Inheritance
Inheritance: One object gets access to the properties and methods of another object.
Classical Inheritance: C#, Java
Verbose: friend, protected, private, interface
Prototypal Inheritance: Simple, flexible, extensible, easy to understand

Understanding the prototype
    obj -> prop1 (obj.prop1)
    obj -> proto {} (reference to object property proto, where it gets its methods and props from)
    obj -> proto {} -> prop2 (obj.prop2) (prop2 isnt on our object its on its prototype)
    obj -> proto {} -> proto {} -> prop3 (obj.prop3) (its down the prototype chain) 
Scope chain is about looking for where we have access to a variable prototypal chain is about where we have access to a method or property in a sequence of objects
    obj2 -> proto {} (obj2.prop2 is referencing obj proto{})
*/

var person = {
    firstname: 'Default',
    lastname: 'Default',
    getFullName: function() {
        return this.firstname + ' ' + this.lastname;
    }
}

var john = {
    firstname: 'John',
    lastname: 'Doe'
}

// dont do this ever, performance issues
john.__proto__ = person; // john now inherits from person
console.log(john.getFullName()); // will see that john doesnt have that method and search its prototype, if not there then thats prototype

var jane = {
    firstname = 'Jane'
}
jane.__proto__ = person;
console.log(jane.getFullName());

/* Reflection and Extend
 Reflection: An object can look at itself, listing and changing its properties and methods 
*/

var person = {
    firstname: 'Default',
    lastname: 'Default',
    getFullName: function() {
        return this.firstname + ' ' + this.lastname;
    }
}

var john = {
    firstname: 'John',
    lastname: 'Doe'
}

john.__proto__ = person;

for (var prop in john) {
    if (john.hasOwnProperty(prop)){ // only log if its on the object not if its on the prototype chain
        console.log(prop + ': ' + john[prop]);
    }
}

// underscore's extend

var jane = {
    address: '111 Main St.',
    getFormalFullName: function() {
        return this.lastname + ', ' + this.firstname
    }
}

var jim = {
    getFirstName: function() {
        return firstname;
    }
}

_.extend(john, jane, jim); // combines jim and janes props/methods to john

// .extends in es6 will extend the prototype

/* Building Objects
    Function constructors 'new' and the history of JS 
*/
var john = new Person(); // Made for Java Devs

function Person(firstname, lastname) {
    console.log(this);
    this.firstname = firstname;
    this.lastname = lastname;
    console.log('This function is invoked');
}

Person.prototype.getFullName = function() {
    return this.firstname + ' ' + this.lastname;
}

var john = new Person('John','Doe'); // Inherits Person.prototype
console.log(john);
var jane = new Person('Jane', 'Doe'); // Inherits Person.prototype
console.log(jane);

// You can add stuff to the prototype later and access it through the prototype chain
Person.prototype.getFormalFullName = function() {
    return this.lastname + ', ' + this.firstname;
}

john.getFormalFullName();

/* Function constructors: 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
    Functions are a special type of object with the following prototypes:
        NAME (optional can be anonymous)
        CODE (Invocable())
        prototype (used only by the 'new' operator, only when used as a constructor)
    Adding methods to the prototype is better than adding them in the constructor function because it saves space (methods in the constructor function are copied to every object instance)
    Its better to go up the prototype chain than copying a method to every object instance
*/

/* Dangerous Aside: 'New' and functions
    If you forget to put the new keyword when instantiating a constructor function it will execute the function
    Use a capital letter for constructor functions so you can tell that it should have the 'new' keyword in front of it instead of executing it
*/

/* Conceptual Aside: Built-in function constructors */
    var a = new Number(3); // Number {[[PrimitiveValue]]: 3}  gets the Number primitive's prototype 
    var a = new String("Aaron"); // String {0: "A", 1: "a", 2: "r", 3: "o", 4: "n", length: 5, [[PrimitiveValue]]: "Aaron"}
    var a = new Date('12/16/2016'); // Fri Dec 16 2016 00:00:00 GMT-0500 (Eastern Standard Time)

    String.prototype.isLengthGreaterThan = function(limit) { // ALL STRINGS INSTANTLY HAVE ACCESS TO THIS METHOD
        return this.length > limit;
    }

    console.log("Aaron".isLengthGreaterThan(3));

    Number.prototype.isPositive = function() { 
        return this > 0;
    }

/*    3.isPositive(); // Errors because JS does not auto convert integers to their primitive for you
    var a = new Number(3); // now it will work now that it is wrapped with primitive prototypes

    Not always the best option, try to avoid this unless necessary
    ------------------
*/
    var a = 3; // is a primitve
    var b = new Number(3); // is an object created with 
    var a == b; // true due to coercion
    var a === b; // false due to b being an object (thanks new keyword)

/*    Preferred to use literals and the actual primitive values instead of the built in constructor functions for primitives

    Arrays and for..in
    ------------------
    Arrays are objects
*/

    Array.prototype.myCustomFeature = 'cool!'; // this will add this property and value to all arrays

    var arr = ['John', 'Jane', 'Jim'];
    for (var prop in arr) {
        console.log(prop + ': ' + arr[prop]);
    }

/*    so iterate through arrays with for (i = 0; i < arr.length; i++)
*/

/* Object.create and Pure Prototypal Inheritance */

    // Polyfill: code that adds a feature which the engine may lack.

    if (!Object.create) {   // older JS engine polyfill
        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();
        }
    }

    var person = {
        firstname: 'Default',
        lastname: 'Default',
        greet: function() {
            return 'Hi' + ' ' + this.firstname; // objects dont get execution context so you need to add this
        }
    }

    var aaron = Object.create(person);
    aaron.firstname = 'Aaron';
    aaron.lastname = 'Rosario';
    console.log(aaron);

/*
    You can change the prototype along the way
*/
/* ECMA Script 6 and Classes
    In other languages classes are not objects

    THIS IS AN OBJECT
*/
    class Person {
        constructor(firstname, lastname) {
            this.firstname = firstname;
            this.lastname = lastname;
        }

        greet() {
            return 'Hi' + firstname;
        }
    }

    var aaron = new Person('Aaron', 'Rosario'); // New object from the person object

  //  The new keyword is an attempt to appease devs comfortable in other classical languages

    class InformalPerson extends Person {   // sets the Prototype __proto__
        constructor(firstname, lastname) {
            super(firstname, lastname);
        }

        greet() {
            return "Yo " + firstname;
        }
    }

    // Syntactic sugar: a different way to type something that doesn't change how it works under the hood

    // Classes in ES6 dont change anything about how constructors or prototypes work in JS engine


/* Odds and Ends 
    Initialization
*/

    var people = [
        {
            // the 'Aaron' object
            firstname: 'Aaron',
            lastname: 'Rosario',
            addresses: [
                '111 Main St',
                '222 Third St.'
            ]
        },
        {
            // the 'Jane' object
            firstname: 'Jane',
            lastname: 'Doe',
            addresses: [
                '333 Main St.',
                '444 Fifth St.'
            ]
        },
        greet: function() {
            return 'Hello!';
        }
    ];

    // typeof, instanceof and figuring out what something is

    var a = 3;
    console.log(typeof a); // number
    var b = 'hello';
    console.log(typeof b); // string
    var c = {}; // object
    var d = []; 
    console.log(typeof d); // object... weird.
    console.log(Object.prototype.toString.call(d)); // [object Array], call = invoke this function but tell it what the this should point to

    function Person(name) {
        this.name = name;
    }

    console.log(typeof e);
    console.log(e instanceof Person); // true, go down the prototype chain to see if its an instance of Person

    console.log(typeof undefined); // undefined
    console.log(typeof null); // null object, bug thats been around too long to fix

    var z = function() { };
    console.log(typeof z); // function

/* Strict Mode */
    "use strict";   // must be top of the file or top of a function
    var person;
    persom = {};
    console.log(persom); // Uncaught ReferenceError: persom is not defined

/* Let's build a framework/library
    Requirements (what should the software do)
    Name: greetr
    When given a first name, last name and optional language, it generates formal and informal greetings
    Support English and Spanish languages
    Reusable library/framework.
    Easy to type 'G$()'
    Support jQuery
*/
(function(global, $) {
    
    var Greetr = function(firstName, lastName, language) {
        return new Greetr.init(firstName, lastName, language);
    }
    
    var supportedLangs = ['en', 'es'];

    var greetings = {
        en: 'Hello',
        es: 'Hola'
    };

    var formalGreetings = {
        en: 'Greetings',
        es: 'Saludos'
    };

    var logMessages = {
        en: 'Logged in',
        es: 'Inicio sesion'
    }

    Greetr.prototype = {
        fullName: function() {
            return this.firstName + ' ' + this.lastName;
        },
        validate: function() {
            if (supportedLangs.indexOf(this.language) === -1) {
                throw "Invalid language";
            }
        },
        greeting: function() {
            return greetings[this.language] + ' ' + this.firstName + '!';
        },
        formalGreeting: function() {
            return formalGreetings[this.language] + ', ' + this.fullName();
        },
        greet: function(formal) {
            var msg;

            if (formal) {
                msg = this.formalGreeting();
            }
            else {
                msg = this.greeting();
            }

            if (console) {
                console.log(msg);
            }

            return this;
        },
        log: function() {
            if (console) {
                console.log(logMessages[this.language] + ': ' + this.fullName());
            }

            return this;
        },
        setLang: function(lang) {
            this.language = lang;
            this.validate();
            return this;
        },
        HTMLGreeting: function(selector, formal) {
            if (!$) {
                throw 'jQuery not loaded';
            }
            if (!selector) {
                throw 'Missing jQuery selector';
            }

            var msg;
            if (formal) {
                msg = this.formalGreeting();
            }
            else {
                msg = this.greeting();
            }

            $(selector).html(msg);

            return this;
        }
    }; 

    Greetr.init = function(firstName, lastName, language) {

        var self = this;
        self.firstName = firstName || '';
        self.lastName = lastName || '';
        self.language = language || 'en';

        self.validate();

    }

    Greetr.init.prototype = Greetr.prototype;

    global.Greetr = global.G$ = Greetr;

}(window, jQuery));