JavaScript Concepts

  • "Java"?Script, ECMAScript? ES? ECMA-262? TC39?
  • ES Variants; What makes JS Unique
  • Base concepts: Objects, this, Strict Mode, Scope & Context, Closures
  • Prototypal Inheritance; Modules
  • Isomorphic & Universal JS
  • Web Components; Service Workers & Web Workers; WebAssembly
  • Dependency Injection

v4.0.1

"Java"?Script, ECMAScript? ES? ECMA-262? TC39?

JavaScript has a sordid history worthy of a daytime soap.

The early years

  • JavaScript was first released with Netscape Navigator 2.0 beta in September '95.
  • JavaScript was originally officially called LiveScript but that didn't even survive the beta releases of Netscape Navigator 2.0.
  • "Java"Script was chosen because Java was starting to make it big into the browser, and then want to sound that and also snub Microsoft.
  • Sun Microsystems trademarked "JavaScript" which eventually passed to Oracle. Netscape/Mozilla was granted a license to use the name.

The "Browser War" Years

  • Microsoft developed JScript by reverse engineering JavaScript, and Macromedia/Adobe had ActionScript which was a derived in part from HyperCard HyperTalk.
  • ECMA-262 ECMAScript became the first official standard in June '97, and TC-39 (Ecma International, Technical Committee 39) was established.
  • ECMAScript 2 in June '98, and v3 December '99.
  • Things were not harmonious with v4 "Harmony" and it was dumped, but not after a lot of time was wasted.

ES3 Long Reigned, then ES5

  • So ES3 is what most of you are probably familiar with, and worked with.
    • Today's JavaScript is much more mature and advanced - but we'll get to that later
  • It took 10 years for ES5 to come out Dec '09. It included significant improvements. Followed by 5.1.

The Mature Years

  • Things got serious and ES6/ECMAScript 2015 was released in June '15, which included fundamental changes.
  • ES7/ECMAScript 2016 in Jun '16, and included only a couple minor things.
  • ES8/ECMAScript 2017 in Jun '17, and besides some minor things, includes async

The Mature Years, 2

  • ES9/ECMAScript 2018 in Jun '18, and includes Async iterators and improvements to Rest/spread, regexp, and template literal, adds Promise.prototype.finally()
  • ES10/ECMAScript 2019 in Jun '19, and includes tweaks to catch, JSON strings and stringify, Symbols, function toString, string trim, plus new Object.fromEntries, array flat & flatMap.

Naming and Standards

  • "TC39 - ECMAscript" is the name of ECMA task group responsible
    • Standardization of the general purpose, cross platform, vendor-neutral programming language ECMAScript
  • Test262 are the official syntax tests of TC39.
  • The "stage" process calls for releases every year.
  • ES.next is a common term for proposals attempting to go through the TC39 process.
  • ECMAScript yyyy is the official name, but most use ESx.

The TC39 Process

Five stages of which the later 4 are considered "maturity" level/stages. TC39 must approve each "change"/spec.

  • Strawman - General "input" to the spec
  • 1, Proposal - Polyfills / demos
  • 2, Draft - all major semantics, syntax and API are covered, but perhaps incomplete
  • 3, Candidate - spec complete
  • 4, Finished - Test262 tests; two JS VM's implementations validate against tests; "shipping"

See the process docs tc39.github.io/process-document/

Noteable ES.next in-process (Stage 3) Proposals

  • Static class fields and private static methods
  • BigInt
  • Private instance methods and accessors
  • Class Public Instance Fields & Private Instance Fields
  • Top-level await
  • Optional Chaining

There's more Stage 3, ~16 Stage 2, ~48 Stage 1, and ~20 Stage 0 so JS will be changing indefinitely.

ES Variants

I don't know of any other language that has spawned so many variants.

  • Node: Introduced '09, it uses Google's V8 JavaScript engine, plus numerous custom modules, to run outside the browser and provide networking, file IO, and more.
  • WebAssembly (wasm): Announced '15 it's a low-level binary "target" that will run in the JS engine with the aim of compiling from C/C++. Avail in most browsers.

ES Variants, 2

  • TypeScript: Introduced in '12 by Microsoft, it stresses types and classes. It's strongly preferred by Angular and others.
  • Dart: Introduced '11 by Google it's a full stand-alone language, that also can compile to ES to run in the browser. Seems to be favored only be Google (internally).

