General JavaScript

Tools

  • JSHint, a Static Code Analysis Tool for JavaScript

Resources

ToDos

  • Learn about relative performance of different object and array operations.
  • Learn how to find and fix bottlenecks in code and articulate tradeoffs between different options
  • Learn about the topic of data structures and master the most popular: arrays, maps, sets, DOM trees.
  • Understand Big O notation
  • Understand tree traversal
  • Practice explaining your code and your choices
  • Study raw DOM APIs
  • Take a utility library like lodash and implement some methods and compare your solutions in terms of performance, understand what edge cases you need to handle
  • Deepen understanding of ES Modules and CommonJS imports and exports. Check out Flavio's article and Exploring JS's chapter.
  • Understand pitfalls of recursion.

Functional JavaScript

Glossary of concepts

  • Understand isomorphic and universal apps. Universal = run everywhere, isomorphic = server rendered for faster initial render on the client.

  • Memoization

  • Strict mode

  • Async

  • Named vs anonymous functions

  • Currying and partial application are two ways of transforming a function into another function with a generally smaller arity (number of arguments). Currying always produces nested unary (1-ary) functions. The transformed function is still largely the same as the original. Partial application produces functions of arbitrary arity. The transformed function is different from the original – it needs less arguments. Interesting point of view in "Currying is not idiomatic in JavaScript". Currying is a technique for transforming functions so that they help with partial application.

  • A falsy value is a value that is considered false when encountered in a Boolean context. Falsy values are false, 0, "", null, undefined and NaN. Truthy values are all the rest. JavaScript coerces values to Boolean in Boolean contexts.

this

I just came across a situation I wasn't expecting with a code snippet like this one:

javascript
let instance = new Something();
document.addEventListener('click', instance.method);

It turns out that when using references to functions (method is a function), they become unbound to this. In the case of passing them as callbacks in addEventListener, they also become bound to the event target. This means that any this.property code you were using in the body of the method might not behave as you expected it to do - because of the rebinding, this no longer refers to instance but to document.

This can be solved like so:

javascript
let instance = new Something();
document.addEventListener('click', instance.method.bind(instance)); // boom!

I found a couple of videos by Zac Gordon quite helpful: "An Introduction to "this" keyword in JavaScript", "Binding "this" in JavaScript" and "An Example of Binding "this" in JavaScript with addEventListener()".

Closures and scope

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created. This Freecodecamp article provides a good discussion. Try to understand this code:

javascript
function outer() {
var b = 10;
var c = 100;
function inner() {
var a = 20;
console.log('a=' + a + ' b=' + b);
a++;
b++;
}
return inner;
}
var X = outer(); // outer() invoked the first time
var Y = outer(); // outer() invoked the second time
//end of outer() function executions
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third time
Y(); // Y() invoked the first time

The result is the following:

shell
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10

Notice that the updated value of b persists in inner's closure from call to call, as you can see from the consecutive calls to X. Note also that c will not be part of inner's closure. Finally, it's not that a is overwritten/redeclared by var a = 20: it leaves memory every time inner finishes and declared anew every new execution. It doesn't form part of the closure.

Type coercion

First understand that there's two "types" of data types: 6 primitive types (Boolean, Null, Undefined, Number, String and Symbol) and the Object type, from which many data structures/types are built. There's these possible transformations:

ToBoolean

  • undefined, null, 0, -0, NaN and '' are all considered falsy and are coerced into the false primitive value (implicitly in boolean contexts or explicitly with the appropriate syntax)
  • Everything is coerced into the true primitive value.

ToNumber

  • null, false, and '', are converted to 0
  • true is converted to 1
  • undefined and strings that aren’t numbers in disguise ( like "2") are converted to NaN. Numbers in strings clothes are converted to the corresponding numeric value.

ToString

  • The literal value of the input becomes the string. For example undefined becomes "undefined", true becomes "true", and 2 becomes "2".

Symbols

Symbols aren’t really meant to be converted into other types. ToBoolean is the only one that will still convert them to true as they’re not on the “falsy” list. ToNumber and ToString will throw TypeErrors when given a symbol value to convert.

