CSC447

Concepts of Programming Languages

Case Study: JavaScript

Instructor: James Riely

Video

Open on youtube

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

Getting Started

  • 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] */
    {} + [] /* 0 */
    {} + {} /* NaN */
                  
  • 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 and NaN
    
    undefined + 1 /* NaN */
                  

JS Wat

  • Many notions of equality with unpleasant results
    
    '' == '0'          /* false */
    0 == ''            /* true */
    0 == '0'           /* true */
    
    false == 'false'   /* false */
    false == '0'       /* true */
    
    false == undefined /* false */
    false == null      /* false */
    null == undefined  /* true */
    
    ' \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
          

Scope

  • Even let statements are subject to hoisting (within the block)

var a = 1;
{
  let b = a + 1; 
  let a = 2;
  console.log (" b = " + b);
}
          

Uncaught ReferenceError: Cannot access 'a' before initialization
          

Scope

  • A similar thing happens for variables (not fields) in scala

val a = 1;
{
  val b = a + 1; 
  val a = 2;
  println (" b = " + b);
}
          

3 |  val b = a + 1; 
  |          ^
  |          a is a forward reference extending over the definition of b
          

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, ...

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

Asynchronous programming example

  • Example: predictive typing for wikipedia entries
    • Listen for keystrokes
    • In response to keystroke:
      • initiate HTTP request to get completions
    • In response to HTTP:
      • show completions
    • Keystrokes and HTTP responses come in any order!
  • Design Patterns to address such problems, for example:

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