ES Variants, 3

  • asm.js: Introduced '13 by Mozilla, it's a strict subset of JS, and strongly typed. Its aim is performance. Most code written is transpiled using EMScripten. It's essentially being replaced by WebAssembly.
  • CoffeeScript: Introduced in '09, influenced by Ruby and Python, yet influenced ES6. It has no real future, despite still being updated.

ES Variants, 4

  • PureScript: "a small strongly typed programming language that compiles to [idiomatic] JavaScript [without a runtime]." In integrates with others including React.
  • JSX (JavaScript XML): Part of Facebook's React, it's HTML, JS (extended, including types) and even CSS written together which gets processed into JavaScript. It uses Babel. It laughs at separation of concerns.

ES Variants, 5

  • ActionScript: Introduced '98 by Adobe (then Macromedia), it was based on HyperTalk/HyperCard, became more JS like, but now it's more TypeScript like.
  • Elm: Introduced '12, it's a statically typed, pure functional language. There's also a package registry.

ES Variants, 6

  • Haxe: Has it's origins in ActionScript 3, but it's real difference is its transpilier which can generate JS, Flash, C#/++, PHP, Phython, and Lua.
  • AtScript: Introduced in '14 by Google for Angular 2.0, its been discontinued (Mar '15) in deference to Typescript which as of v1.5 added/merged many of its features.

ES Variants, 7

  • ClojureScript: "... is a compiler for Clojure that targets JavaScript." It's a "dialect of Lisp", and Functional. A promising, if radical, future.
  • Flow: "A static type checker for JavaScript". It uses annotations and syntax, along with type inference, to find bugs.

ES Variants, 8

  • F#: Introduced '05, by Microsoft; it's strongly typed but "multi-paradigm", including functional, imperative, OO, async and parallel. As of v3 in '12, JavaScript was introduced as target platform.
  • OCaml: "OCaml is an industrial strength programming language supporting functional, imperative and object-oriented styles." Hack, Flow, Haxe, WebAssembly compiliers are written in Ocaml. F# is based on Ocaml. BuckleScript (early '16) by Bloomberg has a JS target.

ES Variants, 9

  • Reason: Introduced by Facebook, it's an "approachable [from JS perspective] interface to the OCaml", plus build system, NPM & BuckleScript integration, and more.
  • Kotlin: Introduced by Jetbrains, it runs under the JVM and hopes to be a "better Java", can also be compiled to JavaScript

What makes JavaScript Unique

JavaScript runs in the "event loop" which is, not surprisingly, event-driven. There is only a single thread and is essentially non-blocking; which avoids having to deal with thread management, locks or race conditions.

Essentially all IO commands are non-blocking.

Concurrency Model

There's three main components:

  • [Main] Stack - is a stack of "frames" each one being a function call, and operates first in, last out
  • Heap - an unstructured memory area containing all the objects
  • [Callback] Queue - where async code is "stored" awaiting execution

Additionally:

  • Job Queue - similar to the Callback Queue, but only for thenable methods from new Promise()

JS Event Loop

The Event Loop basically runs it all, basically in this priority:

  1. Main stack
  2. Job Queue
  3. Callback Queue

But, of course, it's actually more complicated than that (tasks, microtasks, Workers, IO handlers, etc.), and there's some cross-browser variation.

More JS Uniqueness

  • "Run-to-completion" - functions are "atomic": when one runs, it runs, without pre-emption, to completion. Because of this a long-running (or infinite loop) functions can cause blocking.
  • "tick" - name for each iteration when the engine polls the queue and selects another message to process.
  • Async callbacks always get executed "later": at least the next tick.

The result (and purpose, really) of all this is that JavaScript is async, and that has a significant effect on how to code in JS.

Event/Async Coding

"Callbacks" is the traditional way of handling async programming. This basically means passing a function (either by name or an anon function) that will be "called back" when done.

I have a medium post on async patterns with callbacks medium.com/@ToddZebert/javascript-async-patterns-a-progression-part-1-callbacks-530bc3111b55

JavaScript Concepts:

Objects

Primitives are not objects, and are: undefined, null, boolean, string and number.

Magically, some primitives are "un/boxed" (un/wrapped) in objects when coerced: boolean, string and number.

Objects are Boolean, String, Number, Array, Object, RegExp, and Date.

Objects contain properties and methods, have a constructor from which they inherit from, are mutable, and more.

this

this is a property that references an object defining the context at invocation of a function.