Objects

  • For ToBoolean it’s simple: again, objects are not in the falsy list, so they’re always converted to true

  • Most objects get valueOf and toString methods from their prototype. The cards will call one of them to try and get a primitive representation of the object. As you might have guessed, ToNumber prefers to call valueOf first and ToString will first try toString. If this call returns a primitive value, that value is converted to a string or number following the rules above. Otherwise, the card will try the other method, its least favorite, and see if that returns a primitive value. If it didn’t, no conversion is possible, so a TypeError will be thrown.

Operators

  • R1: All operators convert their operands of different types to numbers.

  • E1: Logical operators convert them to booleans instead.

  • E2: == and + apply ToPrimitive on their object operands which means trying toString first for Date objects, and just returning the wrapped symbol value for Symbol objects. E2.1: null and undefined are only considered equal to each other by == and not converted to any other types.

  • E3: If one of the operands is a string, the + operator will convert the other into a string as well.

As an exercise, try to understand these rules and then try to predict the following (taken from this Freecodecamp article):

javascript
true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
[‘x’] == ‘x’
[] + null + 1
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
new Date(0) - 0
new Date(0) + 0

JavaScript frameworks

  • Hyperapp, 1kB JavaScript micro-framework for building declarative web applications.

  • React

  • Vue

  • Svelte is a newish framework. It seems to be part of the latest trend in frameworks: frameworks that compile your declarative code into efficient imperative DOM manipulations with no run-time:

    Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.

    Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.

    I was blown away by "Rethinking reactivity", a talk by Rich Harris. Svelte seems very promising. It also seems a good way to learn about reactive programming, which is something I've had in the back of my head for a while.

A review of ES6

This section has my notes from ES6 For Everyone with things added whenever my curiosity pulled me into going a bit deeper. Another good guide of ES6 features provided by Auth0.

A bit of history

ECMAScript is a scripting language specification of which JavaScript is the most popular implementation (other implementations are JScript, ActionScript and Google Apps Script).

The ES6 is ECMAScript version 6, published in June 2015. It seems that since then, Ecma International has released a new version every June, so in June 2018 we had ES9.

If you've got time, Auth0 did a nice review of JavaScript history:

  • In the early days, Netscape, founded by Marc Andreessen, wanted to add dynamic features to the web. They came up with Mocha, which was to become a scripting language for the web. Simple, dynamic, and accessible to non-developers.

  • They brought in Brendan Eich with the promise of developing "Scheme for the browser" (a dialect of Lisp). Time pressure, the rising popularity of Java and Netscape's relationship with Sun Microsystems ended in "a premature lovechild of Scheme and Self, with Java looks".

  • The Mocha prototype was integrated into Netscape in May 1995 and renamed LiveScript until December 1995 when it settled on JavaScript.

  • JavaScript made such a considerable difference in user experience that competing browsers had no choice but to come up with a working solution, a working implementation of JavaScript.

  • Microsoft called theirs JScript. The first version of JScript was included with Internet Explorer 3.0, released in August 1996.

  • Brendan Eich rewrote Mocha to clean up the mess produced by the rushed release. The JavaScript engine in Netscape was named SpiderMonkey, and that has been carried over to Firefox to this day (Mozilla came out of Netscape).

  • Brendan Eich: "I’m happy that I chose Scheme-ish first-class functions and Self-ish (albeit singular) prototypes as the main ingredients. The Java influences, especially y2k Date bugs but also the primitive vs. object distinction (e.g., string vs. String), were unfortunate."

  • Ecma International is an industry association formed in 1961 concerned solely with standardization of information and communications systems.

  • Work on the standard for JavaScript was started in November 1996. The identification for the standard was ECMA-262 and the committee in charge was TC-39.

  • The second version of the standard, ECMAScript 2, was released to fix inconsistencies between ECMA and the ISO standard for JavaScript (ISO/IEC 16262), so no changes to the language were part of it. It was released in June 1998.

  • AJAX, asynchronous JavaScript and XML, was a technique that was born in the years of ECMAScript 3. Although it was not part of the standard, Microsoft implemented certain extensions to JavaScript for its Internet Explorer 5 browser. One of them was the XMLHttpRequest function (in the form of the XMLHTTP ActiveX control). This function allowed a browser to perform an asynchronous HTTP request against a server, thus allowing pages to be updated on-the-fly. Although the term AJAX was not coined until years later, this technique was pretty much in place.

  • Work on ECMAScript 4 was fraught with strong differences within the committee. Some wanted to add big changes and features that would make JS more powerful, others thought that shouldn't be the path for JS. The work became so big and stretched out that ES4 was abandoned.

  • If you are already familiar with ECMAScript 6/2015 you will notice that many features from ECMAScript 4 were reintroduced in it.

  • All in all, ECMAScript 4 took almost 8 years of development and was finally scrapped. Adobe was in favor of many of the features that were included in ES4 and they created ActionScript implementing those.

  • In the year 2009 ECMAScript 3.1 was completed and signed-off by all involved parties. ECMAScript 4 was already recognized as a specific variant of ECMAScript even without any proper release, so the committee decided to rename ECMAScript 3.1 to ECMAScript 5 to avoid confusion.

  • All-in-all, ECMAScript 5 was a modest improvement that helped JavaScript become a more usable language, for both small scripts, and bigger projects. Still, there were many good ideas from ECMAScript 4 that got scrapped and would see a return through the ECMAScript Harmony proposal.

  • The ECMAScript Harmony proposal became a hub for future improvements to JavaScript. Many ideas from ECMAScript 4 were cancelled for good, but others were rehashed with a new mindset. ECMAScript 6, later renamed to ECMAScript 2015, was slated to bring big changes.

  • The release of ECMAScript 2015 caused a big jump in the use of transpilers such as Babel or Traceur. Even before the release, as these transpilers tracked the progress of the technical committee, people were already experiencing many of the benefits of ECMAScript 2015.

  • A new release process implemented by TC-39. All new proposals must go through a four stage process. Every proposal that reaches stage 4 has a strong chance of getting included in the next version of ECMAScript (though the committee may still opt to push back its inclusion). This way proposals are developed almost on their own (though interaction with other proposals must be taken into account). Proposals do not stop the development of ECMAScript. If a proposal is ready for inclusion, and enough proposals have reached stage 4, a new ECMAScript version can be released.

Variables (var, let, const)

  • Scope: var is function scoped, let and const are block scoped, which is more restrictive.

  • vars can be redeclared, even if it's not clear why you'd do that.

  • Using let to redeclare a variable is not allowed in general, but inside a narrower scope it is.

  • Using const on objects means the object shape cannot change, but the value of it's fields can. On arrays I'm not sure what it provides, probably that it has to remain an Array object. I can edit, add and remove elements from the const array. Object.freeze() can be used to make an object immutable.

  • Before let and const, people used IIFE's to limit the scope of vars. Now, you can just use let or const inside a curly brackets { ... }

  • These pieces of code illustrate more advantages of let vs var. After 5 seconds, this will print out 'Number is 10' ten times:

javascript
for (var i = 0; i < 10; i += 1) {
console.log(i);
setTimeout(() => console.log(`Number is ${i}`), 5000);
}

Versus this, which will print out 'Number is 1', 'Number is 2', etc.

javascript
for (let i = 0; i < 10; i += 1) {
console.log(i);
setTimeout(() => console.log(`Number is ${i}`), 5000);
}
  • let and const also solve the temporal dead zone. I.e. they will throw an error if you reference them before declaring them. vars on the other hand, return undefined in that scenario.

  • Is var dead?. My takeaway: use const by default, let when you need rebinding, don't use var.

Functions

  • Three main benefits from arrow functions: conciseness, implicit return, no rebinding of this.

  • Implicit returns cannot be surrounded by curly braces. To return an object literal, which is surrounded by curly braces, you can wrap it in parenthesis.

  • Arrow functions inside other functions don't rebind this, they inherit it from the parent. This makes them suited for certain cases, unsuited for others. For example:

javascript
const box = document.querySelector('.box');
box.addEventListener('click', function() {
// Regular function required for `this` to be `box`
this.classList.toggle('opening');
// Arrow function required for `this` to be `box`
setTimeout(() => this.classList.toggle('opening'), 500);
});
  • Be careful about using arrow functions whenever you want to use this. Some cases are inside functions, objects and prototype methods. They also don't provide access to the arguments object.

  • You can do function foo(a, b = 2, c = 3) to pass default values for certain arguments. You can call this like foo(1), foo(1, 1) and foo(1, undefined, 1)

