lesson 4

JS part 1

[Because of the wealth of material to cover, the JavaScript portion of the course skips over some of the very basics of the language, and assumes some programming familiarity. Feel free to consult a more thorough language overview before continuing.]

data types

JavaScript has only five primitive types:

All other elements are complex objectes, derived from a base Object type, and can be assigned properties and behaviour. Most of the above primitive types (number, string, boolean) do actually have object counterparts (Number, String, Boolean). Conveniently, the JavaScript interpreter temporarily converts these primitive types in order to borrow properties and methods (length, charAt(), etc) from their object cousins.

type conversion

Because JavaScript is a dynamically typed language, and does not require strict type declarations, types are automatically resolved during script execution. Among other things, this makes it possible to convert or combine different types at runtime:


    "400" + 8 //"4008"
    "400" - 8 //392
    +"3.14" + +"3.14" //6.28
    parseInt("400 fish", 10); //400
    parseFloat("20.5 degrees"); //20.5
        

always specify a radix (base 2, 8, 10, 16, etc) parameter when calling parseInt(string, radix)

truthiness

All objects in JavaScript have an implied truthiness, that is, are either true or false, depending on their type and/or value:


    function truthiness (expression) {
      return expression ? true : false;
    }
    truthiness(undefined); //false
    truthiness(null); //false
    truthiness(true); //true
    truthiness(false); //false
    truthiness(0); //false
    truthiness(NaN); //false
    truthiness(100); //true
    truthiness(''); //false
    truthiness('Hey'); //true
    truthiness({}); //true
    truthiness(new Number(100)); //true
    truthiness(new Number(NaN)); //true
        

coercion

When comparing two values with comparison operators (==, !=, >, <=, etc), the JavaScript interpreter must first convert both values to the same type. This automatic type coercion attempts the following conversions before evaluating equality:


    // pseudo interpreter code:
    "thing" == true;
    "thing" == toNumber(true);
    "thing" == 1;
    toNumber("thing") == 1;
    NaN == 1; //false
        

Type coercion can be avoided by using strict comparison operators (===, !==), where values must first be of the same type in order to be considered equal:

Strict comparison is generally the safest option, though not always necessary when types are known to be the same:


    myArray.length == 3; //length always returns a number
    typeof myVar == "function"; //typeof always returns a string
    myVar == null;
        

null checks should always use non-strict comparison (==, !=) because null and undefined are == (but not ===) to each other.
In addition, undefined is a global property, and could in theory be assigned an unknown, arbitrary value

variables

Variables are used to store data values identified by a symbolic name. All variables are declared with the var keyword:


    var foo = 'bar';
        

Omitting the var keyword will automatically attach a variable to the global object (window in the browser environment):


    foo = "bar";
    console.log(window.foo); //"bar"
        

always declare variables with the var keyword

scope

JavaScript uses function-level scoping, as opposed to the block-level scoping of some other languages (C, for example). In practice, this means that variables declared inside of a function are accessible only within that function block (local scope), and variables declared outside of a function are accessible globally (global scope). Scope determines what variables are available within a function:


    var x = 1;
    function foo() {
      var x = 2;
      console.log(x); //2
    }
    foo();
    console.log(x); //1

    var y = 1;
    console.log(y); //1
    if (true) {
      var y = 2;
      console.log(y); //2
    }
    console.log(y); //2
        

hoisting

When a variable is declared, the JavaScript interpreter first hoists the declaration to the top of the enclosing function scope, assigns the variable a value of undefined, and then assigns the correct value where it was originally declared:


    function foo() {                function foo() {
      bar();              ==>         var x = undefined;
      var x = 1;                      bar();
    }                                 x = 1;
                                    }

    var x = 1;                      var x = undefined;
    function foo() {                x = 1;
      console.log(x);               function foo() {
      if (false) {        ==>         var x = undefined;
        var x = 2;                    console.log(x);
      }                               if (false) {
    }                                   x = 2;
                                      }
                                    }
        

always declare variables at the top of each function block in order to avoid unexpected hoisting behaviour

By value or reference?

There are two distinct ways to manipulate data values in computer programming: by value and by reference.

When data is manipulated by value, it is the actual value that matters. In an assignment, for example, a copy of the data is made and stored in the new variable. When data is manipulated by reference, on the other hand, it is the reference to the value that matters. In this case, an assignment from one variable to another would create two references to a single datum.

By value By reference
Copy Value is copied. Two distinct, independent datums Reference is copied. Change to datum is reflected in all other references
Pass Distinct copy is passed to function. Changes have no effect outside function Reference is passed to function. Changes effect all other references
Compare Two distinct values are compared (byte for byte) Two references are compared to see if they refer to the same value (references to distinct values are not equal even if byte for byte)