It acts somewhat like a pronoun does in English: it points to the antecedent - i.e. the noun in a sentence.

JavaScripts this isn't too dissimilar to PHP's $this.

The Value of this

  • this used in the global scope, a global function, or unbound anon functions point to window, except under strict mode, where it's undefined.
  • Otherwise, it contains the object that invoked the function.

Changing this' value

this doesn't have to contain the default value, it can be altered using three methods.

call and apply are very similar, and invoke a function:

  • apply: pass context object, and arguments as an array.
    
    aMethod.apply(myContext, myParamArray);
    
  • call: pass context object, and arguments listed individually.
    
    aMethod.call(myContext, myParam1, myParam2 /* ,etc */);
    

Whereas bind returns a function. It's also useful for Partial Application (a Functional Programming concept).

  • bind (ES5): pass context object, and arguments listed individually.
    
    var newMethod = aMethod.bind(myContext, myParam1, myParam2 /* ,etc */);
    newMethod(/* params */);
    

All can be used to "borrow" methods, or invoke/compose variable-arity (aka multi-arity) or variadic functions.

Strict mode

An ES5 concept that tells the JS engine to run in a slightly different way, which is beneficial but can also cause issues.

  • It replaces some silent errors by throwing errors.
  • Fixes language "mistakes" that make it easier for JS engines to optimize.
  • It prohibits some syntax likely to be defined in future versions of ES.
  • Improves security.

Using strict mode

While the command can be used at the top of files, it's best in each function:


function myFunc() {
  "use strict";
  // do something
}
						

Enclosing it in quotes uses a "feature" of JS syntax that the statement is ignored by non-compliant engines.

What does Strict mode do?

It causes a syntax error or throws an error:

  • Attempt to create a global var.
  • Attempt to assignment to a non-writable property.
  • Attempt to delete an undeletable property.
  • If function parameter names are not unique.
  • If you use an Octal syntax - it's not part of ES5 even though browsers support it. In ES6 the syntax is prefix with "0o".
  • If you set properties on primitive values.
  • Prohibits with.

More effect of strict mode

  • eval of strict mode code (or in strict mode) prevents vars entering surrounding scope.
  • Makes eval and arguments syntax simpler.
  • Vars passed as this are not "boxed" (forced into an object).
  • Prevents walking the stack using common JS browser extensions.
  • Function arguments don't have access to the corresponding function call's variables.
  • Makes some identifiers reserved keywords.

There's a handful of JS Engine specific changes also.

Strict and ES6

Since "use strict;" was intended for ES5, changes from ES6 can cause parsing problems.

So TC-39 just decided to simply avoid these issues by disallowing stict mode within a function definition when the function's parameters...:

  • Have default values
  • Use destructuring
  • Include a rest parameter (not "REST")
  • ... and a couple rare edge cases

ES6 Strict, 2

"Global" strict mode (outside of function definition) is OK, as well as wrapping within in IIFE.

ES6 Modules and Classes are intrinsically strict mode.

Scope & Context

Every invocation of a function has both an associated scope and context.

Scope is unique to a function (and blocks in ES6+ using let) when it is invoked, and is the access to / visibility of variables.

Context is the value of the this keyword.

Closures

Closures are a way of structuring a function such that the inner (child) function "memorizes" the outer (parent, or enclosing) local scope so that it persists between calls to the inner function.

Anytime you use the function keyword* inside a function, you are potentially creating a closure.

* Or the => (["fat"] Arrow functions) keyword in ES6

Code Example


var myFunc = function (x) {
  var myClosure = function () {
    console.log(x++);
  };
  return myClosure;
};

var myInst = myFunc(10);
myInst(); // 10
myInst(); // 11
						
Do not create closures inside a loop or you're likely to get unexpected results.

Prototypal Inheritance

Prototypal Inheritance is all about objects. Objects inherit properties from other objects.

This stresses "Composition over inheritance", i.e. avoid inheritance hierarchy. Compose objects, not object inheritance, and use Mixins and Decorators.

Prototypal Code

There's too much to get into here so I'm just going to hit some code:


function Square (side) { this.side = side; }

Square.prototype.perimeter = function () {
  return this.side * 4;
};

Square.prototype.area = function () {
  return Math.pow(this.side, 2);
};
var squareA = new Square(5);
var squareB = new Square(15);

squareA.area(); // 25
squareB.perimeter(); // 60
						

Prototypal Inheritance Code