Template literals

  • Template literals are string literals that accept embedded expressions and multi-line strings.

  • Tagged templates allow you to parse template literals with a function. The function signature for a tag would be function myTag(strings, arg0, arg1) or function myTag(strings, ...args) for an unknown number of arguments.

  • Aside: you can use the debugger statement to invoke any available debugging functionality, such as setting a breakpoint. If no debugging functionality is available, this statement has no effect.

  • Consider sanitizing tagged templates with DOMPurify.

Mixed bag

  • New string methods: startsWith, endsWith, includes and repeat. Avoids having to reach for regular expressions in some use cases. Wes showcases a nifty use of repeat for a left padding function that right aligns text:
javascript
function leftPad(str, length = 20) {
return `${' '.repeat(length - str.length)}${str}`;
}
  • padStart and padEnd is even better for that.

De-structuring

javascript
// Assigning values to custom named variables
const { twitter: tw, facebook: fb } = wes.links.social;
// Using defaults
const settings = { width: 300, color: 'black' };
const { width = 100, height = 100, color = 'blue', fontSize = 25 } = settings;
// Destructuring arrays with rest
const team = ['Wes', 'Harry', 'Sarah', 'Keegan', 'Riker'];
const [captain, assistant, ...players] = team;
// Switching values
let inRing = 'Hulk Hogan';
let onSide = 'The Rock';
[inRing, onSide] = [onSide, inRing];
// Named arguments and default values for functions
function tipCalc({ total = 100, tip = 0.15, tax = 0.13 } = {}) {
return total + tip * total + tax * total;
}
console.log(tipCalc());
console.log(tipCalc({ tip: 0.1, total: 80 }));

for loops

  • The for loops available in JavaScript are: regular for, forEach method available on Arrays and other objects, for...in and for...of. This last one seems to be the best - it provides readable syntax, break and continue support and works with everything except objects.
javascript
for (const cut of cuts) {
if (cut === 'Brisket') {
continue;
}
console.log(cut);
}
  • Array.entries() returns an Array iterator that can be used with the for...of loop.

  • The recommended approach to iterate over objects is to use for...in or for...of with the Object.entries() method.

  • Another strategy is to convert arrayish things into arrays using Array.from() and Array.of().

Array methods

  • find and findIndex

  • The spread operator is useful for concatenating and cloning arrays:

javascript
const pizzas = [...featured, 'veg', ...specialty];
const fridayPizzas = [...pizzas];
// fridayPizzas = pizzas creates a new reference to the same
// array in memory, not a new array

Object literal upgrades

You don't need to specify property: variable when variable has the same name as property. Same goes for functions. Also, object keys can now be generated dynamically using template strings.

javascript
const first = 'snickers';
const last = `bos`;
const age = 2;
const breed = 'King Charles Cav';
const dog = {
firstName: first,
last,
age,
breed,
pals: ['Hugo', 'Sunny']
};
const modal = {
create(selector) {
/* body */
},
open(content) {
/* body */
},
close(goodbye) {
/* body */
}
};
const invertColor = color => `#cccccc`;
const key = 'pocketColor';
const value = '#ffc600';
const tShirt = {
[key]: value,
[`${key}Opposite`]: invertColor(value)
};

Promises and async/await

Generators

Proxies

Sets

Maps

Least Common Multiple and Greatest Common Divisor

The GCD can be computed using Euclid's algorithm. GCM is associative, which means GCD(a,b,c) === GCD(GCD(a,b), c).

javascript
function gcd(a, b, ...rest) {
let numbers = [a, b, ...rest];
return numbers.reduce((acc, cur) => (!cur ? acc : gcd(cur, acc % cur)));
}

The LCM can be computed from the GCD:

javascript
function lcm(a, b, ...rest) {
let numbers = [a, b, ...rest];
return numbers.reduce((acc, cur) => {
if (acc === 0 && cur === 0) return 0;
return (acc / gcd(acc, cur)) * cur;
});
}

The Nth common multiple can be calculated like N * lcm.