In JavaScript, primitive types (numbers, booleans, string) are manipulated by value, and all others are manipulated by reference:


    var x = 1;
    var y = x;
    y++;
    console.log(x, y); //1, 2

    function move (pt, dx, dy) {
      pt.x += dx;
      pt.y += dy;
    }
    var point = {x: 1, y: 1};
    move(point, 2, 2);
    console.log(point); //{x: 3, y: 3}
        

objects and arrays are passed to functions by value, but the value that is passed is actually a reference rather than the object itself

function

Functions are a set of statements that perform a task or calculate a value. They are fundamental to JavaScript, and are the source of much of it's flexibility and power. This flexibility can lead to confusion, however, not least because there are several (similar) ways to define a function in JavaScript:

declaration

A function declaration starts with the function keyword, followed by an identifier (name), a list of optional parameters, and a block of statements:


    function name (param1, param2) {
      statement1;
      statement2;
    }
        

expressions

If the identifier is omitted, a function is "anonymous", and referred to as a function expression. Confusingly, however, a function expression may actually have an identifier, in which case context determines whether it is a declaration or an expression:


    function foo() {} //declaration, part of program

    var foo = function() {}; //expression due to assignment

    (function foo() {}); //expression due to grouping operator

    var foo = function () {
      function bar() {} //declaration, part of function body
    }
        

Immediately Invoked Function Expression

An Immediately Invoked Function Expression (IIFE) is a convenient way to define and then immediately execute a function expression:


    (function (param) {
      var bar = 'bar';
      var foo = function() {
        console.log(param);
      };
      foo();
    })('hi');
        

The primary benefit to this pattern is that, by leveraging function-block scoping, it prevents local variables from polluting the global scope.

be a good JavaScript citizen and don't pollute the global space

named function expressions

When a function is assigned and given an identifier, it is referred to as a named function expression:


    var bar = function foo() {};
        

Named function expressions are primarily useful for debugging purposes, adding descriptive function names to error call stacks. They also enable a function to refer to itself in a recursive call (the function name is scoped to the function body, and not available from the outside). However, because of poor implementation in Microsoft's JScript, which considers named function expressions as both declaration and expression, it results in a memory leak, and should therefore be avoided if IE is a target browser.

named function expressions cause memory leaks in IE

hoisting

Although they are not assigned to a variable, function declarations are also subject to hoisting. Unlike variable hoisting, however, where only the identifier is hoisted and not the expression, function hoisting moves the whole function definition to the top of it's function scope. In practice, this means that you can safely call a function declaration before it is defined:


    console.log(foo()); //"bar"

    function foo() {
      return "bar";
    };

    console.log(bar()); //error: undefined is not a function

    var bar = function() {
      return "foo";
    }
        

Because of this hoisting behaviour, you should never conditionally declare functions:


    if (true) {
      function foo() { return "I'm foo"; }
    } else {
      function foo() { return "No, I'm foo"; }
    }

    //use an expression instead
    var foo;
    if (true) {
      foo = function() { return "I'm foo"; }
    } else {
      foo = function() { return "No, I'm foo"; }
    }
        

conditionally declaring functions is non-standard and will lead to unpredictable behaviour

arguments

The arguments object is an array-like object available within all functions, storing all the parameters passed to the function during execution. It has a length property, and can be iterated, but shares no other Array equivalent methods or properties.


    function foo() {
      console.log(arguments.length, arguments)
    }
    foo(1, 2, 3); //3, [1,2,3]
        

context

As discussed previously, scope in JavaScript determines what variables a function has access to. A function's context, on the other hand, determines what objects a function has access to when executed. In general, a function's context relates to the meaning of the this keyword inside of a function body.

this

The value of the this keyword is dynamic, and always dependent on where and when it is referenced during execution. In general, the value of this in a function is resolved depending on how the function is executed:


    console.log(this); //window (global context)

    var obj = {
      x: 10,
      foo: function() {
        var x = 1;
        console.log(x, this.x); //1, 10 (foo method context is obj)
        var bar = function() {
          console.log(x, this.x); //1, undefined (bar function context is global)
        }
        bar();
      }
    }
    obj.foo();
        
Using context to fix scope

As illustrated above in the last example, it's sometimes difficult to both have the correct scope and context. One solution would be to use this context in a manner that preserves the scope:


    var obj = {
      x: 10;
      foo: function() {
        var x = 1;
        this.bar = function() {
          console.log(x, this.x);
        }
        this.bar();
      }
    }
    obj.foo(); //1, 10 (bar function context is obj)
        
Using scope to fix context

Another solution to the same problem would be to use scope to capture the current context:


    var obj = {
      x: 10,
      foo: function() {
        var x = 1;
        var self = this;
        var bar = function() {
          console.log(x, self.x);
        }
        bar();
      }
    }
    obj.foo(); //1, 10 (bar function context is global)
        

application

All functions inherit behaviour from the Function object, including the methods call() and apply():