var MyParentObj = (function () {
  function MyParentObj(cParam) { this.pParam = cParam; }
  MyParentObj.prototype.pDoit = function (mParam) {
    console.log('pDoit: ' + mParam + '; ' + this.pParam);
  }
 return MyParentObj;
})();

var MyObj = (function () {
  function MyObj (cParam) { this.cParam = cParam; }
  MyObj.prototype = new MyParentObj('p');
  MyObj.prototype.cDoit = function (mParam) {
    console.log('cDoit: ' + mParam + '; ' + this.cParam);
  }
  return MyObj;
})();

var aObj = new MyObj('c');
aObj.cDoit(4); // childDoit: 4; c
aObj.pDoit(6); // parentDoit: 6; p
						

Modules

JavaScript has had various module solutions, but always implemented in various libraries. Now Modules are built into ES6.

Basically, modules are for exporting/importing "values" ("first class citizens") from/to modules.

Modules default to "strict" mode, so top-level vars are local (not global/window), top-level this is undefined (not window).

Loading Module Files

Loading "Scripts":



 // ext script
						

Loading "Modules":



						

Modules are stored in files. There is exactly one module per file, and one file per module.

Named Exports

The first of two ways to export, which can be used together, but it's recommended not to.


// myModule.js //
export const pi = 3.14159; // The Math handles these...
export function square(x) { return x * x; }
export class MyClass { /* etc */ }
export { someVar, someFunction } // by name
						

Default Exports

There's a single "default" export, which is anonymous, and there's no trailing semicolon.


export default function () { /* etc */ }
export default class { /* etc */ }
export default someName // something already defined
						

Many think default exports are something of a code smell.

Imports


import { square, diag } from 'myModule'; // named exports
console.log(square(11));

import * as lib from 'myModule'; // load whole module
var m = new lib.MyClass();

import myFuncName from 'myModule'; // from a "default"
myFuncName();
						

Isomorphic & Universal JS

With the success of client-side (CS) JS frameworks, challenges arose as compared to server side (SS):

  • SEO - will search engine crawlers work?
  • Load Speed - CS JS can be slow to load and present the initial UI with data.
  • Run Speed - CS JS rendering can be slow.

Isomorphic & Universal JS, II

With the rise of Node, some hope to use it as an opportunity to get the best of both worlds, CS and SS.

The definitions are vague and debateble - sometimes used interchangeably - but these are reasonable:

Isomorphic JS is seamlessly maintaining state regardless where the code is running (CS or SS) even when switching where the code is running.

Universal JS is code that can actually run in both SS (Node) and CS (usually some FE framework).

Isomorphic & Universal JS, III

Some frameworks have "variations" that encompass these concepts, such as Angular Universal, "Server-side Rendering for Angular 2 apps".

Some are built with the express purpose of working this way, such as React.

Web Components

WC's are native reusable encapsulations of Web interface "components" that include HTML, CSS and JavaScript without "leaking" those into other parts of the document.

They're comprised of:

  • Custom Elements - v1: Chrome, Firefox, Safari partial, IE/Edge no (except "Chromium Edge")
  • HTML Templates - Firefox, Chrome, Safari, and Edge
  • Shadow DOM - V1: Chrome, Firefox, Safari partial, IE/Edge no (except "Chromium Edge")
  • HTML Imports - Chrome only

Web Components

Other benefits of WC's:

  • It can really improve HTML semantics
  • They can be "portable" across sites

Warning: Much of Web Components is highly experimental and subject to even radical implementation changes at this time.

Custom Elements

They allow for creating custom HTML tags and elements. They may also be used apart from WC's.

What differentiates CE's from our current ability to arbitrarily add new tags and elements, is "lifecycle callbacks". These "events" occur when various things happen to the CE.

Custom instances of built-in elements are also possible.

Custom Elements have gone through two major revisions, "v0" and "v1"

HTML Templates

Allows for instantiation/rendering of content fragments via JS.


<template id="some-id"><!-- some html --></template>

Similar functionality was often done custom like:


<script type="text/html" id="some-id"><!-- some html --></script>

Apple published a proposal Nov 2017 for Template Instantiation which may change things...

Shadow DOM

The SD provides the encapsulation for Web Components, keeping its code components isolated from the main DOM.

They're not dissimilar from iframe's which are intended for full documents, while SD is for embedded partial documents and is less restrictive.


It's like an iframe or HTML5's video tags.

Shadow DOM has gone through two major revs, "v0" & "v1"

HTML Imports

