Concepts of Programming Languages

Case Study: JavaScript

Instructor: Stefan Mitsch

Case Study: JavaScript

  • Explain JavaScript in relation to prior topics
    • (scope and functions)

Case Study: JavaScript

  • Summary
    • dynamically-typed
    • first class functions and objects
  • Runs in browsers and elsewhere with Node.js
  • Inspired by
    • Scheme: dynamically-typed functional PL
    • Self: delegation-based object-oriented PL
  • JavaScript is not based on Java at all

JavaScript Evolution

Book Recommendations

JS REPL Development

  • Use console REPL in browser
  • Use Node.js REPL

Statements vs. Expressions

  • Enter JS expression in REPL
    
    1 + 2
                  
  • Print result as side-effect; undefined result
    
    console.log (1 + 2);
                  
  • Function declarations need return statements
    
    function f (x) {
      console.log ("Called with " + x);
      return x + 1;
    }
    
    f (5);
                  
    • (JS has statements and expressions)

Document Object Model

  • DOM tree model of HTML document
  • Browser renders the DOM, which can change over time
  • JS introduced for manipulating DOM tree
  • Callback functions for event handling

JS DOM Development

DOM Example

  • HTML body:
    
    <input id="a" type="text" value="empty"/>
    <div id="b"/><p>Hi Mom</p></div>
    <div id="c"/><p>Hi Dad</p></div>
                  
  • Add in Javascript
    
    function main () {
      var count = 0;
      var a = document.querySelector('input#a');
      var b = document.querySelector('div#b');
      var c = document.querySelector('div#c');
      for (var x of [b,c]) {
        x.onclick = function (e) {
          e.target.outerHTML += "<p>Again! " + count++ + "</p>";
      } }
      a.value = 1 + 2;
      c.innerHTML += "<p>I love teletubbies!</p>";
    }
    document.addEventListener("DOMContentLoaded", main);
                  

JS Wat

  • Complex conversions
    
    [] + [] /* '' */
    [] + {} /* [object Object] */
    {} + [] /* VS8: 0, Node.js: [object Object] */
    {} + {} /* VS8: NaN, Node.js: [object Object][object Object] */
                  
  • Dynamic types and optional arguments create surprising results
    
    xs = ["10", "10", "10"];
    xs.map(parseInt)   /* [10, NaN, 2] */
    [1,5,20,10].sort() /* [1, 10, 20, 5] */
                  
  • Undefined, NaN, and Infinity
    
    undefined + 1 /* NaN */
    false + 1     /* 1 (someone liked C) */
    5/0           /* Infinity */
    -1/0          /* -Infinity */
                  

JS Wat


            '' == '0'
            0 == ''
            0 == '0'

            10 == '10'
            9  == '9'
            10 > 9
            '10' > '9'
            1 + 9
            1 + '9'
            1 + '9' == 19
            -1 + '11'
            '11' - 1

            false == 'false'
            false == ''
            false == '0'
            false == 0
            true  == '1'
            true > false

            5/0 == 6/0
            0/0 == 0/0
            1/0 == -1/0

            /* floats suck (as usual) */
            1/10000000000000000000000      /* 1e-22 */
            1/100000000000000000000000     /* 1.0000000000000001e-23 */
            1/10000000000000000000000000   /* 9.999999999999999e-26 */
            1/1000000000000000000000000000 /* 1e-27 */
            
            false == undefined
            false == null
            null  == undefined
            Infinity == Infinity
            NaN   == NaN
            
            ' \t\r\n ' == 0
          

JS Wat


            '' == '0'          /* false */
            0 == ''            /* true */
            0 == '0'           /* true */

            10 == '10'         /* true */
            9  == '9'          /* true */
            10 > 9             /* true */
            '10' > '9'         /* false */
            1 + 9              /* 10 */
            1 + '9'            /* '19' */
            1 + '9' == 19      /* true */
            -1 + '11'          /* '-111' */
            '11' - 1           /* 10 */

            false == 'false'   /* false */
            false == ''        /* true */
            false == '0'       /* true */
            false == 0         /* true */
            true  == '1'       /* true */
            true > false       /* true */

            5/0 == 6/0         /* true: x/0 is Infinity for x>0 */
            0/0 == 0/0         /* false: 0/0 is NaN */
            1/0 == -1/0        /* false: x/0 is -Infinity for x<0 */

            /* floats suck (as usual) */
            1/10000000000000000000000      /* 1e-22 */
            1/100000000000000000000000     /* 1.0000000000000001e-23 */
            1/10000000000000000000000000   /* 9.999999999999999e-26 */
            1/1000000000000000000000000000 /* 1e-27 */
            
            false == undefined   /* false */
            false == null        /* false */
            null  == undefined   /* true */
            Infinity == Infinity /* true */
            NaN   == NaN         /* false */
            
            ' \t\r\n ' == 0    /* true */
          
Use ===, which does no conversions

Scope: Hoisting

  • Hoists variable declarations to top of nearest enclosing function
  • Initialization code is not hoisted.

var a = 1;
function f () {
  
  console.log ("f1: a = " + a);
  { var a = 2;
    console.log ("f2: a = " + a);
} }
function main() {
  console.log ("m1: a = " + a);
  f ();
  console.log ("m2: a = " + a);
}
          

m1: a = 1
f1: a = undefined
f2: a = 2
m2: a = 1
          

Scope: Hoisting (Equivalent code)

  • Hoists variable declarations to top of nearest enclosing function
  • Initialization code is not hoisted.

var a = 1;
function f () {
  var a; /* = undefined */
  console.log ("f1: a = " + a);
  { a = 2;
    console.log ("f2: a = " + a);
} }
function main() {
  console.log ("m1: a = " + a);
  f ();
  console.log ("m2: a = " + a);
}
          

