General JavaScript
Tools
- JSHint, a Static Code Analysis Tool for JavaScript
Resources
- ECMAScript compatibility table
- 2ality
- javascript.info
- Eloquent JavaScript
- Fun Fun Function YouTube channel.
- List of resources by Emma Wedekind.
- Improve your understanding of synchronous vs asynchronous code with Loupe, a little visualization to help you understand how JavaScript's call stack/event loop/callback queue interact with each other.
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
- State Monad in JavaScript
- Function composition with lodash
- Professor Frisby Introduces Composable Functional JavaScript
- Professor Frisby's Mostly Adequate Guide To Functional Programming
Glossary of concepts
Understand isomorphic and universal apps. Universal = run everywhere, isomorphic = server rendered for faster initial render on the client.
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
andNaN
. 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:
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:
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:
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 timevar Y = outer(); // outer() invoked the second time//end of outer() function executionsX(); // X() invoked the first timeX(); // X() invoked the second timeX(); // X() invoked the third timeY(); // Y() invoked the first time
The result is the following:
a=20 b=10a=20 b=11a=20 b=12a=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 thefalse
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 to0
true
is converted to1
undefined
and strings that aren’t numbers in disguise ( like"2"
) are converted toNaN
. 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"
, and2
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
andtoString
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 callvalueOf
first and ToString will first trytoString
. 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 aTypeError
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 tryingtoString
first forDate
objects, and just returning the wrapped symbol value forSymbol
objects. E2.1:null
andundefined
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):
true + false12 / "6""number" + 15 + 315 + 3 + "number"[1] > null"foo" + + "bar"'true' == truefalse == 'false'null == ''!!"false" == !!"true"[‘x’] == ‘x’[] + null + 1[1,2,3] == [1,2,3]{}+[]+{}+[1]!+[]+[]+![]new Date(0) - 0new Date(0) + 0
JavaScript frameworks
Hyperapp, 1kB JavaScript micro-framework for building declarative web applications.
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
andconst
are block scoped, which is more restrictive.var
s 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 theconst
array.Object.freeze()
can be used to make an object immutable.Before
let
andconst
, people used IIFE's to limit the scope ofvar
s. Now, you can just uselet
orconst
inside a curly brackets{ ... }
These pieces of code illustrate more advantages of
let
vsvar
. After 5 seconds, this will print out 'Number is 10' ten times:
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.
for (let i = 0; i < 10; i += 1) {console.log(i);setTimeout(() => console.log(`Number is ${i}`), 5000);}
let
andconst
also solve the temporal dead zone. I.e. they will throw an error if you reference them before declaring them.var
s on the other hand, return undefined in that scenario.Is
var
dead?. My takeaway: useconst
by default,let
when you need rebinding, don't usevar
.
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:
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 thearguments
object.You can do
function foo(a, b = 2, c = 3)
to pass default values for certain arguments. You can call this likefoo(1)
,foo(1, 1)
andfoo(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)
orfunction 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
andrepeat
. Avoids having to reach for regular expressions in some use cases. Wes showcases a nifty use ofrepeat
for a left padding function that right aligns text:
function leftPad(str, length = 20) {return `${' '.repeat(length - str.length)}${str}`;}
padStart
andpadEnd
is even better for that.
De-structuring
// Assigning values to custom named variablesconst { twitter: tw, facebook: fb } = wes.links.social;// Using defaultsconst settings = { width: 300, color: 'black' };const { width = 100, height = 100, color = 'blue', fontSize = 25 } = settings;// Destructuring arrays with restconst team = ['Wes', 'Harry', 'Sarah', 'Keegan', 'Riker'];const [captain, assistant, ...players] = team;// Switching valueslet inRing = 'Hulk Hogan';let onSide = 'The Rock';[inRing, onSide] = [onSide, inRing];// Named arguments and default values for functionsfunction 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: regularfor
,forEach
method available on Arrays and other objects,for...in
andfor...of
. This last one seems to be the best - it provides readable syntax,break
andcontinue
support and works with everything except objects.
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
orfor...of
with theObject.entries()
method.Another strategy is to convert arrayish things into arrays using
Array.from()
andArray.of()
.
Array methods
find
andfindIndex
The spread operator is useful for concatenating and cloning arrays:
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.
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)
.
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:
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
.