Introduction

Welcome to my personal JavaScript toolbox!

This was created as a school project for the modul WebPr. Here, everything that happened is documented and (hopefully) sufficiently explain so that it can be understand and replicated. Keep in mind that this is written from the perspective of somebody who is already familiar with programming, so many basic concepts won't be explained anymore.

Start here with chapter one!

Strings

A small excursion into strings and how to declare them in JavaScript.

You can declare them the same way as you would declare in Java.

"This is a string"

You can also declare them like you would a single character in Java.

'This is also a String'

More interesting is the declaration with backticks(`). With this you can declare a multiline strings and even use string interpolation! This allows us to build nice little templates, which is why we also call these strings "template strings".

`
This
is
a
multiline
string :)
`

let x = 1;
`
This is {x} template
`

A more obscure way to declare strings is to use forward slashes(/), like so:

/test/
//put this into a constructor
String(/eins/)

Huh? Why does this work? What does this do?

The answer is simple, forward slashes denote a regular expression in JavaScript. Because a regular expression is usually just a string that gets parsed, putting this into a string constructor also yields a string.

Arrays

A small excurse into more JavaScript arrays functions.

push()

Adds one or more elements to the end of an array and returns the new length of the array.

let fruits = ["apple", "banana"];
fruits.push("orange"); // returns 3
console.log(fruits);  // output: ["apple", "banana", "orange"]

pop()

Removes the last element from an array and returns that element.

let fruits = ["apple", "banana", "orange"];
let lastFruit = fruits.pop();
console.log(lastFruit);  // output: "orange"
console.log(fruits);     // output: ["apple", "banana"]

shift()

Removes the first element from an array and returns that element.

let fruits = ["apple", "banana", "orange"];
let firstFruit = fruits.shift();
console.log(firstFruit);  // output: "apple"
console.log(fruits);      // output: ["banana", "orange"]

unshift()

Adds one or more elements to the beginning of an array and returns the new length of the array.

let fruits = ["banana", "orange"];
fruits.unshift("apple");
console.log(fruits);  // output: ["apple", "banana", "orange"]

slice()

Is used to slice (hence the name) a part out of the array.

let fruits = ["banana", "orange", "grape"];
const sliced = fruits.slice(1,3); // 1 is inclusive, 3 not
console.log(sliced);  // output: [ "orange", "grape" ]

const sliced2 = fruits.slice(1); // until the end of the array
console.log(sliced2);  // output: [ "orange", "grape" ]

const sliced3 = fruits.slice(0, -1); // until -1 the length of the array
console.log(sliced3);  // output: [ "banana", "orange" ]

splice()

Changes the contents of an array by removing, replacing, or adding elements at specific positions.

// removing elements
let fruits = ["apple", "banana", "orange", "grape"];
fruits.splice(1, 2);  // Removes 2 elements starting from index 1
console.log(fruits);  // Output: ["apple", "grape"]

// adding elements
let fruits = ["apple", "banana", "grape"];
fruits.splice(2, 0, "orange", "kiwi");  // Adds "orange" and "kiwi" at index 2
console.log(fruits);  // Output: ["apple", "banana", "orange", "kiwi", "grape"]

//replacing elements
let fruits = ["apple", "banana", "orange"];
fruits.splice(1, 1, "kiwi");  // Replaces 1 element at index 1 with "kiwi"
console.log(fruits);  // Output: ["apple", "kiwi", "orange"]

Create arrays

With the from method, you can create an array from every object that has a length property

Array.from("abc") //output ["a", "b", "c" ]
Array.from({length: 2}) //output [undfined, undefined]

This can also be used to create very large arrays in an easy way.

Array.from({length: 1000}, (it, idx) => idx) //output [0, 1, ..., 999 ]

Functions in JavaScript

JavaScript function initially look a lot like Java functions, just without the type system.

function myCoolFunction(a, b){
    return a + b;
};

myCoolFunction(1, 2) // evaluates to 3

But they can also be written in other ways, because they are essentially just references to a "function" object.

// Same as above
const myCoolFunction => (a,b) => { return a + b; };

// With no arguments, always returns 1
const noArgs => () => { return 1; };

Those ways of writing functions also enables that we can write functions in JS, that return something without using the return statement.

// This always return 1
const return = () => 1;

// BUT THIS RETURN UNDEFINED! DONT DO THIS!
function noReturn()     { 1; }
const noReturn2 = () => { 1; };

As mention previously, functions are essentially just an object. This means we can also define functions as variables or even put them into an array.

function fun1(){ return 1; };
const myfun = fun1;
const funs = [null, undefined, fun1];
        
// The function can then be called like this
funs[2]()

That also allows us to pass functions as arguments to other functions. Kinda like Lambda expressions in Java.

function doit(whatToDo) {
    return function bla(arg) { 
        return whatToDo(arg); 
    }
}

doit(fun1)();

// Same thing but written differently
const doit2 = callme => arg => callme(arg) ;
doit2(fun1)()

Scope

Strictly speaking JavaScript only has two scopes:

  • global the entire window (if you are in a Browser)
  • function variables are alwayes local to the enclosing functions

Despite just having two scopes, JavaScript gives us four different ways to declare variables.

Number 1:

x = 12;

This creates a mutable global variable. This is not good for obvious reasons. DONT DO THIS!

Number 2:

var x = 12;

This creates a mutable hoisted variable. What does "hoisted mean? It means that the variable gets initialized at the beginning of the scope, even if we declare it further down. Which causes things like this:

x = 0;
function foo(){
    document.writeln(x); //this prints undefined, not 0
    var x = 12;
}

This is also not good for also obvious reasons. DONT DO THIS!

Number 3:

let x = 12;

This creates a mutable variable inside of the local scope. This is good, use this.

Number 4:

const x = 12;

This creates an immutable variable inside of the local scope. Immutable meaning not reasignable, not really immutable. This is good, use this.

Objects

The idea of objects is to be data structures with their associated methods and state. The same as in (almost) every other programming language.

Ways to create Objects

There are many different ways to create objects in JavaScript. Each with their advantages and disadvantages.

Open, Dynamic

Very simpel way to create objects. This way is very dynamic and easy to manipulate. This is also its main disadvantage, along with the inability to enforce and share its structure.

Using this also has major problems, which you can read more about here.

const good = { 
    firstname : "Good", 
    lastname  : "Boy", 
    getName   : function() {  
         return this.firstname + " "  + this.lastname  
    };
}; 

Closed, Explicit

This solves our problems from before. It is basically a constructor, which means that the structure is always the same. It is also not possible to change the first or lastname property afterwards. This may be good or bad, depending on what you want to do with it.

function Person(first, last) { 
    return { 
        getName: () => first + " "  + last;                  
    } 
}

Mixed, classified

Here, both of the examples from before are mixed together.

This is essentially a very elaborate constructor from our first version. Each function has a property prototype, which defines the name of it. With the "newish" new keyword, a new empty object is silently passed to this function. Which then means we can use this more like we are accustomed to from Java.

This is the "default" (if there is such a thing in JavaScript) way to create new objects.

const Person = ( () => { // lexical scope     
    function Person(first, last) { // ctor, binding 
        this.firstname = first; 
        this.lastname  = last; 
    } 
    Person.prototype.getName = function() { 
           return this.firstname + " " + this.lastname; 
    }; 
    return Person; 
}) (); // IIFE 
// new Person("Good", "Boy") instanceof Person 

Prototyp

The prototype is a sort of "type" of object. It is itself also an objects. Using prototypes, it is possible to use things like instanceof.

Classes

JavaScript has two keyword, which are relevant for this chapter: class and extends.

class Keyword

The class keyword is syntactic sugar for the mixed way to create objects. There are a couple of minor difference, which will no be covered here, but it looks very similar.

A new class can be created like this:

class Person { 
    constructor(first, last) {
        this.firstname = first;
        this.lastname = last;
    }
    getName() {
        return this.firstname + " " + this.lastname;
    }
}
// new Person("Good", "Boy") instanceof Person

extends Keyword

JavaScript does NOT have real inheritance1.

You can use the extends keyword like you would in Java. But there will be no warning if you forget a call to super.

class constructor Student extends Person { 
    constructor (first, last, grade) {
        super(first, last); // Do not forget!
        this.grade = grade;
    }
    getGrade(){
        return this.grade;
    }
}
const s = new Student("Top","Student", 5.5);

But what does actually happen if this is not real inheritance? Instead prototype chaining is used.

What is Prototype Chaining

Each JavaScript object has a __proto__ object, which can link it to another object. In our example above, the Student has a __proto__ object which links to the Person prototype.

If you then call a method on a student object, it will first search if Student itself has a method with that name. If not it will look at the prototype, inside of the __proto__ property. This will then go up the chain (see where the name comes from?) until the __proto__ property is null. Then we know that no such method exists.

const s = new Student("Top","Student", 5.5);
s.getName(); // calls the method in Person
s.getGrade(); // calls the method in Student

Problems with Prototyp Chaining

Because everybody has access to the prototype via __proto__ everybody can change at any time this prototype. This will propagated to all other instances.

It is also possible to replace the prototype at runtime.

Object.setPrototypeOf(obj, proto);

Using Delegation Instead

Instead of using class and extends, we can also use delegation.

 function Person(worker) {
    const worklog = [];
    return {
      worklog: worklog,
      work: () => worklog.push(worker.work()),
    };
  }
  const manager = Person({ work: () => "" });

  function Student(name) {
    return {
      name: name,
      work: () => name + " filled quiz",
    };
  }
  const p = Person(Student("top"));
1

Depending on what you consider real inheritance of course.

Type System

Because JavaScript has no compiler, types cannot be enforced by it. It can, however, be enforced by your IDE.

Built in Types

JavaScript has a whole list of primitive builtin types:

  • Boolean
  • Number
  • String
  • Object
  • Null
  • Undefined
  • Function
  • ...

It has also a couple of classes, that can be used the same way in JsDocs:

  • Array
  • Date
  • Error
  • Map
  • ...

JsDoc

JsDoc looks very similiar to JavaDoc. It has, on account of the missing compiler, a couple more features though. It can not only document but also define the input/output types or define entire "custom types".

This is a very basic definition:

/**
 * This is a basic description of a function.
 * @param { String } name - The name of the person.
 * @param { Number } age - The age of the person.
 * @returns { String } A greeting message.
 */
function greet(name) {
  return `Hello, ${name}! You are going to be ${age + 1} next year.`;
}

Common Tags

There are a lot of tags in JsDoc. Here are the most frequently used listed, a couple of them have more infos further below.

  • @function - Explicitly marks a block as a function, often for function expressions or anonymous functions.
  • @param - Describes a function parameter.
  • @template - Allows generic types, similar to TypeScript generics.
  • @returns - Describes what the function returns.
  • @type - Declares the type of a variable.
  • @typedef - Creates a custom type, like an object structure.
  • @property - Defines properties in a custom type.
  • @constructor - Indicates that a function is intended to be used as a constructor (for creating instances of an object).
  • @callback - Defines a function signature expected to be used as a callback type, specifying its parameters and return type.

Define Data Types

Union type: This is useful if the parameter can have multiple types.

/**
 * Get a string to describe the age of a person
 * @param { Number | String } age - The age of the person.
 * @returns { String } A greeting message.
 */
function age(age) {
  return `You are ${age} year(s) old!`;
}

Intersection type: Here the type must be a person who is also cool. This helps enact even more specific type checking.

/**
 * Log how cool a person is
 * @param { Person & Cool } person - A cool person.
 */
function cool(person) {
  console.log(`Hey, ${person.name} is really cool!`)
}

String literal type: If you want to enable a couple of different strings. Very much like enums in Java.

/**
 * Get a string to describe the greating of me or you
 * @param { "me" | "you" }  - arg Me or you
 * @returns { String } A greeting message.
 */
function age(arg) {
  return `Hello, you are ${arg}!`;
}

Capability: This can be used to describe what kind of capabilities an object should have, i.e. it should have a properties age that is of type number.

/**
 * Processes an object with properties `a` and `b`.
 * @param {{a: string, b: Function}} obj - The object to process.
 * @returns {void} 
 */
function processObject(obj) {
  console.log(`Value of a: ${obj.a}`); // We know a must be a string
  obj.b(); // Invoke the function
}

Define Interfaces

With the typedef tag, you can define your own data structures. These can then be used like types, i.e. @returns {Person}.

/**
 * @typedef {Object} Person
 * @property {string} name - The name of the person.
 * @property {number} age - The age of the person.
 * @property {Function} greet - A method that returns a greeting message.
 */

Generics

Using @template, you can use generics.

/**
 * @template T
 * @param {T[]} array - An array of elements of type T.
 * @returns {T} The first element of the array.
 */
function getFirstElement(array) {
  return array[0];
}

Functions: With this you can describe functions that use currying, in different degrees of precision.

The most simple way to describe something as a function, that takes A and B which produces C.

/**
 * A curried function that takes a string and returns another function.
 * The returned function then takes a number and returns a boolean.
 * @type { (string) => (number) => boolean }
 */
const isLongerThan = (str) => (length) => str.length > length;

To be a bit more precise, you can also name the parameters:

/**
 * A curried function that takes a number `a` and returns another function.
 * The returned function takes a number `b` and returns their sum as a string.
 * @type { (a:number) => (b:number) => string }
 */
const addAndStringify = (a) => (b) => String(a + b);

It is also possible to throw generics into this mix:

/**
 * A curried function that takes a value of any type `T` and returns another function.
 * The returned function takes a string and simply returns the original `T` value.
 * @type { <T> (a:T) => (message:string) => T }
 */
const echoAfterMessage = (a) => (message) => {
  console.log(message);
  return a;
};

Best Practices

If you are using types, or JsDoc in general, there are a couple of best practices you may want to follow:

  • Set up your IDE properly: it will make your life so much easier
  • Follow conventions: pick or define a convention and stick to it
  • Add examples: makes it so much easier to figure out how a function call should look
  • Link to documentation: use @link to link to relevant documentation
  • Validate the documentation: validate what you've documented with test cases

Immediatly Invoked Function Expression

An immediatly invoked function expression, short IIFE, are functions without names, that are executed immediatly.

JavaScript function initially look a lot like Java functions, just without the type system.

You can achieve this by writting an empty lambda function and define this as a function:

(() => {
    //do stuff
})()

Ok thats cool but how did we get here? If we do it step by step it gets a lot easier to understand:

function foo(){} foo();
(function foot(){})(); //evluate directly, loose the function call
(function(){})() //loose the function name
(() => {})() //loose the keyword

Using IIFE can have a couple advantages:

  • Encapsulates variables and their values which prevents pollution of (global) namespace
  • Mimize risks of name collisions

Lambda Calculus

Lambda calculus describes can describe all the operations one can do on a computer. This can help us to understand (JavaScript) code.

Basics

Lambda calculus has three operations:

  • α => Rename parameter
  • β => Apply argument
  • η => Cancel parameter

Technically we only need α and β, from those two we could then infer η. Too keep it simple, we will not do that and, instead, look at η as a single operation.

Alpha Translation (α)

This operation allows us to rename things. This is really simple, like so:

const id = x => x

Beta Reduction (β)

All this operation does is search x on the left and replace it with x on the right.

Practically this may look like this:

(x => x)(1) //x is our foo(x) and (1) is the method call foo(1)
(1 => x)(1) //if we replace our x with the input it looks like this
(1 => 1)(1) //which means that the output x is also 1...
1 // and which means everything cancels each other out

Eta Reduction (η)

If we have an expression where on the right-most left is a parameter and on the right-most right is exactly an input, which is equivalent to the parameter, we can remove both of those.

Sounds complicated, is really simple:

x => foo (x) //right-most parameter and input is both x
foo // so this is foo

If we have more than one parameter or input, we always have to remember to focus on the right-most:

x => y => both(x)(y)
x => both(x) //remove the y
both //remove the x

Lambda II

This is a continuation of the "Lambda Calculus" chapter. So if you haven't read that one yet, do so here.

Atomic Lambdas

With the things we learned, we can defined atomic lambda terms, which are the most basic terms. You should have seen these already:

const id = x => x;
const konst => x => y => x;

These two allow us to derive pretty much everything needed from them. Basic things such as true, false, or and so on. See a couple of those here:

const T = konst;
const F = snd;

const and = a => b => a (b) (a);
const or = a => b => a (a) (b);
//.. and more

Pair, Product Type

The preparation of those "basic" things, allow us to do more complex things. For example define a pair (or product) type.

const Pair = x => y => f(x)(y);
const fst = p => p(T);
const snd = p => p(F);

This basically defined a function which "holds" two values. The fst and snd functions allow us to retrieve the first and second value respectively.

If we expand this concept a bit it allows us to do crazy things. See the code below. It is very Haskell like but just in JavaScript.

//This defines a person "object" that is a pair of a names and an age. The names are also a pair themselves.
const person = 
    firstname =>
    lastname =>
    age => 
    pair(pair(firstname)(lastname)(age));

//These functions can read the values
const firstn = p => fst(fst(p));
const lastn = p => snd(fst(p));
const firstn = p => snd(p);

Immutability and Lazy

Using this style of programming has two advantages: - the "objects" are immutable, means you cant change them - they are lazily evaluated, means they compute on demand not before

Either Function

Armed with that knowledge, we can create an either function. This function should execute either one lambda or another, depending on the result of a first function.

const Left = x => f => g => f(x); //take the argument and execute function g
const Right = x => f => g => g(x); //take the argument and execute function g
const either = e => f => g => e(f)(g); //if e evaluates to true, execute f else execute g



//we then invoke it like that
const someFunc = true ? Left("This would be called if it were evaluated as false") : Right("This is actually called");
either (someFunc)(x => console.log(x => "Never called anyway"))(x => "This returns: " + x);


Higher Order Functions

Higher order functions are functions which receive other functions as arguments. Most commonly they are used on collection data structures, such as lists and arrays. For the sake of simplicity we will use arrays here.

Map

A very basic higher order function is map() where a function is applied to each value inside of an array. The function then returns a new array.

[1,2,3].map(x => x *2);
// this results in a new array with the values [2,4,6]

Instead of writing the lambda each and every time, we can also define it beforehand. If we use currying we can even decide how much we want to abstract.

const multiply = a => b => a * b;
const multiplyBy2 = multiply(2)

[1,2,3].map(x => x *2);
[1,2,3].map(x => multiplyBy2(x));
[1,2,3].map(multiplyBy2);

Which way is the best is mostly personal preference.

Filter

The filter() function creates a new array with all elements that pass the test defined by the provided function. It only returns the elements that evaluate to true.

const odd = x => x %2 == 1;

[1,2,3].filter(x => odd(x));// new array with the value(s) [2]
[1,2,3].filter(odd);// same thing as above but written differently

Reduce

The reduce() function executes a provided reducer function on each element of the array, resulting in a single output value. It's used for aggregating data like summing up an array.

const sum = (accu, cur) => accu + cur;

[1,2,3].reduce(sum);// output: 6 (which is 1 + 2 + 3)

Apply Multiple Functions

These functions can of course be chained together.

const numbers = [1, 2, 3, 4, 5];

const result = numbers
  .filter(num => num % 2 === 0)   // results in [2, 4]
  .map(num => num * 2)            // results in [4, 8]
  .reduce((acc, curr) => acc + curr, 0); // then finally returns 12

Logging

JavaScript logging is similar to logging in basically every other language.

Basic Log Levels

JavaScript offers several built-in logging methods, each with a specific purpose. The most commonly used are:

Log

Used for general output of information to the console. It's often used for basic debugging or status messages.

console.log('Just general output');

Info

Used to display informational messages that are not errors but important to note.

console.info('Some basic information');

Warn

Used to indicate potential problems or warnings that don't stop the execution of the program but might lead to issues.

console.warn('Hey this could be important');

Error

Used to log error messages when something goes wrong, especially when an exception occurs.

console.error('SOMETHING HAS GONE WRONG');

Debug

Used for detailed debugging information. In some browsers, these messages may be hidden by default unless you enable verbose logging.

console.debug('Psst, I am a sneaky message');

Logging Objects

It is also possible to pass entire objects or arrays of objects to the logging functions. These then get logged out nicely in JSON.

const user = {
  name: 'Alice',
  age: 30,
  occupation: 'Engineer'
};

console.log('User object:', user);

This will result in something like this (may look different depending on your browser):

User object: Object { name: "Alice", age: 30, occupation: "Engineer" }

Of course the same also works for arrays of objects.

const users = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 },
  { name: 'Charlie', age: 35 }
];

console.log('Array of users:', users);

Which then results in:

Array of users: Array(3) [ ​0: Object { name: "Alice", age: 30 }, 1: Object { name: "Bob", age: 25 }, 2: Object { name: "Charlie", age: 35 } ]

Fun fact: you can also display an array as a table with the console.table() function!

Scripting

A scripting language, like JavaScript, is characterised mainly by the ability to execute arbitrary texts from sources like files, URLs, the user and so on. Other characteristics are that they are interpreted and thus have a lenient type system.

Normally this is a big no-no. A very very big no-no. But this is essentially what every browser does: load file from example.com and execute the code that is saved there.

Use Cases

Scripting is used at an endless amount of places:

  • Command line
  • Automation (DevOps)
  • Build System
  • Templating
  • Business Rules
  • Formulae
  • DSL (Data Structure Language)

Examples

Because that was a lot of theory, here a couple of examples.

"Auto" Imports

If we want to import JavaScript files with the <script> tag into our HTML, we normally have to include each module in a seperate tag. This gets cumbersome fast. Instead we can dynamically create <script> tags, whiche then import the needed files.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>All Tests</title>
</head>
<body>
    <script>
        ["function", "lambda"].forEach(n => 
            {
                document.writeln('<script src="' + n + '/' + n + '.js"><' + '/script>')
                document.writeln('<script src="' + n + '/' + n + 'Test.js"><' + '/script>')
            }
        );
    </script>

</body>
</html>

Plotter

If we want to take a (math) function that a user gives us and evaluate it, we can do that a couple of different ways. The easiest way would be to call the eval function, which then evaluates the supplied string.

function start() {
  const userFunction = document.getElementById("user_function");
  const canvas = document.getElementById("canvas");

  display(canvas, (x) => eval(userFunction.value));
  userFunction.onchange = (_) =>
    display(canvas, (x) => eval(userFunction.value));
}
// lot of code missing for brevities sake

One problem of this approche is that it is very very innefficient. Instead we can use the functional programming things we learned in the weeks before and rewrite it:

function start() {
    const userFunction = document.getElementById('user_function');
    const canvas       = document.getElementById('canvas');

    const f = _ => Function( "x", "return " + userFunction.value);
    display(canvas, f() );
    userFunction.onchange = _ => display(canvas, f() );
}
// lot of code missing for brevities sake

Check out the offical WEBPR repo for the entire code and even more cool examples!