JavaScript Crash Course

Preface

const a = 10; // undefined - variable assignment returns undefined
2 + 2;        // 4 - expression returns its value

Resources

Language Basics

Types

typeof 42; // "number"
typeof null; // "object"
typeof {};            // "object" - object
typeof [];            // "object" - array
typeof function() {}; // "function" - function

Coercion

10 + '2'; // "102" - everything can be coerced to string
10 - '2'; // 8 - subtraction isn't defined for strings so they are coerced to numbers
10 - 'a'; // NaN - 'a' can't be coerced to a number
10 == '10';  // true
10 === '10'; // false
const negative = !value;   // coerces to boolean and negates the result
const boolean = !!value;   // coerces to boolean (double negation)
const number = +value;     // coerces to number
const string = '' + value; // coerces to string
0 || 'default'; // "default"
42 && 'value';  // "value"

Objects

const object = {
    key1: 'value1',   // basic key
    ['key 2']: 42,    // useful for keys with invalid characters (e.g., ' ' or '-')
    foo() {           // functions can be values too
        return 'bar';
    }
};
object.key1;     // "value1"
object['key 2']; // 42
object.foo();    // "bar"
object.key3; // undefined
object.key3 = true;
object.key3; // true
delete object.key1;
object.key1; // undefined
'key1' in object; // false
'key3' in object; // true
'hello'.length;  // 5
true.toString(); // "true"
(42).toString(); // "42" - brackets are needed to avoid ambiguity with decimal points
1.23.toFixed(1); // "1.2"

Functions

function add(a, b) {
    return a + b;
}
const subtract = function(a, b) {
    return a - b;
};
const multiply = (a, b) => {
    return a * b;
}
const divide = (a, b) => a / b; // implicit return
function foo() {
    return 'bar';
}
foo.property = 'foo';
foo.property; // "foo"
function createUser({ name, age, isAdmin = false }) {
    // ...
}
const user = createUser({ name: 'Jo', age: 42 }); // isAdmin will be false by default

Arrays

const array = [ 1, 'two', { value: 'three' }, [ 4 ] ];
array[0]; // 1
array[4]; // undefined
array.push(5); // adds 5 to the end
array.pop();   // removes and returns the last element
const numbers = [ 1, 2, 3, 4, 5 ];
const odd = numbers.filter(n => n % 2 !== 0);      // [ 1, 3, 5 ]
const doubled = odd.map(n => n * 2);               // [ 2, 6, 10 ]
const sorted = doubled.toSorted((a, b) => b - a);  // [ 10, 6, 2 ]
sorted.forEach(n => console.log(n));               // prints 10, 6, 2
const sum = sorted.reduce((n, ans) => n + ans, 0); // 18
const array = [ 'a', 'b', 'c' ];
for (const value of array)
    console.log(value);                 // "a", "b", "c"

const object = { x: 1, y: 2, z: 3 };
const entries = Object.entries(object); // [ [ "x", 1 ], [ "y", 2 ], [ "z", 3 ] ]
for (const [ key, value ] of entries)   // array destructuring, see below
    console.log(key, value);            // "x" 1, "y" 2, "z" 3
for (const key in object)
    console.log(key, object[key]);      // "x" 1, "y" 2, "z" 3

Destructuring and Spread Operator

const object = { a: 1, b: 2, c: 3, d: 4 };
const { a, b: x, ...rest } = object; // a = 1, x = 2, rest = { c: 3, d: 4 }
const array = [ 1, 2, 3, 4 ];
const [ x, y, ...rest ] = array; // x = 1, y = 2, rest = [ 3, 4 ]
const array1 = [ 2, 3 ];
const array2 = [ 5, 6 ]
const joined = [ 1, ...array1, 4, ...array2, 7 ]; // [ 1, 2, 3, 4, 5, 6, 7 ]
function sum(...numbers) {
    return numbers.reduce((a, ans) => a + ans, 0);
}
const array = [ 1, 2, 3 ];
sum(0, ...array, 4); // 10

JS in Browser

Window

DOM

<section>
    <h2>Heading</h2>
    <p>Some text.</p>
</section>

DOM Traversal

element.getElementsByClassName(className) // live `HTMLCollection`
element.getElementsByTagName(tagName)     // live `HTMLCollection`
element.querySelectorAll(selector)        // static `NodeList`
element.querySelector(selector)           // first matching `Element` (or `null`)

DOM Manipulation

const user = { name: 'Joe', year: 1984, email: 'joe@ma.ma' };

const table = document.getElementById('users-table');
// Get the `<tbody>`. Another approach would be to use `table.tBodies[0]` but that only works for tables.
const tableBody = table.getElementsByTagName('tbody')[0];
const newRow = document.createElement('tr');

for (const key of [ 'name', 'year', 'email' ]) {
    // It should be fine to dirrectly append to `newRow` here because the row itself is not yet attached to the DOM.
    const cell = document.createElement('td')
    newRow.append(cell);

    if (key === 'email') {
        const link = document.createElement('a')
        cell.append(link);
        link.href = `mailto:${user.email}`;
        link.textContent = user.email;
    } else {
        cell.textContent = user[key];
    }
}

// Let's insert the new row at the start of the table so that it's immediately visible.
table.prepend(newRow);

Element Properties

// Explicit methods with actual property names.
element.style.getPropertyValue('font-size'); // "16px"
element.style.setPropertyValue('font-size', '20px');
// Basically the same but shorter. Notice the camelCase naming (instead of kebab-case).
element.style.fontSize; // "20px"
element.style.fontSize = '16px';
<button class="toggle-button" data-is-open="false">Show</button>
const button = ...;                               // get the button element from the event
const wasOpen = button.dataset.isOpen === 'true'; // convert to boolean
const isOpen = !wasOpen;                          // toggle 
button.dataset.isOpen = String(isOpen);           // convert to string
button.textContent = isOpen ? 'Hide' : 'Show';

Event Loop

const intervalId = setInterval(() => {
    console.log('This runs every 2 seconds');
}, 2000);
setTimeout(() => {
    console.log('This runs after 7 seconds');
    clearInterval(intervalId); // stop the interval
}, 7000);
// Output:
// "This runs every 2 seconds"
// "This runs every 2 seconds"
// "This runs every 2 seconds"
// "This runs after 7 seconds"

Events

const input = document.getElementById('name-input');
// Register an event listener for the 'input' event (fired whenever the content changes).
input.addEventListener('input', event => {
    console.log('Input content: ', event.target.value);
});
function listener(event) {
    console.log('Input content: ', event.target.value);
};
input.addEventListener('input', listener);
// ... some time later ...
input.removeEventListener('input', listener);
const controller = new AbortController();
input.addEventListener('input', event => {
    console.log('Input content: ', event.target.value);
}, { signal: controller.signal });
// ... some time later ...
controller.abort(); // remove the event listener
[ ...document.getElementsByTagName('a') ].forEach(link => {
    link.addEventListener('click', event => {
        event.preventDefault(); // prevent navigation
    });
});
const outer = document.getElementById('some-id');
const inner = outer.firstElementChild;
outer.addEventListener('click', () => {
    console.log('Outer clicked! Do sth ...');
});
inner.addEventListener('click', event => {
    event.stopPropagation(); // prevent the outer listener from being called
    console.log('Inner clicked! Do sth else ...');
});