JavaScript

The most-misunderstood language

Created by Christian Köberl

Intro

JavaScript ...

  • is actually called ECMAScript, JavaScript is only a dialect
  • is a functional, loosely typed language with prototypal OOP
  • is one of the most common programming languages
  • has nothing to do with Java (but the name)

JS History in 30 Seconds

  • Browser: Mosaic, Netscape, IE
  • Sun created Java
  • Netscape created JavaScript
  • IE created JScript
  • Standardized under ECMA: ECMAScript

Versions

  • ES3, JScript 5.5 - 8.0, JavaScript 1.5 - 1.8.3
  • ES5, JScript 9.0+, JavaScript 1.8.5 (IE9+, FF4+, Chrome, Safari5+)
  • ES6 / ES2015 (Edge, FF, Chrome, Safari)
  • ES20**/ES.next

Basic stuff

Variables - var (1)

  • Always declare variables:
    var i = 4;
  • Declare variables at begin of function:
    function myFunc() {
      var i, greeting = 'Hello ';
    
      for(i = 0; i < 10; i++) {
        console.log(greeting + i);
      }
    }

Variables - var (2)

Variables have function scope!
function bench() {
  // this is an infinite loop!
  for(var i = 0; i < 1000; i++) {
    for(var i = 0; i < 10; i++) {
      var u = i * 10;
    }
    // u is still in scope here
    console.log(u);
  }
}

Statements and Semicolons

var x = 5
3
x // 5

var y = 5
+ 3
y // 8

var z = 5
(3) // TypeError: number is not a function
  • Always use ; at the end of statements
  • Use JSLint to verify this

Comparison

  • Always use === (!==) to compare stuff
  • == (!=) does type coercion
'' == '0'           // false
0 == ''             // true
0 == '0'            // true

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

false == undefined  // false
false == null       // false
null == undefined   // true

Be Aware of Numbers

  • Number class/prototype
  • IEEE floating point double precision - aka double
  • IEE FP are not real:
    0.1 + 0.2 !== 0.3
    0.1 + 0.2 === 0.30000000000000004
  • Correct money arithmetics:
    1. Transform to whole EUR/$: amount * 100
    2. Calculate
    3. Transform back: amount / 100

Never (ever) use "with"

with(user) {
  // read to user.name or global variable 'name'?
  var userName = name;

  // write user.password if user has password
  // otherwise global variable 'password' :(
  password = generatePwd(userName);
}

Functions

Functions in JS are

  • Functions
  • Methods
  • Constructors
  • Classes
  • Modules

Functions as functions

// create a function and a variable
function func1(arg1, arg2) {
  ...
  return x;
}

// same can be achieved with
var func2 = function(arg1) {
  ...
}

// execute anoynmous function
(function () {
  ...
}());

Arguments

  • arguments is an array-like structure
  • In ES5 arguments inherits from Array
  • arguments.lengthis count of arguments passed
  • Never change arguments! Only read-access.

Arguments

// ES5
function sum() {
  return arguments.reduce(function(x, y) {
    return x + y;
  });
}

Invoking Functions


myFunction(); // function form

myObject.myFunction(); // method form

new MyFunction(); // constructor form

myFunction.apply(thisObject, [arg1, arg2]); // apply/call form
myFunction.call(thisObject, arg1, arg2)
            

What the hell is this?

Function form global object (window),
ES5: undefined
Method form the object the method was called on
Constructor form the object currently created
Apply/call form the object passed as parameter

Closure and Advanced Functions

Closure is cool :)

var myThing = (function() {
  var x = 42; // x is private
  return {
    // but can be accessed via getX
    getX : function() { return x; }
  };
}());

myThing.getX(); // 42

But be aware of closure

for(i = 0; i < elms.length; i++) {
  elm = elms[i];
  elm.onclick = function() {
    elm.style.display = 'none'; // this will always be the last elm
    return false;
  }
}

⇒ never define functions in loops!

function hideHandler(elm) {
  return function() {
    elm.style.display = 'none';
    return false;
  };
}
for(i = 0; i < elms.length; i++) {
  elm = elms[i];
  elm.onclick = hideHandler(elm);
}

Callbacks Using Higher Order Functions

function calc(args, callback) {
  var result = someComplexCalc(args);
  callback(result);
}

calc(data, function(result) { 
  myObj.calculated = result; 
});

Classical example: Prototype Ajax

new Ajax.Request('/your/url', {
  onSuccess: function(response) {
    // Handle the response content...
  }
});

Encapsulated Functions

// encapsulated functions to be called only once
function once(func) {
  return function() {
    var f = func;
    func = null;
    return f.apply(this, arguments);
  }
}

// now calc result can only be set once 
calc(data, once(function(result) { 
  myObj.calculated = result; 
}));

Partial Application and Currying


function add(x, y) {
  return x + y;
}
function inc(x) {
  return add(x, 1); // looks like currying 
}

// real currying
Function.prototype.curry = function() {
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function() {
    return fn.apply(this, args.concat(
      Array.prototype.slice.call(arguments)));
  };
};
var inc2 = add.curry(1);

Polyfills and Shims

What Are Polyfills, Shims?

  • Polyfill: add functionality of an unsupported API
  • Shim: delegate API calls to another version of an API
  • Examples:

Polyfill/Shim Example

Simple polyfill for ES5 forEach function for arrays
if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(fn, scope) {
    for (var i = 0, len = this.length; i < len; ++i) {
      fn.call(scope, this[i], i, this);
    }
  }
}

Polyfill/Extend API With Mixins

function extend(destination, source) {
  for (var property in source) {
    destination[property] = source[property];
  }
  return destination;
}

var myMixin = {
  clear : function() { return this.splice(0, this.legth); }
  filter : function() { ... },
};

extend(Array.prototype, myMixin);

Object-oriented programming

Objects

Objects are associative arrays

var myObj = { 
  fname : 'Christian',
  lname : 'Köberl',
  toString : function() { 
    return 'This is ' + this.lname.substring(0, 2);
  } 
};

myObj.fname; // 'Christian'
myObj['lname']; // 'Köberl'
myObj.toString(); // 'This is Kö'
myObj['toString']; // function() { return 'This is ' + ...

myObj.dateOfBirth = new Date();
myObj['lname'] = 'Muster';
            

Prototypal Inheritance

// ES5 polyfill
if (!Object.hasOwnProperty('create') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

var newObj = Object.create(myObj);
newObj.toString(); // 'This is Kö'

newObj.toString = function() { return 'My ' + this.fname; }
newObj.toString(); // My Christian

Classical OOP in JavaScript

function Person(id) {
  this.id = id;
}
Person.prototype.toString = function() {
  return 'Person (id=' + this.id + ')';
}

var p = new Person(5);
p.toString(); // Person (id=5)

Pseudoclassical Inheritance

function Person(id) {
  this.id = id;
}
Person.prototype.toString = function() {
  return 'Person (id=' + this.id + ')';
}

function Employee(id, employeeNo) {
  this.id = id;
  this.employeeNo = employeeNo;
}
Employee.prototype = new Person();
Employee.prototype.toString = function() {
  return 'Employee (id=' + this.id + ', no=' + this.employeeNo + ')';
}

Functional Class Model

function createPerson(name) {
  // private
  var id = null;
  function doGetId() {
    return id || generateId();
  }
  // public (privileged)
  return {
    name: name,
    getId : function() {
      return doGetId();
    }
  }
}

Parasitic Inheritance

function createEmployee(name, employeeNo) {
  var that = createPerson(name);
  that.toString = function() {
    return 'Employee (name=' + that.name
           + ', no=' + employeeNo + ')';
  }
  return that;
}

var e = createEmployee('Christian', 42);
e.name = 'Hubert';
e.toString(); // Employee (name=Hubert, no=42)

Singleton Pattern

var adder = (function() {
  // private stuff
  var calcCount = 0;
  function doAdd(x, y) {
    calcCount++;
    return x + y;
  }
  // public stuff
  return {
    add : function(x, y) {
      return doAdd(x, y);
    },
    calcCount : function() {
      return calcCount;
    }
  };
}());

Modules and AMD

Private Module Pattern

(function() {
  // everything is private
  var i, links = document.getElementsByTagName('a');

  function createConfirmListener(link) {
    return function() {
      return confirm('Open link ' + link.href + '?');
    }
  }

  // but we change global behaviour
  for (i = 0; i < links.length; i++) {
    links[i].onclick = createConfirmListener(links[i]);
  };
}());

Global Module Pattern

(function(global) {
  // private stuff
  var calcCount = 0;
  function doAdd(x, y) {
    calcCount += 1;
    return x + y;
  }
  // public stuff
  global.ADD_MODULE = {
    add : function(x, y) {
      return doAdd(x, y);
    },
    calcCount : function() {
      return calcCount;
    }
  };
}(window));

Extendable Global Module Pattern

// in math.js
MATH_MODULE = { calcCount : 0; };

// in add.js
MATH_MODULE.add = function(x, y) {
  MATH_MODULE.calcCount += 1;
  return x + y;
}

// in mult.js
MATH_MODULE.mult = function(x, y) {
  MATH_MODULE.calcCount += 1;
  return x * y;
}

Asynchronous Module Definition (AMD)

  • Modules are loaded asynchronously
  • Dependencies are injected
  • require.js curl.js
  • define modules in JS files
  • require modules in main app

AMD Usage

define('pocoModule', ['dep1', 'dep2'], function (dep1, dep2) {
  var priv1 = 'Hello';
  function priv2 { ... }
  ...
  // Define the module value by returning a value.
  return function () {
    publicVal : priv1 + ' World',
    enhanceLink : function() { ... }
  };
});
require(['jQuery', 'pocoModule'], function ($, poco) {
  $('a').each(function() {
    poco.enhanceLink($(this));
  });
});

ES6 und ES.next

Scoped variables (let, const, function)

const length = 10; // length cannot be changed
for (let i = 0; i < length; i++) {
  function geti() {
    return i;
  }
}
// i, geti are out of scope here

Arrow functions

const inc = (a) => a + 1;

[1, 2, 3].map(inc).forEach((entry) => {
  console.log(entry);
});
            

Arrow functions preserve this:

this.nums.forEach((num) => {
  // this is the outer this :)
  if (num > this.max) {
    this.max = num;
  }
});

Template Strings

const fullName = `${person.first} ${person.last}`

const multiLine = `Hello ${person.gender === 'M' ? 'Mr.' : 'Ms.'} ${fullName}!

Your car VIN is ${car.vin}.

Regards ${company.name}!`

Classes, Methods and Properties

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  get first() { return this.firstName; }
  set first(firstName) { this._firstName = firstName; }
}
            

firstName is still public

Objects with Methods/Properties

const person = {
  firstName: "Christian",
  lastName: "Köberl",
  fullName() { return `${this.firstName} ${this.lastName}`; }
}
let person = {
  get firstName() { return "Christian" },
  get lastName() { return "Köberl" },
  fullName() { return `${this.firstName} ${this.lastName}`; }
}

Promises

fetch('/api/user') // returns a Promise
  .then((response) => response.json())
  .then(user => {
    this.user = user;
  });
// Create a promise
const inOneSecond = new Promise((resolve, reject) => {
  setTimeout(() => resolve(`${msg} Hello ${who}!`), 1000);
});
inOneSecond.then(console.log);

Async / Await (ES2017)

async function load() {
  const result = await fetch('/api/user');
  return await response.json();
}

this.user = await load(); // this won't work, load returns a promise

(async () => {
  this.user = await load();
})()

THX

Questions?

Sources