These methods allow functions to be executed (applied) with specific contexts, enabling all sorts of flexible constructions, including the borrowing of behaviour from one object to another:


    var foo = {
      name: 'foo',
      toString: function() {
        console.log(this.name);
      }
    };
    var bar = {
      name: 'bar'
    };
    //call foo's toString() in the context of bar
    foo.toString.call(bar); //'bar'

    function foo() {
      //convert arguments to Array
      var args = Array.prototype.slice.call(arguments);
    }
        

binding

Function binding attempts to freeze a function's context by binding this to a specific object, regardless of how the function is executed:


    var bind = function(func, thisValue) {
      return function() {
        return func.apply(thisValue, arguments);
      }
    }
    var person = {
      name: "Joe Smith",
      hello: function(thing) {
        console.log(this.name + " says hello " + thing);
      }
    }
    var boundHello = bind(person.hello, person);
    boundHello("world"); //"Joe Smith says hello world"
        

ECMAScript 5 has added a bind method to the Function object

Function binding is particularly useful when subscribing to event handlers, allowing callbacks to be bound to a specific object context:


    var main = {
      width: 0,
      onWindowResizeHandler: function() {
        this.width = document.documentElement.clientWidth;
      }
    }
    window.addEventListener('resize', bind(main.onWindowResizeHandler, main), false);
        

closures

Function closures are a special application of scope, and are in essence an object that combines both a function and the environment (scope) in which it was created. Closures are created when an outer function closes over the definition of an inner function along with the outer function's scope (by preserving and passing along the value of parameters and local variables to the inner function).


    function additionFactory(x) {
      return function (y) {
        return x + y; //unique value of x is preserved
      }
    }
    var add10 = additionFactory(10);
    var add20 = additionFactory(20);
    console.log(add10(2)); //12
    console.log(add20(5)); //25
        

objects

As we have seen, very few things in JavaScript are not objects one way or another, including Arrays, Functions, and the primitive types that automatically borrow functionality from related objects. At it's most basic, an object is simply a container for values defined by a unique key:


    var obj = {
      key: value
    }
        

Values can be any valid JavaScript type, including other objects. If a key references a function, it is referred to as a method of the object, otherwise it is referred to as a property of the object.

creation

Creating a unique object is as simple as using the object literal notation above to define parameters and behaviour. If you want to create more than one copy of an object, there are several approaches possible:

simple factory function

Use a simple factory function to generate a new object with attached behaviour:


    function makeObj(name) {
      var obj = {};
      obj.name = name;
      obj.speak = function() {
        console.log('my name is ' + this.name);
      }
      return obj;
    }
    var obj1 = makeObj('obj1');
    obj1.speak(); //'my name is obj1'
    var obj2 = makeObj('obj2');
    obj2.speak(); //'my name is obj2'
        

constructor function

In JavaScript, constructor functions are just plain function declarations. By convention they use a capitalized identifier, and when called with the new keyword, return an object instance containing the defined behaviour:


    function Obj(name) {
      this.name = name;
      this.speak = function() {
        console.log('my name is ' + this.name);
      }
    }
    var obj1 = new Obj('obj1');
    obj1.speak(); //'my name is obj1'
    var obj2 = new Obj('obj2');
    obj2.speak(); //'my name is obj2'
        

this in constructor functions references the new object's context, not the global context

inheritance

JavaScript uses prototypical inheritance to pass on behaviour from one object to it's descendants. In classical languages, classes are used to define a blueprint for constructing object instances, with classes extending classes to add more functionality. In JavaScript, however, inheritance is simply established through one object referencing another.

prototype

Each JavaScript object has a property called prototype that contains a reference to the object it was "descended" from. When a property or method is called on an object, the JavaScript interpreter walks this prototype chain until it finds the matching key. In this way, objects can share behaviour by sharing the same prototype object:


    function Obj(name) {
      this.name = name;
    }
    Obj.prototype.speak = function() {
      console.log('my name is ' + this.name);
    }
    var obj1 = new Obj('obj1');
    var obj2 = new Obj('obj2');
        

The benefit of using prototype to share behaviour across multiple instances is that memory need only be allocated for one copy of a method, rather than one per instance.

inherited properties and methods can be overridden by simply redefining them further down the prototype chain

mixins

Although prototypes are memory efficient, they suffer from the weakness that an object can only directly inherit from one object at a time. One way around this limitation is to "mixin" behaviour from another object. Quite simply, this means copying behaviour from one object to another:


    var extend = function(obj, extension) {
      for (var prop in extension) {
        obj[prop] = extension[prop];
      }
    }

    var voice = {
      speak: function() { console.log('hi'); }
    }
    var sight = {
      see: function() { console.log('I see you'); }
    }

    var myObj = {};
    extend(myObj, voice);
    extend(myObj, sight);
    myObj.speak(); //'hi'
    myObj.see(); //'I see you'
        

next week...js and the browser

60