Used to load a "whole" WC from an HTML file, often with either inline or loaded CSS and JavaScript files.


<link rel="import" href="my_component.html">

Some are thinking that HTML Imports may go away, either to die or be replaced.

Components

While Web Components are an actual (evolving) W3C set of technologies, the concept of "Components" is really taking hold.

Most new (or newly revised) frameworks have some concept of "components" they implement.

See also The Gold Standard Checklist for Web Components github.com/webcomponents/gold-standard/wiki

Service Workers & Web Workers

Since JavaScript is single threaded and subject to its event loop, these "workers" provide ways for background tasks to run, and "threading".

Workers do not affect the UI's responsiveness.

Neither can directly interact with the DOM, have very resticted access, and are limited to communication through a special method.

Service Worker

SW are a network proxy that allow more control over network requests.

They are essential for building offline-first and/or PWA (Progressive Web Apps) and handling things like periodic background syncs, inconsistent network connectivity, push notifications, smart caching, and the like.

They in essence replace the App Cache API that had a number of flaws.

Service Worker, 2

Service Workers can use localhost but otherwise must use HTTPS.

SW's are too complex to offer any simple examples.

SW's are supported by Chrome, Firefox, Safari 11.1 (iOS Safari 11.3), and Edge 17.

Web Workers

WW are general purpose "threads" in which to run computationally intensive processes outside of the main UI thread.

Given JS's "run-to-completion" event loop, "blocking" code is quite easy to create; so run it in a WW.

Web Workers, 2

They come in two flavors, Dedicated and Shared:

  • Dedicated can only be accessed from the parent script.
  • Shared can be accessed from any script from that same domain.

WW's are well supported (even IE11) but Shared WW, Chrome, FF and "Chromium Edge" only.

WW Example


// main code
var myWorker = new Worker('my-task.js');
myWorker.addEventListener('aMessage', function(e) {
  console.log('from worker: ', e.data);
}, false);
myWorker.postMessage(messageObj); // Start worker & pass message

// in my-task.js
self.addEventListener('aMessage', function(e) {
  self.postMessage(e.data); // this just returns message
}, false);
						

Webassembly (wasm)

Brace yourself, webassembly is ... here!

  • Wasm is new code "format" that's portable, faster (near-native!), smaller, and load/decode efficient.
  • It's intendend as a "compilation target" meaning you can take a higher-level language and compile it to Wasm to run in the browser.
    • Initial efforts were to get C/C++ to compile, with other languages to come.
  • It's a binary format with formal semantics

Compilation

  • Binaryen is a compiler and toolchain infrastructure library for WebAssembly
  • Emscripten compiles C/C++ to asm.js and wasm
  • Rust (with the help of Emscripten) can compile to wasm
  • Run a whole Lua VM running in wasm
  • JavaScript and TypeScript to wasm (usually only subsets)

There's more that are considered expiremental.

Uses

  • Fast graphics (and game physics)
  • Image and video processing, DSPs
  • Scientific applications
  • Even "regular" apps like GMail, or frameworks like Angular could use it

It's supported in Chrome, Firefox, Safari 11, and even Edge 15 and Node! (Usually behind a flag)

Demo of UE using WebGL2: Epic Zen Garden (you'll need browser WebGL)

Dependency Injectiion (DI)

Instead of an object having to construct it's dependencies, you provide them by passing them in. It's another inversion of control.

While this can happen manually, often in frameworks it's handled by an Object Builder.

Why DI?

It makes the code more modular by reducing "coupling". Tight coupling is a code smell.

It helps a lot with testing because you can pass in a mock object during a test, whereas if the object had to ask for it's dependency you'd have to intercept to provide the mock.

DI Example


var MyParentA = (function () { /* A stuff */
  return MyParentA; })();
var MyParentB = /* like MyParentA but does "B stuff" */

var MyObjA = (function () { // non-DI
  MyObjA.prototype = new MyParentObjA();  return MyObj; })();
var MyObjB = /* like MyObjA but uses new MyParentObjB(); */

var MyObj = (function (parentObj) { // DI
  MyObjA.prototype = new parentObj();
  return MyObj; })();

var objA = new MyObjA(); // non-DI
var objB = new MyObjB(); // non-DI, same code but diff function
var diObjA = new MyObj(MyParentA); // DI
var diObjB = new MyObj(MyParentB); // DI, same code, same function
						

About Todd

Lead Wed Developer at @meetmiles