m1: a = 1
f1: a = undefined
f2: a = 2
m2: a = 1
          

Scope: Hoisting (Block Scope)

  • ES6 introduced let
  • Block oriented scope (curly braces = blocks)

var a = 1;
function f () {

  console.log ("f1: a = " + a);
  { let a = 2;
    console.log ("f2: a = " + a);
} }
function main() {
  console.log ("m1: a = " + a);
  f ();
  console.log ("m2: a = " + a);
}
          

m1: a = 1
f1: a = 1
f2: a = 2
m2: a = 1
          

Scope

  • Hoisting happens anywhere!

var a = 1;
function f (b) {
  a = 2;
  if (b) {
    var a;
    a = a + 1; 
  }
  console.log (" f: a = " + a);
}
function main() {
  f (true);
  console.log ("m1: a = " + a);
  f (false);
  console.log ("m2: a = " + a);
}
          

 f: a = 3
m1: a = 1
 f: a = 2
m2: a = 1
          

Lexical Scope in JS

  • Functions and objects are first-class citizens
    • create at runtime
    • pass as args, return, store in data structures
  • Nested functions (and objects) are commonplace
    • JS uses static (lexical) scoping, see here
  • Nested functions common for callbacks:
    • DOM events: onclick, ...
    • Other asynchronous browser APIs: AJAX, Web Storage, Web Workers, Geolocation, ...
    • Collections processing: map, reduce, ...

Recursion

  • JavaScript standard includes tail-call optimization, but only Safari implements it
  • Can still make sense to think about alternative implementations, see Fibonacci

            function fibtail(n) {
              function fibtail(n, r1, r2) {               // nested function, inner n shadows outer n
                  if (n===0) return r1;
                  else if (n===1) return r2;
                  else return fibtail(n-1, r2, r1+r2);
              }
              return fibtail(n, 0, 1);
            }
          

Argument passing

  • JavaScript uses pass-by-value
  • Visible to outside
    • changes to fields of objects
    • changes to elements of arrays

            function swaparray(a) {
              let [x,y] = a; // deconstruct array
              a[0] = y;
              a[1] = x;
            }

            let a = [x, y];            
            swaparray(a);
            
            {
              [y,x] = [x,y]; // deconstruct array to swap in place
            }
          

Asynchronous programming

  • JS is single threaded
    • Only one thread to handle all JS code
    • Good: Simple
    • Bad: Need to keep JS code brief
      • Otherwise browser locks up!
  • Browsers are multithreaded
    • Real work happens in the browser API
  • There is also multithreading using Web Workers and WebAssembly

Asynchronous programming

  • Typical JS program:
    • At startup, register a bunch of callbacks on the browser
    • Wait for the browser to call back
    • When handling a callback, maybe create new callbacks
  • This can get complicated

Collections Processing

  • Map and friends built into modern JS
    
    var xs = [ 11, 21, 31 ];
    xs.map (x => (2 * x));
    xs.filter (x => x%7===0)
    xs.reduce (((z,x) => z+x), 0)
                  

Common Scope Problem

  • Recall javac requires final i from enclosing scope
    
    for (int i = 0; i < 5; i++) { /* rejected: i mutates */
      new Thread (new Runnable () {
          public void run () {
            while (true) { System.out.print (i); }
          }
        }).start ();
    }
                  
  • So a copy is made
    
    for (int i = 0; i < 5; i++) { 
      int x = i; /* accepted: x never mutates */
      new Thread (new Runnable () {
          public void run () {
            while (true) { System.out.print (x); }
          }
        }).start ();
    }
                  

Common Scope Problem

  • JS allows shared i; rarely what you want

var funcs = [];
for (var i = 0; i < 5; i++) {
  funcs.push (function () { return i; });
}
funcs.map (f => f());
          

[ 5, 5, 5, 5, 5 ]
          

Common Scope Problem

  • But how to copy i?

var funcs = [];
var x;
for (var i = 0; i < 5; i++) {
  x = i; /* x is shared too! */
  funcs.push (function () { return x; });
}
funcs.map (f => f());
          

[ 4, 4, 4, 4, 4 ]
          

Common Scope Problem

  • var is not block scope

var funcs = [];
for (var i = 0; i < 5; i++) {
  var x = i; /* x is still shared! */
  funcs.push (function () { return x; });
}
funcs.map (f => f());
          

[ 4, 4, 4, 4, 4 ]
          

Common Scope Problem

  • Block scope in using let

var funcs = [];
for (var i = 0; i < 5; i++) {
  let x = i; /* block scope */
  funcs.push (function () { return x; });
}
funcs.map (f => f());
          

[ 0, 1, 2, 3, 4 ]
          

Common Scope Problem

  • Before ES6, get function scope using IIFE
    • Immediately Invoked Function Expression

var funcs = [];
for (var i = 0; i < 5; i++) {
  (function () { 
    var x = i;
    funcs.push (function () { return x; });
  }) ();
}
funcs.map (f => f());
          

[ 0, 1, 2, 3, 4 ]
          

Common Scope Problem

  • Or by adding a helper function

var funcs = [];
for (var i = 0; i < 5; i++) {
  var help = function (x) {
    return function () { return x; }
  };
  funcs.push (help (i));
}
funcs.map (f => f());
          

[ 0, 1, 2, 3, 4 ]
          

Libraries

  • Javascript limitations often addressed initially via libraries
  • jQuery standardized the browser API
    • Defines a single function, named $
    • Smooths over browser inconsistencies, particularly IE
  • Underscore and Lodash provided map, reduce, etc
    • Defines a single object, named _

Extensions