Wednesday, November 22, 2017

JavaScript Notes

Objects and Prototypes

Notes from Jim Cooper's excellent PluraSight course on JavaScript Objects and Prototypes and Kyle Simpson's Advanced JavaScript class, and my random scribblings.

Use jsbin.com to play with JavaScript.

  1. Six Ways to Create Objects:
    1. Creating objects with Object Literals
      'use strict';
      var cat = {name: 'Fluffy', color: 'White'}
      console.log(cat.name); //Fluffy
      cat.age = 3; //we can create properties on the fly
      cat.speak = function() {console.log("Meeooow") }//add functions directly
      
    2. Creating objects with constructors:

      Calling "new" makes the function a constructor and does four magic things:

      1. Creates new object
      2. Sets the 'this' to its context
      3. Calls the function
      4. Returns a pointer to the new object

      Here's Jim's example:

      function Cat(name, color) {
        this.name = name
        this.color = color
      }
      var cat = new Cat('Fluffy','White');
      console.log(cat);
      
      This writes:
      [object Object] {
        color: "White",
        name: "Fluffy"
      }
      
    3. Creating objects with Object.create syntax Object.create()

      The "create()" method exists at the top level object cleverly named, "Object".

      Object.create(proto[, propertiesObject])
      

      "Object.create()" creates a new object and sets its prototype link to be the first parameter passed in. It also copies the properties from propertiesObject to the new object.

      The "new" keyword is just syntactic sugar, we could do the previous example like this:

      var cat = Object.create(Object.prototype,
      { 
      name: {
      value: 'Fluffy',
      enumerable:true,
      writable:true,
      configurable:true
      },
      color: {
      value: 'White',
      enumerable:true,
      writable:true,
      configurable:true
      }
      });
                             
      console.log(cat);
      
      o = {};
      //is shorthand for 
      o = Object.create(Object.prototype);
      
    4. A Fallback Polyfill for Older Browsers

      Older browsers may not have the "create()" method, so you can use a polyfill from developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

      if (typeof Object.create !== "function") {
          Object.create = function (proto, propertiesObject) {
              if (!(proto === null || typeof proto === "object" || typeof proto === "function")) {
                  throw TypeError('Argument must be an object, or null');
              }
              var temp = new Object();
              temp.__proto__ = proto;
              Object.defineProperties(temp, propertiesObject);
              return temp;
          };
      }
      
    5. Creating objects with the new ECMAScript 6

      ES6 has new built in support for creating classes:

      'use strict';
      class Cat {
         constructor(name, color) {
            this.name= name
            this.color = color
         }
         speak() {
         console.log('Meeooow')
         }
      }
      
  2. Properties
    1. Bracket Notation

      Besides the dot notation, 'this.color = "blue"' you can use the bracket notation. An advantage of the bracket notation is that you can use property names with embedded spaces:

      cat['color'] = "blue";
      cat['Eye Color'] = "green";
      
    2. Descriptors

      JavaScript properties have 4 descriptors: value, writable, enumerable, and configurable.

      1. Value

        This is simply the actual "value" of the property.

      2. writable

        "writable" is a boolean that determines if the value can be written over.

        var cat = {name: 'Fluffy', color: 'White'}
        console.log(Object.getOwnPropertyDescriptor(cat, 'name')
        Object {
          value: Fluffy
          writable: true
          enumerable: true
          configurable: true
        }
        

        To set a property of field use defineProperty

        Object.defineProperty(cat, 'name', {writable: false})
        var cat = {
           name: {first: 'Fluffy', last: 'LaBeouf'}, color: 'White'
        }
        

        To prevent writing of an object:

        Object.freeze(cat.name)
        
      3. enumerable

        If "enumerable" set to false, the property will not show up in any list

        Object.defineProperty(cat, 'name', {enumerable: false})
        
        console.log(Object.keys(cat))
        
        console.log(JSON.stringify(cat))//will not show enumerable-false items
        

        How to Loop Over Enumerables:

        for(var propertyName in cat) {
          console.log(propertyName+':'+ cat[propertyName])
        }
        
      4. configurable

        If configurable is set to true you cannot change enumerable attr or configurable attr, or delete it, but can change writable attr.

        Object.defineProperty(cat, 'name', {configurable: false}
        
    3. Getters and Setters

      Let's create a new property and add a get() and set() methods.

      var cat = {
         name: {first: 'Fluffy', last: 'LaBeouf'}, color: 'White'
      }
      Object.defineProperty(cat, 'fullName',
      {
      get: function() {
        return this.name.first + ' ' + this.name.last
      },
      set: function(value) {
        var nameParts = value.split(' ')
        this.name.first = nameParts[0]
        this.name.last = nameParts[1]
      }
      });
      console.log(cat.fullName); //"Fluffy LaBeouf"
      cat.fullName = 'Top Cat'; //"Top Cat"
      console.log(cat.fullName);
      
  3. Prototypes and Inheritance
    1. We can add methods to individual objects. Here we create a 'last' method to an object, arr.

      'use strict';
      var arr = ['red','blue','green']
      Object.defineProperty(arr, 'last', {get: function() {
         return this[this.length-1];}});
      console.log(arr.last);//"green"
      

      But wouldn't it be nice to add a method to all arrays. We can by adding a method to 'Array's prototype which is shared by all the Array objects. If an individual object doesn't contain a function, it looks to its "prototype" to see if it has that function.

      'use strict';
      var arr = ['red','blue','green']
      Object.defineProperty(Array.prototype, 'last', {get: function() {
         return this[this.length-1];}});
      console.log(arr.last); //"green"
      console.log(['sour','sweet','bitter'].last); //"bitter"
      

      All functions have a built-in property that points to another object called "prototype".

      var myFunc = function() { }
      console.log(myFunc.prototype); //[object Object] { ... }
      

      Objects do not have a prototype property but they have a __proto__

    2. function Cat(name, color) {
        this.name = name;
        this.color = color;
      }
      
      var fluffy = new Cat('Fluffy','White');
      console.log(Cat.prototype);//[object Object] { ... }
      console.log(fluffy.__proto__);//[object Object] { ... }
      console.log(Cat.prototype === fluffy.__proto__);//true
      Cat.prototype.age = 4;
      console.log(fluffy.__proto__.age);//4
      

      Important: A function's prototype is the object instance that will become the default prototype for all objects created using this function.

      Jim has a great example of linking two classes, "Animal" and "Cat" into an inheritance chain. It's kinda tedious and error prone. Be very careful. This is why Kyle Simpson prefers OLOO. (more on that later).

      'use strict';
      function Animal(voice) {
        this.voice = voice || 'grunt';
      }
      Animal.prototype.speak = function() {
        console.log(this.voice)
      }
      
      function Cat(name, color) {
        Animal.call(this, 'Meow');
        this.name = name;
        this.color = color;
      }
      Cat.prototype = Object.create(Animal.prototype);
      Cat.prototype.constructor = Cat;
      var fluffy = new Cat('Fluffy','White');
      console.log(fluffy.__proto__.__proto__);
      

      This prints:

      [object Object] {
        speak: function () {
        window.runnerWindow.proxyConsole.log(this.voice)
      }
      }
      

      The above code is a little cleaner with the new ES6 "class" structure:

      'use strict'
      class Animal {
        constructor(voice) {
        this.voice = voice || 'grunt'
        }
        speak() {
          console.log(this.voice)
        }
      }
      
      class Cat extends Animal {
      constructor(name, color) {
        super('Meow');
        this.name = name;
        this.color = color;
        }
      }
      
      var fluffy = new Cat('Fluffy', 'White');
      fluffy.speak();//"Meow"
      

JavaScript Scope

My notes from Plurasight's Advanced JavaScript by Kyle Simpson. His series of online books is available on github at github.com/getify/You-Dont-Know-JS.

A good reference site for JavaScript is the Mozilla Developer Network MDM at https://developer.mozilla.org/en-US/docs/JavaScript

A good place to learn styles of programming JS is

https://github.com/rwldrn/idiomatic.js.

The actual definitive spec is at http://www.ecma-international.org/ecma-262/5.1

  1. Details of Scope

    Scope: where to look for things

    JavaScript is not interpreted. Bash is interpreted. The JS compiler goes through all the code before starting execution. It finds declaration of variables and puts them into appropriate scope slots. Finds "var" and "function"s.

    JS has function scope only (kinda).

    lhs - left hand side (target)

    rhs - right hand side (source)

    "undefined" really means uninitialized

    Kyle has a good way of explaining scope: When executing a function "foo" and encountering a variable "foo", JavaScript asks the question: "Hey, scope of 'foo' do you have a lhs reference to variable x?"

    For a function declaration, the first thing in the statement is "function name()".

    function bar() { ... }
    

    function expressions should have names

    var foo = function bar() { ... }
    

    Why use named functions and not anonymous?

    1. Can refer to itself for recurse
    2. Can find it in minified stack trace
    3. Is self-documenting
  2. eval

    "eval" cheats lexical scope. putting an "eval" in your code, forces the engine to run slower in strict mode, code is more optimizable. Don't use eval unless absolutely necessary.

    For example, using setTimeout with a string of code is bad practive since it invokes "eval()"; better to use with a function:

    setTimeout("console.log('hi')", 1000); 
    setTimeout(function () {console.log('bye');}, 2000); //ok with func pointer
    
  3. "with" is "more evil than eval"
    var obj = { a: 2, b: 3, c: 4 };
    with(obj) {//implies an obj.
    a = b + c;
    }
    

    "with" effects the scope and in strick mode, you can't use "with".

Immediately-invoked-function-expression (IIFE) pattern

This is a widespread pattern in JavaScript with a history here: benalman.com/news/2010/11/immediately-invoked-function-expression. It forces a function to run immediately - hense the catchy name.

var foo = "outside";
(function() {
  var foo = "inside";
  console.log("foo:"+foo)//prints "inside"
})();

"let" (ES6) and block scope

"let" will set the scope of the variable to the "for" loop (or outer braces), not the function scope as "var" would have done. creates an implicit block on the "for".

function foo() {
  for(let i=0; i<var.length; i++) {
    ...
  }
}

warnings: "let" is not hoisted. If in the middle of an if statement block, the variable is only available after the "let". The area above the "let" declaration where the variable is not defined is called the "Temporal Dead Zone".

How to get a new scope? create a function, catch, or a brace with the "let" in ES6

"undefined" is a value, meaning a variable doesn't currently have a value.

"undeclared" never been declared. reference error.

hoisting

during compile phase, functions and then declarations are "hoisted" to the top.

var a=6;
function foo() { ... }

The compiler rearrages and hoists declarations to top

function foo() { ... }
var a;
a=6;

Why hoisting? mutual recursion would be impossible. like c header files.

The "this" pointer

Four Rules for how the 'this' keyword get bound.

Every function, while executing, has a reference to its current execution context called "this".

The value of the "this" pointer depends on the call site i.e., where the method gets invoked.

BTW, "this" is not like "this" in any other language.

  1. new

    "new" turns a function into a constructor call and sets the this pointer

    The "new" constructor does four things:

    1. A brand new empty object will be created
    2. The new object gets linked to another object
    3. The new object gets bound as the "this" keyword for the function call
    4. If the function doesn't return something, it will return "this"
  2. Explicit binding Rule

    .call() or .apply() take as their first arg "this"

    example of hardcoding

    function foo() { console.log(this.bar); }
    var obj = { bar: "bar" };
    var orig = foo;
    foo = function() { orig.call(obj); } //forces this context on foo
    foo.call(obj)
    

    utility to do hardbinding:

    function bind(fn,o) {return function() { fn.call(o); };}
    function foo() { console.log(this.bar); }
    var obj = { bar: "bar" };
    foo = bind(foo,obj);
    foo();//bar
    

    put bind on Function prototype

    if(!Function.prototype.bind2) {
      Function.prototype.bind2 =
        function(o) {
          var fn = this; //the original function
          return function() {
            return fn.apply(o,arguments);
          };
        };
    }
    var obj = { bar: "bar" };
    foo = foo.bind2(obj);
    foo("baz");
    

    ES5 has a builtin "bind"

    hardbind makes "this" be predictable
  3. Implicit binding Rule

    - object calling function becomes the "this"

  4. Default Binding Rule

    in strict mode, default "this" is set to the undefined value

    not in strict, "this" defaults to global, which in browser is window

Summary: Four Questions to determine what is the "this" object
  1. Was the function called with the "new" keyword? use that object.
  2. Was it called with .call() or .apply(). Use explicit object
  3. Was the function called with an owning object? use that.
  4. Default: global object (except in strict mode)

Details in his second book "You Don't Know JS.com" - chapter 2

Closure

Closure comes from Lambda calculus. Closure is when a function "remembers" its lexical scope even when the function is executed outside that lexical scope.

function foo() {
  var bar = "bar";
  function baz() {
     console.log(bar);
  }
  bam(baz);
}

function bam(baz) { baz(); }
foo();

//calling a method that returns a method that retains lexical scope (Closure)
function foo() {
  var bar = "bar";
  
  return function() { 
  console.log(bar);
  };
}

function bam() {
    foo()();  //call the returned method
}

bam();

The Classic Closure Example

for(var i=1; i<=5; i++) {
  setTimeout(function() {
    console.log("i: "+i);//writes 6 since i references outer scope
  },i*1000)
}


for(var i=1; i<=5; i++) {
  (function(i) {//use an IIFE
  setTimeout(function() {
    console.log("i: "+i);//writes 1,2,3,4,5
  },i*1000);
  })(i);
}

Classic Module Pattern

Two Characterics of Classic Module Pattern

1. Must have outer function wrapper

2. One or more functions that are returned that have closure over inner scope to access variables

example of module pattern

var foo = (function() {
  var o = {bar: "bar" };
  return {
    bar: function() {
      console.log(o.bar);
    }
  };
})();
foo.bar();
 
//example of modified module pattern
var foo = (function() {
  var publicAPI = {
    bar: function() {
      publicAPI.baz();
    },
    baz: function() {
      console.log("baz");
    }
  };
return publicAPI;
})();

foo.bar()

Object Orienting

OO Design Patterns

  1. Prototype

    Every single "object" is built by a constructor function.

    Each time a constructor is called, a new object is created.

    A constructor makes an object linked to its own prototype. (In class-based system, new objects are instantiations of the class)

    An object has a link called ".prototype" pointing to its prototype object. The prototype object has a link back to the object called ".constructor"

    A object has a private link called "[[Prototype]]" which points to its prototype object.

    Three ways to get a "new"'d object's prototype.

    1. A object in all browsers except IE has a public link called "__prototype__" (called DunderProto) which points to its prototype object.(in ES6, adopted in IE11)

    2. The standard way to get an object's prototype is "Object.getPrototypeOf(o)" (in IE9, ES5)

    3. For IE8 (ES3): o.constructor.prototype (but .constructor and .prototype are writtable)

    A function has a [[Prototype]] link (__proto__ in ES6 and all browsers except IE below 10) to the object "Function"'s prototype which contains "call()", "apply()", and "bind()".

  2. Inheritance

    Classical inheritance copys the class to make object. Instead use "Behavior Delegation".

    OLOO - Objects Linked to Other Objects

    function Foo(who) {
    	 this.me = who;
    }
    Foo.prototype.identify = function() {
      return "I am " + this.me;
    };
    function Bar(who) {
    	 Foo.call(this,who);
    }
    Bar.prototype = Object.create(Foo.prototype);
    Bar.prototype.speak = function() {
        alert("Hello, " + this.identify() + ".");
    };
    var b1 = new Bar("b1");
    b1.speak(); //alerts: "Hello, I am b1."
    
    //a slight improvement
    var b1 = Object.create(Bar.prototype);
    Bar.call(b1,"b1");
    b1.speak();
    
    //moving forward
    bar b1 = Object.create(Bar);
    b1.init("b1");
    b1.speak();
    

No comments: