Skip to content

Instantly share code, notes, and snippets.

@martialboniou
Last active March 16, 2021 13:00
Show Gist options
  • Save martialboniou/45735475811030b885528ceab932d4c3 to your computer and use it in GitHub Desktop.
Save martialboniou/45735475811030b885528ceab932d4c3 to your computer and use it in GitHub Desktop.
JavaScript DIGEST

Here's some random memos about JavaScript.

Important notes

  • NaN is a number (?); operators on NaN yields into NaN except in the + case with a string (as below);

  • IMPORTANT: if we write (x + y) with x as a number and y as a string, it yields into a string (because + is mainly a string concatenation operator); but if + is another math operator (eg: *), it yields into a number; another important case of +: 1 + true yields into 2 because true(otherwise false or null) is converted into 1 (0 respectively); 1 + undefined yield into NaN; "1" + null yields into "1null";

  • beware of precision issues with floating-point numbers (because JS uses binary form to represent decimal numbers internally); try to remove the floating point (multiply by 10eX where X is a positive integer) then calculate, finally then divide by the same multiplier;

  • remember the ternary operator: (condition) ? yes : no;

  • IMPORTANT (of for arrays, strings, in for objects): for(let fruit of fruits) should be used to iterate over an array where the index order is important; the for(let i in fruits) /* use fruits[i] */is optimized for iterating over object's properties;

ES6 specifics

Template literals

Backquotes define template literals; you can place variables or expressions inside the string by using ${...}:

        let a = 10;
        let b = 20;
        let res = `The sum of ${a} and ${b} is ${a+b}.`;

Arrow functions

arrow functions don't have its own this:

        function Person(nickname, country) {
          this.nickname = nickname;
          this.country = country;

          this.getInfo = function() {
            return () => {
              alert(this.constructor.name); // Person (Window if you use a function)
              alert(`Hi, I'm ${this.nickname} from ${this.country}`); // works as expected (undefined if you use a function)
            };
          };
        }

Class

class example:

        class Rectangle {
          constructor(length, width) {
            this.length = length;
            this.width = width;
          }
          getArea() {
            return this.length * this.width;
          }
        }
        class Square extends Rectangle {
          constructor(length) {
            super(length, length);
          }
          getPerimeter() {
            return 2 * (this.length + this.width);
          }
        }

Modules

An example:

  • in main.js, you declare some variables and functions to export:
          let greet = "Hello!";
          function multiplyNumbers(a, b) {
            return a * b;
          }
          export { greet, multiplyNumbers };
  • in app.js, you import some variables and functions from a file:
          import { greet, multiplyNumbers } from './main.js';
          alert(greet);
          alert(multiplyNumbers(3,5));
  • in an HTML file using HTTP protocol:
          <!DOCTYPE html>
          <html lang="en">
          <head><meta charset="utf-8"><title>ES6 Module Demo</title></head>
          <body><script type="module" src="{...}

Rest & Spread parameters

A rest parameter can only be the last one (like a Common Lisp &rest); let's see some examples:

          // rest makes an array from the rest (or all parameters here)
          function sortNames(...names) {
            return names.sort();
          }

          // spread makes a list of arguments from an array
          function addNumbers(a, b, c) {
            return a + b + c;
          }
          let numbers = [5, 12, 8];
          alert(addNumbers(...numbers));

Array/Object destructuring assignment

          let [a, b] = ["Apple", "Banana"];
          let [a, ...arr] = ["Apple", "Banana", "Mango"]; //arr is assigned from the rest (with the rest operator)
          let {name, age} = person; // from the person.name & person.age properties

Random notes

  • push() & pop() runs faster than unshift() & shift();
  • unlike slice() & concat(), splice() modifies the array on which it is called on;
  • sort() & reverse() modify the original array and return a reference to the same array; BEWARE: sort() sorts as strings so numeric array elements are converted; to avoid to mess up:
numbers.sort(function(a,b) {
        return a - b;
      });
- `apply() is a good way to convert an array to a list of arguments to pass to a function:

    const numbers = [5, 6, 2, 3, 7];
    const max = Math.max.apply(null, numbers);
    const min = Math.min(...numbers); // ES6 spread operator can do this too (read above)

it's the same as having written this: Math.max(5, 6, 2, 3, 7);

  • sorting example of an array of objects:
    var persons = [
      { name: "Harry", age: 14 },
      { name: "Ethan", age: 30 }
    ];
    persons.sort(function(a,b) {
        var x = a.name.toLowerCase();
        var y = b.name.toLowerCase();
        if (x < y) {
          return -1;
        }
        if (x > y) {
          return 1;
        }
        return 0;
    });
  • let scope:
    var funcs = [];
    for (let i = 0; i < 3; i++) {
      funcs[i] = function(){
        console.log("My value: " + i);
      };
    }

funcs[1]() yields into a My value: 1 output. IMPORTANT: if you declare i with var, the global scope of i (in the state 3 in the last i++ call) makes funcs[1]() to return My value: 3 because of the state of i before the invokation since the anonymous functions were bound to the same variable;

  • NOTE: you can use name = 'Guest' as a default parameter in ES6; previously you'd need such a construct: var name = name || 'Guest';;
  • REMINDER: no need to put a semicolon after the closing curly bracket in a function declaration but function expressions like var getSum=function(a,b){return a+b;}; (which must be declared before the invokation) should always end with a semicolon;
  • the square bracket notation (to access object's properties) offers much more flexibility than the dot notation: it allows you to specify property names as variables instead of just string literals;
  • IMPORTANT: JavaScript objects are reference types (like in Java) but the primitive values like strings and numbers are assigned or copied as a whole value;

DOM

  • document.querySelectorAll("ul li.tick"): this method supports CSS pseudo-classes (like :first-child) but not the CSS pseudo-elements (such as ::before or ::first-line; in that case, it returns an empty list);
  • REMINDER: a CSS property like font-size or border-left-style becomes a DOM property like fontSize or borderLeftStyle;
  • window.getComputedStyle() is useful:
        var elem = document.getElementById("intro");
        var styles = window.getComputedStyle(elem);
        alert(styles.getPropertyValue("font-size"));
  • elem.classList.add("newClass") is very useful; you can call remove(), toggle() (switch between add() and remove()) or contains() (the last one returns a boolean);
  • example of element creation:
        var newDiv = document.createElement("div");
        var newContent = document.createTextNode("Hi, how are you doing?");
        newDiv.appendChild(newContent);
        var existingDiv = document.getElementById("main");
        document.body.insertBefore(newDiv, existingDiv); //at the beginning
        document.body.appendChild(newDiv, existingDiv);  //at the end
  • insertAdjacentHTML() inserts HTML into the document without replacing the existing contents (innerHTML replaces all existing content); it uses "beforebegin", "afterbegin", "beforeend" or "afterend" as first argument;
  • REMINDER: remember these snippets: childElem.parentElement.removeChild(childElem); & `childElem.parentNode.replaceChild(newChildElem, childElem)``;
  • nodeName as in document.getElementById("main").firstElementChild.nodeName is read-only;
  • if (main.hasChildNodes()) then use children instead of childNodes (like you use firstElementChild, previousElementSibling or parentElement instead of firstChild, previousSibling or parentNode) to get a collection of elements (instead of all the nodes);
  • use the nodeType property.

BOM's useful properties and functions

  • window.screen;
  • window.location.href;
  • window.addEventListener("online", function(){alert("Welcome back!");});
  • navigator.cookieEnabled.

Type conversions

  • use (typeof something);
  • Number(str), String(null), Boolean(NaN);
  • Boolean("0") yields into true; BEWARE: in PHP, string "0" is false but a non-empty string in JS is always true.

Event Listeners

  • Bubbling from the lowest level to the higher parent is the way to go in modern browsers (Capturing is rarely used, often unsupported and only works with event handlers registered with the addeventlistener() method with the third argument is set to true);
  • event.target is the target element that generates the event; this represents the current element so the element tht has a currently running handler attached to it (thus, not the same as event.target);
  • you can stop the propagation (or prevent the default one to happen with event.preventDefault()):
      function showAlert(event){
          alert("You clicked: " + this.tagName);
          event.stopPropagation(); // or event.stopImmediatePropagation() to enforce the stop
        }
        var elems = document.querySelectorAll("div, p, a");
        for (let elem of elems) {
          elem.addEventListener("click", showAlert);
        }

Borrowing methods

You can use call() to borrow a method from the caller (IMPORTANT: apply(thisObj. [argsArray]) is like call(thisObj, arg1, arg2, ...) but with an array of arguments as a second parameter):

    var A = {
      name: "object A";
      say: function(greet) {
        alert(greet + ",  " + this.name);
      }
    }
    var B = {
      name: "object B";
    }
    A.say.call(B, "Hello");

Reduce

Array.reduce() is very useful; it's a functional trick; the first argument will be the accumulator and the second argument is the current value in the array;

    var numbers = [2, 5, 6, 4, 3, 7];
    var max = numbers.reduce(function(acc, i) {
      return Math.max(acc, i);
    });

Closures

Like in Lisp, it's an inner function that has access to the parent function's scope, even after the parent function has finished executing.

    function makeCounter() {
      var counter = 0;
      function make() {
        counter += 1;
        return counter;
      }
      return make;
    }

    var myCounter = makeCounter();
    console.log(myCounter()); // the makeCounter() has already finished executing,
                              // but the closure internally stores references to
                              // their outer variables, and can access and update
                              // their values

    /** as an anonymous function expression */
    var myCounter = (function() {
      var counter = 0;
      return function() {
        counter += 1;
        return counter;
      }
    })(); // don't forget the last () to invoke the outer lambda

    /** getter/setter examples */
    var getValue, setValue;
    (function() {
      var secret = 0;
      getValue = function() {
        return secret;
      };
      setValue = function(x) {
        if (typeof(x) === "number") {
          secret = x;
        }
      };
    }());

Strict mode

Enable strict mode: "use strict"; restrictions:

  • undeclared variables;
  • deleting a variable;
  • duplicating a parameter name;
  • writing a read-only property as in: Object.defineProperty(aPersonObject, "gender", {value: "male", writable: false}); will throw an error;
  • adding a new property to a non-extensible object set with Object.freeze(myObject) (REMINDER: check the extensibility with Object.isExtensible(myObject));
  • eval cannot alter scope (neither declaration/modification of variables, nor definition of function);
  • eval & arguments are treated like keywords; await, implements, interface, package, private, protected, public and static are reserved for future (post ES6) and aren't allowed in strict mode;
  • the unrecommended with statement is forbidden;
  • octal numbers.

JSON

  • syntax rules: {} for object, [] for array, number, string (must be enclosed in "), true, false & null;
  • example:
      var json = `{
          "book": {
            "name": "Harry Potter and the Goblet of Fire",
            "author": "J. K. Rowling",
            "year": 2000,
            "characters": ["Harry", "Hermione", "Ron" ],
            "price": {
              "paperback": "$10.40",
              "hardcover": "$20.32",
              "kindle": "$4.11"
            }
          }
        }`;
        var obj = JSON.parse(json);
        function printValues(obj) {
          for (var k in obj) {
            if ((obj[k] instanceof Object)) {
              printValues(obj[k]);
            } else {
              document.write(obj[k] + "<br>");
            };
          }
        };
        var sameJson = JSON.stringify(obj);

Error handling example

    var num = prompt("Enter a positive integer between 0 & 100");
    var start = Date.now();
    try {
      if (num > 0 && num <= 100) {
        alert(Math.pow(num,num));
      } else {
        throw new Error("An invalid value is entered);
      }
    } catch(e) {
      alert(e.message);
    } finally {
      alert("Execution took: " + (Date.now() - start) + "ms");
    }

Regex

  • syntax:
        var literalRegex = /^Mr\./;
        var regex = new RegExp("^Mr\\."); // BEWARE: you need to double-escape
                                          //         in the constructor syntax
  • test() (and exec()) is a RegExp method;
  • search(), replace(), match() & split() are String methods.

Cookies

  • creation:
        document.cookie = "name=" + encodeURIComponent("Christopher Columbus") + "; max-age=" + 30*24*60*60 + "; path=/; domain=hondana.net; secure";
  • reading:
        function getCookie(name) {
          var cookieArr = document.cookie.split(";");
          for (var cookie of cookieArr) {
            var cookiePair = cookie.split("=");
            if (name == cookiePair[0].trim()) {
              return decodeURIComponent(cookiePair[1]);
            }
          }
          return null;
        }

Published by Martial BONIOU (2021-03-16)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment