JavaScript - ttulka/programming GitHub Wiki

Basics

  • Automatic semicolon insertion (ASI) will take effect in the presence of a newline.

Variables, constants and scopes

  • Any variable that you declare is by default defined in global scope.
  • As a global variable is visible in all other scopes, a global variable can be modified by any scope.
  • No block-level scope; instead, function-level scope.
var a = 1;
(function() {
  var a = 2;
  console.log( a ); // 2
})();
console.log( a ); // 1
  • let keyword to introduce traditional block scope (ES6).
var myv = 1;
let myl = "a";
const myc = true;
{
    var myv = 2;
    let myl = "b";
    const myc = false;

    console.log(myv + " " + myl + " " + myc);

    myv = 3;
    myl = "c";
    // myc = true;  /* cannot redefine a constant */

    console.log(myv + " " + myl + " " + myc);
}
console.log(myv + " " + myl + " " + myc);

prints:

2 b false
3 c false
3 a true

Computed property names (ES6)

const key = 'name'
const user = {
    [key]: 'Tomas',
}
console.log(user)  // { name: 'Tomas' }

Hoisting

Only var variables are partialy hoisted (only the variable name, not its value), functions are fully hoisted, let and const variables are not hoisted.

// prints Hello!
function a(){ console.log('Hi!') }
a(); 
function a(){ console.log('Hello!') }

Wherever a var appears inside a scope, that declaration is taken to belong to the entire scope and accessible everywhere throughout.

a = 2
var a
console.log(a)  // 2
console.log(a)  // undefined
var a = 2
var a = 2;
foo();     // works because `foo()` function declaration is "hoisted"
function foo() {
    a = 3;
    console.log( a ); // 3
    var a; // declaration is "hoisted" to the top of `foo()`
}
console.log( a ); // 2

Function declarations are hoisted, but function expressions are not:

foo(); // TypeError
bar(); // ReferenceError

var foo = function bar() { ... }

The variable identifier foo is hoisted and attached to the enclosing scope (global) of this program, so foo() doesn’t fail as a ReferenceError. But foo has no value yet (as it would if it had been a true function declaration instead of expression). So, foo() is attempting to invoke the undefined value, which is a TypeError illegal operation.

Hoisting happends on every execution context:

var a = 'A'; 
function f() { 
    console.log('1: a is', a);   // 1: a is undefined
    var a = 'AA';
    console.log('2: a is', a);   // 2: a is AA
}
f();

Special values null and undefined

Absence of meaningful values is indicated by two special values — null, when the non-value is deliberate, and undefined, when the value is not assigned to the variable yet.

var xl;  // undefined
xl;      // undefined
xl;      // undefined
null == undefined;  // true

An “undefined” variable is one that has been declared in the accessible scope, but at the moment has no other value in it. By contrast, an “undeclared” variable is one that has not been formally declared in the accessible scope.

With default parameters, undefined will use the default while null does not:

let logHi = (str = 'hi') => {
  console.log(str);
}
logHi(undefined);
// hi
logHi(null);
// null
  • Prefer undefined to null (null is a bad idea).
// this is a safe existence check
if (typeof DEBUG !== "undefined") {
    console.log( "Debugging is starting" );
}

The Strict Mode (ES5)

  • Variables must be explicitly declared
  • The with statement is not allowed.
  • Octal literals are not allowed (eg. 010 === 8 true)
'use strict';

Numbers

var decimal = 6;
var hex = 0xf00d;
var binary = 0b1010;
var octal = 0o744;

Special constants and methods

  • Number.MIN_VALUE and Number.MAX_VALUE; Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER
  • Number.parseInt' and Number.parseFloat'
  • Number.NaN and Number.isNaN
isNaN(NaN);  // true
NaN == NaN;  // false
isNaN("elephant"); // true
NaN + 5;     // NaN

Special math constants and functions

  • Math.E, Math.PI, Math.LOG10E, Math.LOG2E
  • Math.abs, Math.pow, Math.sqrt, Math.min, Math.max, Math.trunc, Math.round, Math.ceil, Math.floor
  • Math.random <0;1)
Math.random(); // 0.24329434322856125
Math.floor(Math.random() * Math.floor(5));  // 0,1,2,3,4

In JavaScript, binary floating point numbers do not map correctly to Decimal numbers:

.1 + .2   // 0.30000000000000004
  • For true decimal math use big.js

Strings

  • Sequence of Unicode characters (each 16 bit)
  • Quotes ' and double quotes " are equivalent.
  • Apostrophes (Grave accent) allows multiline-strings and variables resolution.
console.log(`Value: ${obj.value}`);
  • Strings are not just arrays of characters. Strings are immutable while arrays mutable.
"foo".repeat(3)        // foofoofoo

"bar".startsWith("b")  // true
"bar".endsWith("r")    // true
"bar".includes("a")    // true

Booleans

The following rules govern what becomes false (falsy values) and what turns out to be true (truthy values):

  • false, 0 (and "0"), the empty string (""), NaN, null, and undefined are represented as false
  • everything else is true
var oFalse = new Boolean(false);
var pFalse = false;
oFalse;   // [Boolean: false]
pFalse;   // false

oFalse == pFalse;   // true
oFalse === pFalse;  // false

if (pFalse) "It's true!"  // undefined
if (oFalse) "It's true (but don't believe it)!"  // It's true (but don't believe it)!
if (oFalse.valueOf()) "It false!" // undefined

typeof oFalse;             // object
oFalse instanceof Boolean; // true
typeof pFalse;             // boolean
pFalse instanceof Boolean; // false
var a = new Boolean(false)
var b = new Number(0)
var c = new String("")

var d = Boolean( a && b && c )  // true
a || b  // roughly equivalent to: `a ? a : b`
a && b  // roughly equivalent to: `a ? b : a`

The && operator “selects” the second operand if and only if the first operand tests as truthy, and this usage is sometimes called the “guard operator” -- the first expression test “guards” the second expression:

foo = () => console.log( a )
var a = 42
a && foo()  // 42

Equality (== vs ===)

  • == tries to do type coercion between two variables.
"" == "0"  // false
0 == ""    // true

"" === "0" // false
0 === ""   // false
  • Always use === and !== except for null checks
null == null       // true
null == undefined  // true
0 == null          // false
'' == null         // false
false == null      // false

null === null      // true
null === undefined // false
0 === null         // false
'' === null        // false
false === null     // false
[] == ![]   // true -- before is even processed, it’s actually already translated to `[] == false`

0 == "\n"   // true -- with empty "", "\n" (or " " or any other whitespace combination) is coerced to 0

42 == "43"       // false
"foo" == 42      // false
"true" == true   // false
42 == "42"       // true
"foo" == ["foo"] // true
  • If either side of the comparison can have true or false values, never use ==
  • If either side of the comparison can have [], "", or 0 values, consider not using ==
Structural Equality
{a:123} == {a:123}   // false
{a:123} === {a:123}  // false
import * as deepEqual from "deep-equal";
console.log(deepEqual({a:123},{a:123})); // true

The spec says for a <= b, it will actually evaluate b < a first, and then negate that result. Since b < a is also false, the result of a <= b is true:

var a = { b: 42 }
var b = { b: 43 }
a < b    // false
a == b   // false
a > b    // false
a <= b   // true
a >= b   // true

Dates

JavaScript store dates as the number of milliseconds since January 1, 1970, 00:00:00.

new Date() /* today */     // 2018-03-11T14:05:50.094Z
new Date(2000, 1, 25)      // 2000-02-24T23:00:00.000Z
new Date(2000, 1, 25, 10, 20, 30, 444) // 2000-02-25T09:20:30.444Z

Date.now()  // 1520777339877
Date.now()  // 1520777340140

Special date methods

  • get/setFullYear, get/setMonth, get/setDate, get/setHours, get/setMinutes, get/setSeconds (and sets)
  • get/setTime

Functions and Closures

There are many ways how to create a function:

  • named function declaration: function first(...) {...};

  • anonymous function expression: var second = function(...) {...};

  • named function expression: var third = function someName(...) {...};

  • function constructor: var fifth = new Function(...);

  • arrow function: var sixth = (...) => {...};

  • Immediately invoked function expression (IIFE)

// prints `hello!` immediately.
(function() {
  console.log("hello!");
})();
  • The this parameter refers to an object that's implicitly associated with the function invocation, termed as a function context.

Anonymous functions

var santa = {
  say: function() {
    console.log("ho ho ho");
  }
}
santa.say();
function eventHandler(event){
  event();
}
eventHandler(function(){
  console.log("Event fired");
});

Default parameters (ES6)

function sum(a=0, b=0){
  return (a + b);
}
console.log(sum(9,9)); // 18
console.log(sum(9));   // 9

Arrow functions (ES6)

  • Lexically captures the meaning of this.
  • Lexically captures the meaning of arguments.
console.log(( x => x * 3 )( 3 )); // 9

var f1 = () => console.log("Hello!"); 
f1(); // Hello!

var multiply = (a,b) => a * b;
console.log(multiply(2,2)); // 4

var multiply = (x,y) => {
  if(x != 0 && y != 0) {
    return x * y;
  }
}
console.log(multiply(2,2));  // 4

Inside an arrow function this is always the calling context.

Returning an object from an arrow function
let f = () => {
  return { name: "Tomas" };
}
// could be written:
let f = () => ({ name: "Tomas" })

f()  // { name: 'Tomas' }

Spread and rest (ES6)

The spread operator expands an expression in places where you would otherwise require multiple arguments, elements, or variables.

function sum3(a, b, c) { return a + b + c; }
const x = [1, 2, 3];
const y = sum3(...x);  // 6

const numbers = [2, 2, 9, 6, 0, 1, 2, 4, 5, 6];
const maxArray = arr => Math.max(...arr);
const maxA = maxArray(numbers); // 9

Generators (function*)

A generator is a special kind of function that can start and stop one or more times, and doesn't necessarily ever have to finish.

function *foo() {
     x++
     yield  // pause!
     console.log( "x:", x )
}
function bar() {
    x++
}

var x = 1

// construct an iterator `it` to control the generator
var it = foo()

// start `foo()` here!
it.next()
x  // 2
bar() 
x  // 3
it.next()  // x: 3
function* dummyGenerator() {
  yield 1;
  yield 2;
  yield 3;
}
var gen = dummyGenerator();
gen.next().value; // 1
gen.next().value; // 2
gen.next().value; // 3
gen.next().value; // undefined
function *foo(x) {
    var y = x * (yield)
    return y
}
var it = foo( 6 )
it.next()
var res = it.next( 7 )
res.value  // 42

Closures

The key concept is that when you define a function, it can refer to not only its own local variables, but also to everything outside of the context of the function:

function newCounter() {
  let count = 0;
  return function() {
    return ++count;
  };
} 
const nc = newCounter();
console.log(nc()); // 1
console.log(nc()); // 2
console.log(nc()); // 3
var v = 1
function a() {
  function b() {
    console.log(v)
  }
  return b
}
var f = a()
f() // 1
v = 2
f() // 2
Variable capturing quirks
for (var i = 0; i < 10; i++) {
  setTimeout(function() { console.log(i); }, 100 * i);
}
// 10 10 10 10 10 10 10 10 10 10
for (let i = 0; i < 10 ; i++) {
  setTimeout(function() { console.log(i); }, 100 * i);
}
// 0 1 2 3 4 5 6 7 8 9

Currying

sum3 = x => y => z => x + y + z;
sum3(1)(2)(3); // 6

Patial application

const multiply = (a,b) => a*b
const multiplyByTwo = multiply.bind(this, 2)
multiplyByTwo(5)  // 10

Modules

Modules are used to mimic classes and focus on public and private access to variables and functions.

var moduleName = (function() {
  // private state
  // private functions
  return {
    // public state
    // public variables
  }
})()

ES6 Modules

// module1.js
export function myfn1() { ... }

// module2.js
export default function myfn2() { ... }

// module3.js
export default function myfn3A() { ... }
export function myfn3B() { ... }
<!-- index.html -->
<script type="module">
  import { myfn1 } from './module1.js';
  import myfn2 from './module2.js';
  import myfn3A, { myfn3B } from './module3.js';
</script>

Asynchrony

JavaScript never shares data across threads, the function code is atomic - "run-to-completion" behaviour.

Asynchrony is implemented by a jobs queue.

console.log('A')
setTimeout(() => console.log('B'), 0)
console.log('C')

// A C B

Callbacks

  • Synchronous callback is invoked before a function returns, that is, while the API receiving the callback remains on the stack. An example might be: list.foreach(callback) when foreach() returns, you would expect that the callback had been invoked on each element.
  • Asynchronous or deferred callback is invoked after a function returns, or at least on another thread’s stack. Mechanisms for deferral include threads and main loops (other names include event loops, dispatchers, executors). Asynchronous callbacks are popular with IO-related APIs, such as socket.connect(callback) you would expect that when connect() returns, the callback may not have been called, since it’s waiting for the connection to complete.

Callbacks should be either always sync or always async, as a documented part of the API contract - "Don't release Zalgo!"

In this example, the result (0 or 1) depends on conditions (sync or async callback invocation):

function callback(data) {
    console.log(a)
}

var a = 0

ajax(url, callback)
a++

Promises

const promisify = fn => (...args) => new Promise((resolve, reject) =>
  fn(...args, (err, data) => (err ? reject(err) : resolve(data)))
);

promisify(/* do something expensive */)
  .then(data => /* success 1 */)
  .then(data => /* success 2 */)
  .catch(err => /* error */)    // catchs only error happened before
  .then(data => /* success 3 */)
;
function wait(ms) {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, ms)
  })
}
Promise.resolve('Success').then(function(value) {
  console.log(value); // "Success"
});

Promises are handled by the Job Queue in the JavaScript Runtime (browser, Node.js) which has higher priority than the Callback Queue (used for setTimeout) for the Event Loop:

setTimeout(()=>console.log(1), 0)
Promise.resolve(2).then(console.log)
console.log(3)
// 3 2 1 

async/await functions

  • The async function declaration defines an asynchronous function, which returns an AsyncFunction object.
  • The await operator is used to wait for a Promise. It can only be used inside an async function.
function resolveAfter2Seconds(x) { 
  return new Promise(resolve => {
    setTimeout(() => { resolve(x); }, 2000);
  });
}

async function f1() {
  var x = await resolveAfter2Seconds(10);
  console.log(x); // 10
}
f1();

await is not a no-op. If the awaited thing is not a promise, it is wrapped in a promise, that promise is awaited. Therefore await changes the execution order:

// prints 1,2,3
console.log(1);
(async function() {
  var x = await null; // remove await to see 1,3,2
  console.log(3);
})();
console.log(2);

this

this is the object that the function is a property of.

this represents the function’s context.

this is an implicit input.

When this is used, we need to always call the function with call() or apply() and pass in the context object.

While it may often seem that this is related to “object-oriented patterns,” in JS this is a different mechanism.

If a function has a this reference inside it, that this reference usually points to an object. But which object it points to depends on how the function was called.

  • this does not refer to the function itself.
function say(thing) {
  console.log(this + ' says ' + thing);
}

say.call("John", "hello");   // John says hello

say("hello"); // [object global] says hello - Node.js      == say.call(global, "hello");
              // [object Window] says hello - HTML browser == say.call(window, "hello");

var person = {
  say: function(thing) {
    console.log(this + ' says ' + thing);
  }
  say2: (thing) => {
    console.log(this + ' says ' + thing);
  }
};
person.say("hello");  // [object Object] says hello                == person.say.call(person, "hello");
person.say2("hello"); // [object global] says hello - Node.js      == person.say.call(global, "hello");
                      // [object Window] says hello - HTML browser == person.say.call(window, "hello");

class Person {
  say(thing) {
    console.log(this + ' says ' + thing);
  }
}
var person = new Person();
person.say("hello");  // [object Object] says hello  == person.say.call(person, "hello");

When using arrow function, the lexical scope is preserved so this is referring to the context in which translated object has defined in, not the the actual object that holds the reference to the function.

Arrays

var arr = new Array(1,2,3)
var arr = Array(1,2,3)
var arr = [1,2,3]
var a = Array( 3 )
a.length   // 3
a[0]       // undefined

var b = Array.of( 3 )
b.length   // 1
b[0]       // 3

var c = Array.of( 1, 2, 3 )
c.length   // 3
c          // [1,2,3]
// array-like object
var arrLike = { length: 3, 0: "foo", 1: "bar" }

var arr = Array.from( arrLike )    // [ 'foo', 'bar', undefined ]
var arrCopy = Array.from( arr )    // [ 'foo', 'bar', undefined ]
var myArray = new Array("1", "2", "3");
var last = myArray.pop();
// myArray = ["1", "2"], last = "3"
var myArray = new Array("1", "2");
myArray.push("3");
// myArray = ["1", "2", "3"]
var a = [ ];
a[0] = 1;
// no `a[1]` slot set here
a[2] = [ 3 ];
a[1];      // undefined
a.length;  // 3

Arrays are numerically indexed, but they also are objects that can have string keys/properties added to them (but which don’t count toward the length of the array):

var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length;     // 1
a["foobar"];  // 2
a.foobar;     // 2

However, if a string value intended as a key can be coerced to a standard base-10 number, then it is assumed that you wanted to use it as a number index rather than as a string key:

var a = [ ];
a["13"] = 42;
a.length;  // 14
var colors = ['Red', 'Blue', 'Yellow'];
console.log(colors.length); // 3
colors.length = 2;
console.log(colors); // ["Red","Blue"] - Yellow has been removed
colors.length = 0;
console.log(colors); // [] the colors array is empty
colors.length = 3;
console.log(colors); // [undefined, undefined, undefined]
colors.forEach(function(color) {
  console.log(color);
});
var myArray = new Array("33", "44", "55");
myArray = myArray.concat("3", "2", "1");
console.log(myArray);
// ["33", "44", "55", "3", "2", "1"]
var myArray = new Array('Red','Blue','Yellow');
var list = myArray.join(", ");
console.log(list);
// "Red, Blue, Yellow"
var myArray = new Array ("1", "2", "3");
myArray.reverse();
// transposes the array so that myArray = [ "3", "2", "1" ]
var myArray = new Array("A", "C", "B");
myArray.sort();
// sorts the array so that myArray = [ "A","B","c" ]
var a = ['a', 'b', 'a', 'b', 'a','c','a'];
console.log(a.indexOf('b')); // 1
// Now try again, starting from after the last match
console.log(a.indexOf('b', 2)); // 3
console.log(a.indexOf('1')); // -1, 'q' is not found
const a1 = [1,1,3];
const a2 = [...new Set(a1)];  // [1,3]

Streaming

[1, 2, 3].map(x => x * 10).reduce((sum,x) => sum + x) // 60
[1, 2, 3].map(function(x, idx, arr){ return arr[0] + x + idx + this.salt}, {salt: 100}) // 102, 104, 106

// reduceRight is starting at the end and looping until the beginning of the array
["a","b","c"].reduce((sum,x) => sum + x)       // abc
["a","b","c"].reduceRight((sum,x) => sum + x)  // cba

[1,2,3,4,5].filter(x => x > 2)  // 3,4,5
[1,2,3,4,5].find(x => x > 2)    // 3

[1,2,3,4,5].some(x => x > 2)   // true
[1,2,3,4,5].every(x => x > 2)  // false

// ES10
[1,2,[3,4,[5,6]]].flat()  // [1, 2, 3, 4, Array(2)]
[1,2,[3,4,[5,6]]].flat().flat()  // [1, 2, 3, 4, 5, 6]

// ES10
[1,2,3].flatMap(x => [x,x])  // [1, 1, 2, 2, 3, 3]
// takes functions as parameters, each function takes x as a parameter
// await for the promise `y` and apply the next function (`f`)
// the result becomes the next `y`
const asyncPipe = (...fns) => x => (
  fns.reduce(async (y, f) => f(await y), x)
)
const uploadFiles = asyncPipe(
  readUser,
  getFolderInfo,
  haveWriteAccess,
  uploadToFolder
)
uploadFiles({user, folder, files})  // a composited param `x` (merged params for all the functions)
  .then(log)

Composing Functions

// Compose functions from top to bottom:
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

// Sugar to kick off the pipeline with an initial value:
const sumT = (...fns) => pipe(...fns)(t(0));

sumT(
  t(2),
  t(4),
  t(-1)
).valueOf(); // 5

Composing Objects

Object.assign(destination, a, b)
/* or */ 
{...a, ...b}

Delegating Objects

const delegate = (a, b) => Object.assign(Object.create(a), b);

const d = objs.reduceRight(delegate, {});

Maps and Sets (ES6)

var founders = new Map();
founders.set("facebook", "mark");
founders.set("google", "larry");
founders.size; // 2
founders.get("twitter"); // undefined
founders.has("yahoo"); // false

for (var [key, value] of founders) {
  console.log(key + " founded by " + value);
}
// "facebook founded by mark"
// "google founded by larry"
var mySet = new Set();
mySet.add(1);
mySet.add("Howdy");
mySet.add("foo");
mySet.has(1); // true
mySet.delete("foo");
mySet.size; // 2

for (let item of mySet) console.log(item);
// 1
// "Howdy"

Destructuring (ES6)

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
// swap variables
[first, second] = [second, first];
var [x, y, ...remaining] = [1, 2, 3, 4];
console.log(x, y, remaining); // 1, 2, [3,4]

// destructuring with ignore
var [x, , ...remaining] = [1, 2, 3, 4];
console.log(x, remaining); // 1, [3,4]

// object destructuring
var {w, x, ...remaining} = {w: 1, x: 2, y: 3, z: 4};
console.log(w, x, remaining); // 1, 2, {y:3, z:4}

// destructuring for array items
var csvFileLine = '1997,John Doe,US,[email protected],New York';
var { 2: country, 4: state } = csvFileLine.split(',');

// destructuring for function parameters
var numbers = [1,2,3];
Math.max(...numbers);  //3
Simulating named parameters with object destructuring
function doSomething({ foo = 'Hi', bar = 'Yo!', baz = 13 }) {
  // ... 
}

// make the parameters optional
function doSomething({ foo = 'Hi', bar = 'Yo!', baz = 13 } = {}) {
  // ...
}

Control Blocks

For loops (ES6)

var list = ['Apple','Orange','Banana'];
for (let i in list){
  console.log(i); // 0 1 2
}
for (let i of list){
  console.log(i); // Apple Orange Banana
}

The in operator can be used to check if a property name exists in an object:

'b' in [a:1, b:2]  // true 
1 in [a:1, b:2]  // false 

The in operator, when used together with arrays, will check if an index exists:

'b' in ['a', 'b']  // false  
1 in ['a', 'b']  // true 

You can check for properties on built-in data types:

'length' in []  // true 
'constructor' in Object  // true 
'a' in {a:1}  // true 

Switch with ranges

switch (true) {
  case (tempInCelsius <= 0): 
    state = 'Solid';
    break;
  case (tempInCelsius > 0 && tempInCelsius < 100): 
    state = 'Liquid';
    break;
  default: 
    state = 'Gas';
}

Regular Expressions

var pattern = /test/flag;
var pattern = new RegExp("test", flag); /* alternatively */

Flags

  • i: case-insensitive.
  • g: matches all the instances of the pattern as opposed to the default of local, which matches the first occurrence only.
  • m: allows matches across multiple lines.

Methods

test
> /orange/ig.test("Orange Juice") // true
exec
/Toy/g.exec('A Toyota! Race fast, safe car! A Toyota!');
match
'A Toyota! Race fast, safe car! A Toyota!'.match(/Toy/g);

Exceptions

try {
  var a = doesnotexist; // throws a runtime exception
} catch(e) {
  console.log(e.message); // doesnotexist is not defined
} finally {
  // will be always executed
}

A return inside a finally has the special ability to override a previous return from the try or catch clause, but only if return is explicitly called:

function foo() {
    try {
        return 42
    } finally {
        // no `return ..` here, so no override
    } }
function bar() {
    try {
        return 42
    } finally {
        // override previous `return 42`
        return
    } }
function baz() {
    try {
        return 42
    } finally {
        // override previous `return 42`
        return "Hello"
    } }
foo()  // 42
bar()  // undefined
baz()  // Hello

Throwing an exception

function willThrowException(){
  throw new Error("Invalid State");
}

ES10

try {
    ...
} catch {
    console.log('Something went wrong.');
}

JSON (JavaScript Object Notation)

let json = JSON.stringify({ name:"Tomas", surname:"Tulka"}) 
json // {"name":"Tomas","surname":"Tulka"}
let obj = JSON.parse(json)

Object-Oriented JavaScript

// a direct object
var author = {
  firstname : "Douglas",
  lastname : "Crockford",
  book : {
    title:"JavaScript- The Good Parts",
    pages:"172"
  }
};
Object.keys(author);  // [ 'firstname', 'lastname', 'book' ]

Prototypes

There is one default property for almost all objects, called a prototype.

function Person() {}
Person.prototype.cry = function() { console.log("Crying"); }
var bob = new Person()
bob.cry()  // Crying
var a = {v: "my value"}
var b = Object.create(a)

console.log(b.v)  // my value

a.v = "updated"

console.log(b.v)  // updated
var anotherObject = { a: 2 }
var myObject = Object.create( anotherObject )

anotherObject.a  // 2
myObject.a       // 2

anotherObject.hasOwnProperty( "a" ) // true
myObject.hasOwnProperty( "a" )      // false

myObject.a++     // implicit shadowing!
anotherObject.a  // 2

myObject.a       // 3
myObject.hasOwnProperty( "a" )  // true

Constructors

var Person = function(name) {
  this.name = name;
};
var albert = new Person('Albert Einstein');
console.log(albert.name); // Albert Einstein

Functions themselves are not constructors. However, when you put the new keyword in front of a normal function call, that makes that function call a “constructor call.” In fact, new sort of hijacks any normal function and calls it in a fashion that constructs an object, in addition to whatever else it was going to do:

function NothingSpecial() {
    console.log( "Don't mind me!" )
}
var a = new NothingSpecial()
// "Don't mind me!"
a  // {}

Inheritance

function Child() {}
Child.prototype = new Person();
var aChild = new Child();
console.log(aChild instanceof Child); //true
console.log(aChild instanceof Person); //true
console.log(aChild instanceof Object); //true

Getters and setters (ES5)

var person = {
  firstname: "Albert",
  lastname: "Einstein",
  get fullname() {
    return this.firstname +" "+this.lastname;
  },
  set fullname(_name){
    var words = _name.toString().split(' ');
    this.firstname = words[0];
    this.lastname = words[1];
  }
};
person.fullname = "Issac Newton";
console.log(person.firstname); // "Issac"
console.log(person.lastname);  // "Newton"
console.log(person.fullname);  // "Issac Newton"

Classes (ES6)

  • Class declarations are not hoisted (you first need to declare your class and then access it) - in opposite of functions.
class Rectangle {
  // Attributes have an implicit getter/setter.
  constructor(height, width) { 
    this.height = height;
    this.width = width;
  }
  // Getter
  get area() {
    return this.calcArea();
  }
  // Method
  calcArea() {
    return this.height * this.width;
  }
  // Static method
  static calcDiagonal(h, w) {
    return Math.sqrt(Math.pow(h,2) + Math.pow(w,2))
  }
}

const square = new Rectangle(10, 10);
console.log(square.area); // 100
console.log(Rectangle.calcDiagonal(2,3)); // 3.605551275463989

Subclasses

class Human {
  speak() { console.log('Ecce homo!'); }
}
class Physicist extends Human {
  speak() { 
    super.speak();
    console.log('E=m*c^2'); }
}
var albert = new Physicist();
albert.speak();  // Ecce homo!\nE=m*c^2

Object destructuring (ES6)

let obj = {
  a: "foo",
  b: 123,
  c: { 
    d: "bar"
  }
};
let { a, b, c: {d} } = obj;
a  // 'foo'
b  // 123
c  // not defined
d  // 'bar'

// Property renaming
let { a: newName1, b: newName2 } = obj;

Object.assign (ES6)

The first argument is the target, and any other arguments passed are the sources, which will be processed in listed order. For each source, its enumerable and own (e.g., not “inherited”) keys, not symbols, are copied as if by plain = assignment and the target object is returned:

var target = {}
var o1 = { a: 1 }, o2 = { b: 2 }

Object.assign( target, o1, o2 )  // { a: 1, b: 2 }
target                           // { a: 1, b: 2 }

Proxies

A proxy is a special kind of object you create that “wraps" — or sits in front of — another normal object.

Example: pre-define all the properties/methods for an object, and have an error thrown if a non-existent property name is subsequently used:

var obj = { a: 1, foo () { console.log("a:", this.a) } }

var handlers = {
    get(target,key,context) {
        if (Reflect.has( target, key )) {
            return Reflect.get(target, key, context)
        } else {
            throw "No such property/method!";
        }
    } }
    
var pobj = new Proxy( obj, handlers )

pobj.a      // 3
pobj.foo()  // a: 3

pobj.b      // Error: No such property/method!
pobj.bar()  // Error: No such property/method!

Console

  • console.trace()
  • console.time() && console.timeEnd()
  • console.memory
  • console.count('STUFF I COUNT')
  • console.assert(false, 'Log me!')
  • console.clear()
  • console.table({name:"Tomas", age:33}, {name:"John", age:26},)

String Substitutions

%s = string, %i = integer, %o = object, %f = float

console.log("Hello, %s. Your numer is %i.", "Tomas", 123)

Best Practises

Avoid long argument list

// DON'T
function getRegisteredUsers (fields, include, fromDate, toDate) { /* implementation */ }
getRegisteredUsers(['firstName', 'lastName', 'email'], ['invitedUsers'], '2016-09-26', '2016-12-13')

// DO
function getRegisteredUsers ({ fields, include, fromDate, toDate }) { /* implementation */ }
getRegisteredUsers({
  fields: ['firstName', 'lastName', 'email'],
  include: ['invitedUsers'],
  fromDate: '2016-09-26',
  toDate: '2016-12-13'
})

Prefer Promises to Callbacks

// AVOID
asyncFunc1((err, result1) => {
  asyncFunc2(result1, (err, result2) => {
    asyncFunc3(result2, (err, result3) => {
      console.lor(result3)
    })
  })
})

// PREFER
asyncFuncPromise1()
  .then(asyncFuncPromise2)
  .then(asyncFuncPromise3)
  .then((result) => console.log(result))
  .catch((err) => console.error(err))

Libraries & Frameworks

References

⚠️ **GitHub.com Fallback** ⚠️