This page covers lots of JS tidbits that I'm having trouble categorizing under other pages. Things on this page might frequently shift around or get moved to other pages.
ES Versions
Very comprehensive compatibility table: kangax.github.io/compat-table
Forgetful JS
These are things that, for whatever reason, have a hard time sticking in my head. I'm hoping that summarizing some of them and writing them down will help me remember!
Multiple Variable Declaration and Initialization
There are multiple ways to create multiple variables in a single go, with or without value assignment:
// If you don't need to assign a value, this is fine
let alpha, bravo, charlie;
// If you need to assign values, it gets trickier
// You can use **Destructuring Assignment**...
// | ... with arrays
let [width, height] = [1920, 1080];
// | ... with objects
let {wheels, seat} = bike;
// You can use also use a single keyword with multiple initializations, but you still need an `=` with each, so not much space is saved.
let day = 'Tuesday',
month = 'August',
date = 10
Object Destructuring
There are all kinds of destructuring in JS - check out MDN's page on Destructuring Assignment for details. There are lots of advanced ways to use them, but I would argue that some of the more advanced uses sacrifice readability and simplicity for not much gain in terms of reducing boilerplate.
Function Argument Destructuring
A common one I forget is available is destructuring of function arguments within the signature itself. I'll give you an example:
// This:
function talkToPets(shelter) {
const {cats, dogs, birds} = shelter;
// Or even more verbose, without destructuring at all
// const cats = shelter.cats
// const dogs = shelter.dogs
// const birds = shelter.birds
}
// Becomes this:
function talkToPets({cats, dogs, birds}) {
//
}
You can even define defaults!
function talkToPets({cats = ['whiskers','felix'], dogs, birds}) {
//
}
Destructuring to existing variables
To destructure to existing variables, wrap the entire code section in parentheses:
({varName, varNameTwo} = hasBothVars);
Here is a real-world example (getting x & y from touch events):
/**
* @param {MouseEvent | TouchEvent} event
*/
const getEventCoords = (event) => {
let clientX, clientY;
if ('touches' in event) {
({ clientX, clientY } = event.touches[0]);
} else {
({ clientX, clientY } = event);
}
return {clientX, clientY};
}
For more details, see Flavio's post
Computed Property Names
Computed property names are an awesome new(er) feature (ES6), which reduce a lot of the standard boilerplate involved when trying to use a dynamic value as the key itself on an object. For example, pretend you want to store an action keyed by a timestamp. Without computed property names, you would have to use:
const nowStamp = (new Date()).getTime();
const actions = {};
actions[nowStamp] = 'User logged in';
Using computed properties, you can use the variable directly as the key:
const nowStamp = (new Date()).getTime();
const actions = {[nowStamp]: 'User logged in'}
Or, even more succinct
const actions = {[(new Date()).getTime()]: 'User logged in'}
Computed Property Trick: Variable name as key
If you want to construct an object, where the keys are the actual variable names, and the values are the values, perhaps for logging, this is easy to do with Computer Properties, + ES6 Shorthand Property Names.
var dog = {
name: 'barky',
age: 5
}
var cat = {
name: 'whiskers',
age: 8
}
console.log({dog, cat});
/*
{dog: {…}, cat: {…}}
dog: {name: "barky", age: 5}
cat: {name: "whiskers", age: 8}
*/
For the above,
console.table([dog,cat])
might be even better, sincedog
andcat
share the same keys.
Buffers
String to buffer
In Node.js, this is pretty easy with built-ins:
const myBuff = Buffer.from(myString, 'utf8');
With Web APIs, there is a bit more setup (and the ecosystem is currently changing). Currently, the best way seems to be to use the TextEncoder API, which returns a buffer (specifically a Uint8Array
):
const encoder = new TextEncoder();
const myBuff = encoder.encode(str);
Object Methods
This is not a completely comprehensive list, but has most of the unique ones that some devs might not be familiar with
Method | Use | Signature |
---|---|---|
assign |
Copy all enumerable key-value pairs from source to target. | Object.assign(target, source) |
create |
Manually construct a new object, using an existing object as the prototype. | const clone = Object.create(source) |
entries |
Get an array of key-value pairs (as [key,value] tuples) |
kvPairsArr = Object.entries(myObj) |
values |
Get an array of just the values inside an object (not keys). | vals = Object.values(myObj) |
freeze (*) |
Freeze an existing object, preventing changes (makes immutable) | Object.freeze(myObj) |
seal (*) |
Prevents additions or deletions, but allows mutations of existing props' values. | Object.seal(myObj) |
*
= By definition, definitely mutates the original (source) object.
Array methods
Why are these so hard to remember? I don't know. But hopefully these practical examples will help!
Here is a master table of methods, some of which will be broken down more further down the page. An even more complete list of methods can be found on MDN, here.
Method | Use | Signature |
---|---|---|
forEach |
Loop over an array and callback for each element | myArr.forEach((element) => doAny(element) ) Full Signature: myArr.forEach((element, index, myArr) => doAny(element)) |
map |
Create a new array from the output of a callback called on each element of the original | const formattedArr = myArray.map((element) => return newElement) |
filter |
Creates a new array from the elements of the source that passed a test. Key to remember: If you want to keep element, return true, if you want to discard, return false | const filtered = myArr.filter((element) => true || false) Full: const filtered = myArr.filter((element, index, sourceArr) => true || false, thisArg?) |
findIndex |
Find the index of the first element in the arr that passes your test function | myArr.findIndex((element) => true || false) |
push |
Add to end of array | myArr.push(newElement) |
unshift |
Add to start of array | myArr.unshift(newElement) |
pop |
Remove from end | const removedEnd = myArr.pop() |
shift |
Remove from start | const removedStart = myArr.shift() |
indexOf |
Get the index of thing | const found = myArr.indexOf(searchVal) Will be -1 if not found. |
splice |
Remove by index | const removed = myArr.splice(startIndex, deleteCount) Does modify original array in place. Elements can be inserted in place of those removed, by passing as final args. |
slice |
Returns a copy of a subsection of the original array | const mySlice = myArr.slice(startIndex, endIndex) Does not modify original array Element at endIndex is not included in extracted.This is a great way to quickly create a clone of an array - use without arguments: const clone = myArr.slice() |
join |
Join elements of an array | const joined = myArr.join(separator) |
concat |
Join multiple arrays together | const combined = myArr.concat(myArrBeta) |
reverse |
Reverse the order | myArr.reverse() |
sort |
Sort the array | myArr.sort((firstElem, secondElem) => (+ || - || same)) Also, see subsection below |
reduce |
Reduces an array to a single element output | myArr.reduce((runningValue, currentValue) => newRunningValue) Also, see subsection below |
some |
Tests that at least one element of array satisfies test | const result = myArr.some(testFunc) Full: const result = myArr.some((elem,index,fullArr)=> true || false, thisArg?) |
For each of the samples, this will be the raw starting array:
const users = [{
"id": 25,
"name": "Amata Pittet",
"email": "apittet0@utexas.edu",
"age": 32,
"num_pets": 1,
"notification_opt_out": false
}, {
"id": 10,
"name": "Tessi Maddy",
"email": "tmaddy1@cocolog-nifty.com",
"age": 29,
"num_pets": 2,
"notification_opt_out": true
}, {
"id": 3,
"name": "Gillan Birkby",
"email": "gbirkby2@thetimes.co.uk",
"age": 50,
"num_pets": 0,
"notification_opt_out": true
}];
Creating an Array
- If the "thing" is iterable, or array-like, you can use
Array.from()
- For a regular object, you can use...
- For values:
Object.values(object);
- For keys:
Object.keys(object);
- Both ({alpha: 1, beta: 2} => [{alpha: 1}, {beta: 2}])
Array.prototype.reduce
is a decent approach:Object.keys(myObj).reduce((run, curr) => { run.push({ [curr]: myObj[curr] }); return run; }, []); // Or, one liner: Object.keys(myObj).reduce((run, curr) => [...run, { [curr]: myObj[curr] }], []);
-
Warning: I've used
[curr]: value
to set the key to the value of a variable. This is an ES6 feature, called "computed property names"; for legacy support, you need to first create an object, then create key-pair normally (tempObj = {}; tempObj[curr] = myObj[key]
);
- For values:
- For creating an array from scratch, prefilled with a value:
- Use
const myArr = Array(n).fill(val)
- Where
n
= size of array, andval
is value to fill with - If you want
val
to be the return value of some function,myFunction
, you could use:Array(n).fill(0).map(myFunction)
- Where
- Don't forget to use
.fill()
before.map()
, or else map will have no effect! - Relevant S/O
- Use
Array Splice vs Slice
If you are having trouble remembering the difference between splice
and slice
, an easy way to remember is imagining you want to buy a piece of cake. You would ask for a slice, and would definitely not use the word splice.
Similarly, if you want part of an array, you use slice
, and calling myArray.slice()
to clone the entire array is analogous to buying an entire cake but buying it piece by piece.
For splice
, a good analogy is old movie film reel editing, where you cut scenes out of a film by removing a section and then joining the ends where the gap has opened up.
Array Reduce
Signature:
- Minimal:
myArr.reduce((runningValue, currentValue) => newRunningValue)
- Full:
myArr.reduce((runningValue, currentValue, currIndex, sourceArr) => newRunningValue, initialValue)
What:
- At its simplest, reduce takes an array and reduces it to one thing.
Uses:
- Often used for summing:
const totalPets = users.reduce((running, curr)=> curr.num_pets + running, 0) // > 3
[0, 24, 20].reduce((run,curr) => run + curr) // 44
- Map an array of objects to a dictionary object with sensible keys. In this example, the user's ID # will become the object key
const usersById = users.reduce((running,curr) => { running[curr.id] = curr; return running; }, {}); // Now we can do this: const gillian = usersById[3];
- Creating aggregators:
const averages = users.reduce((running, curr, index, srcArr) => { const count = (index + 1); const totalAge = ((running.totalAge || 0) + srcArr[index].age); const totalPets = ((running.totalPets || 0) + srcArr[index].num_pets); running['totalAge'] = totalAge; running['totalPets'] = totalPets; running['avgAge'] = totalAge / count; running['avgPets'] = totalPets / count; return running; }, {}); delete averages.totalAge; delete averages.totalPets; console.log(averages) // > {avgAge: 37, avgPets: 1}
Note: MDN and most sites use "accumulator" as the name of the first argument to reduce. This bothers me, as besides being overly specific, it implies that reduce is about summing or gathering values together, which is does not have to be. I much prefer to use variables like
runningValue
, which just implies that there is a value carried while reducing and is used for the output.
Array Sort
MDN: array.prototype.sort
Signature:
myArr.sort((firstElem, secondElem) => ( - || + || 0))
What:
- Sorts the array based on comparison function / callback
Here is how your comparison function should return a value:
Element you want to come first / move forwards | What you should return |
---|---|
firstElem | < 0 ( - ) |
Neither (don't change position) | 0 |
secondElem | > 0 ( + ) |
Put another way, if you are going to sort numerically, here is how you would apply subtraction to sort:
Sort Direction | Basis of comparison |
---|---|
Small -> Large | firstElem - secondElem |
Large -> Small | secondElem - firstElem |
For example, if we wanted to sort our users by age, from youngest to oldest, we could do this:
const youngToOldUsers = users.sort((userA, userB) => userA.age - userB.age);
But you can use it with more than just numerical comparison!
For example, what if we want to sort users so that everyone that has opted out of notifications comes before those who have not:
const optedOutFirst = users.sort((userA,userB) => userA.notification_opt_out === true ? -1 : 0);
💡 If you want to sort strings alphabetically, you can use the
localeCompare
method
Arrays - Dealing With Duplicates
Arrays - Removing Duplicates
The easiest way is to convert the array into a set
, since those cannot contain duplicates (by design), and then convert back with the spread operator (...
):
const uniques = [...new Set(myArray)];
If all you want is the count, or something like that, you don't have to use spread to convert it back:
// Notice: we use `.size` instead of `.length`, since we are keeping it as a Set
const uniqueCount = (new Set(myArray)).size;
For a non-ES6 solution, there are a lot of other approaches you can use, including combining .filter() and .indexOf(), hash maps, or .reduce().
Some examples:
const uniques = myArr.filter((elem,pos,arr) => arr.indexOf(elem) == pos)
Arrays - Finding Duplicates
If you want to find the duplicates in an array, you can use some approaches that are very similar to those for removing duplicates. For example,
const getDupes = items => items.reduce((acc, v, i, arr) => arr.indexOf(v) !== i && acc.indexOf(v) === -1 ? acc.concat(v) : acc, []);
(credit)
Arrays - Splitting Into Batches
The array slice()
method is perfect for chunking an array into sized batches.
For example:
const myArr = ['a','b','c','d','e','f','g'];
const chunkSize = 3;
for (let x = 0; x < myArr.length; x += chunkSize) {
const chunk = myArr.slice(x, x + chunkSize);
console.log(chunk);
}
// ['a','b','c']
// ['d','e','f']
// ['g']
Loop Types
MDN main page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration
Another resource - https://www.tutorialrepublic.com/javascript-tutorial/javascript-loops.php
Loop | Signature | Note |
---|---|---|
for |
for ([initialVal]; [executeIfCondition]; [finalExpression];) {...} |
Don't use const for variable assignment of initialVal - won't allow modification via ++ , -- , etc.Prefer let or var |
do...while |
do {[statement]} while ({condition}); |
Condition is evaluated after do block, and if true, starts another loop through |
while |
while ([condition]) {[statement]} |
Condition is evaluated before statement is executed. You can use break to exit statement early and stop loop. |
for...in |
for ([variable] in [object]) {[statement]} |
Only iterates over enumerable properties, in a random order. Unlike for loops, you can use const for assigment here (and probably should!).Although works with arrays, not recommended due to random order. In general, simple for , or for...of is recommended over for...in . Unless you need to iterate over object keys of course. |
for...of |
for ([variable] of [iterableObject]) {[statement]} |
Loop over iterable objects. Using let vs const depends on how you use var in statement - use let if reassigning / modifying. |
[].forEach |
myArr.forEach((element) => doAny(element) ) Full Signature: myArr.forEach((element, index, myArr) => doAny(element)) |
Loop over an array and callback for each element |
For Loops - Which to Pick?
In general, for...of
is recommended as the go-to pick.
Compared to for...in
- It ignores non-iterable properties,
for...in
does not.
Compared to forEach
for...of
works on many different iterable objectsfor...of
is also generally fasterfor...of
allows exiting the loop early (viabreak
), whereasforEach
does not
The main remaining reason to use
for()
or[].forEach()
is if you need access to the index. But this could also be accomplished with a counter, and most of the time is not needed anyways.
You can also combine with
for ... of
with object key iteration, by using something likefor (const key of Object.keys(myObject)) {}
For In vs For Of
For which is better to use, see above (for of
is usually better, if you can use it), but this section is for remembering how to use these methods.
Most important distinction:
for ... of
loops over iterables, and returns the values- Objects = ❌
- Arrays = ✔
for ... in
loops over objects, and returns the enumerable properties (keys)- Objects = ✔
- Arrays = ✔
If you can avoid using
for ... in
with objects, you should, and stick withObject.keys()
Examples:
const testObj = {
propA: 'hello',
propB: 'world'
}
const testArr = ['hello', 'world'];
/**
* 👇 -- Using `for ... of` -- 👇
*/
for (const val of testArr) {
console.log(val);
// > 'hello'
// > 'world'
}
try {
for (const val of testObj) {};
} catch (e) {
console.log(e);
// TypeError: testObj is not iterable
}
/**
* 👇 -- Using `for ... in` -- 👇
*/
for (const prop in testArr) {
console.log(prop);
// > 0
// > 1
console.log(testArr[prop]);
// > 'hello'
// > 'world'
}
for (const prop in testObj) {
console.log(prop);
// > propA
// > propB
console.log(testObj[prop]);
// > 'hello'
// > 'world'
}
Special Loop Flow Controls
See:
Console Methods
Method | Use | Signature |
---|---|---|
console.log |
Explodes your computer. JK! Logs to the console of course! | console.log(thingToLog); console.log(object_1, object_2?, object_3?, ...) console.log(msgStr, substr_1?, substr_2?, ...) (string replacement) |
console.assert |
Spit something out to the console only if your expression evaluates as falsy. Kind of like wrapping logs in if statements -> if(!success) {console.log(result);} is close to console.assert(success, result) |
console.assert(boolLikeAssertion,thingToLog); console.assert(boolLikeAssertion, object_1, object_2?, object_3?, ...) console.assert(boolLikeAssertion, msgStr, substr_1?, substr_2?, ...) (string replacement) |
console.clear |
Clears the console | console.clear() |
console.count |
Increments and displays a counter value | console.count(labelStr?) |
console.countReset |
Resets the global counter, or a specific one if passed a label. | console.countReset(labelStr?) |
console.debug |
Logs at the debug level, which means it will only show for users with that level enabled. |
console.debug(thing) Full signature matches console.log() |
console.dir |
Displays a tree / interactive hierarchical display of an object, with collapsible sections. Usually identical to just using log() |
console.dir(myJsObj) |
console.dirxml |
Same as console.dir() (shows hierarchical tree view), but for XML/HTML element.Also usually identical to just using log() |
console.dirxml(object) |
'console.error' | Logs error | console.error(thing) Full signature is same as log() |
console.group |
Starts a group of messages in the console (kind of like a tree level) | console.group() console.group(label?) Use console.groupCollapsed() if you want to start with it collapsed by default |
console.groupEnd |
Ends a group of messages in the console (started by console.group() ) |
console.groupEnd() |
console.info |
Logs at the info level. Identical to log() except for Firefox, which displays an icon. |
console.info(thing) Full signature matches console.log() |
console.table |
Output a pretty formatted table, with auto headers and body. Only helpful if all objects have same keys, logging a single object, or logging a multidimensional array where each sub-array has the same length |
console.table([thing_1, thing_2, ...]) console.table([thing_1, thing_2, ...], [columnNameToOmit_1, ...]) |
console.time |
Start a timer, which can be checked later. | (labelStr?) |
console.timeEnd |
Stop a timer, and log the value. | console.timeEnd(label?) |
console.timeLog |
Log the value of a timer without stopping it | console.timeLog(label?) |
console.trace |
Output an (interactive) stack trace to the console. | console.trace() Or, pass any data (unlimited), and they will be logged just like console.log() --->console.trace(any?) |
console.warn |
Logs at the warn level. |
console.warn(thing) Full signature is same as log() |
arg_name?
<--?
indicates argument is optional
Most browsers extend native Console methods with extra utilities; this docs page from the MS Edge documentation is an excellent overview that applies to many of the popular browsers, not just Edge.
- For example, in both Firefox and Chromium browsers, you can use
$
as an alias fordocument.querySelector
, and$$
as an alias fordocument.querySelectorAll
Post vs Pre-increment
Most of the conversation around post vs pre-incrementing is about optimization and performance; but this is a bit of a moot point in a more abstracted / less bare metal language like JS anyways. But... there is something very important to remember: preincrement adds one to the value and then returns it, whereas postincrement returns the value first and then increments it last.
Why does this matter? Consider the following example, where we have a value that is either a number or undefined, and if undefined, we want to start it at 1:
var val = undefined;
val = val ? val++ : 1;
// > 1
// Good so far!
val = val ? val++ : 1;
// > 1
// Wait... val is not going up!
Because we are assigning the value of the return of a postincrementer to itself, and a postincrementer always returns the original value before incrementing it, the value is stuck at whatever was first fed into the postincrementer!
Now, with pre-incrementer:
var val = undefined;
val = val ? ++val : 1;
// > 1
// Good so far!
val = val ? ++val : 1;
// > 2
// Yeahoo!
URL API
The URL Web API has been around for a while now, and provides a much-need abstraction API for dealing with URLs.
For example, to access a query string parameter with document.location.search
requires some sort of pattern matching (e.g. RegEx
) to extract the key pair you want, but with the URL API, it is as easy as (new URL(document.location)).searchParams.get(myParamKeyStr)
or (new URLSearchParams(window.location.search)).get(myParamKeyStr)
.
URL API Constructor
One of the main things to remember is that, in order to use the methods of URL()
, you need to instantiate an instance of a URL object by calling the constructor.
The constructor can take:
- A full URL - aka absolute URL
const myUrlObj = new URL('https://joshuatz.com/blog/?utm_source=cheatsheets')
- A relative URL, plus a base
const myUrlObj = new URL('/blog/?utm_source=cheatsheets', 'https://joshuatz.com')
- Something that can be stringified to one of the above options
- This is helpful to know, because it lets you use shorthand; if you want to construct a URL object from the current browser URL, you can use
new URL(document.location)
instead ofnew URL(document.location.href)
, sincedocument.location.toString()
returns the fullhref
value anyway.
- This is helpful to know, because it lets you use shorthand; if you want to construct a URL object from the current browser URL, you can use
Setting Query String Parameters
You can set query string (aka querystring) values with UrlSearchParams.set('key', 'value')
.
Warning:
.set()
uses a special encoding when storing the value
Keep in mind that this only changes the value for that instance, not necessarily what the value was originally derived from.
For example, the current browser query string will not be updated until line 3 of the below code snippet:
const searchParams = new URLSearchParams(window.location.search);
searchParams.set('user', 'joshua');
window.location.search = searchParams.toString(); // actually set the live value in the browser
// This WILL navigate the page
// OR
const url = new URL(window.location.href);
url.searchParams.set('user', 'joshua');
window.location.href = url.toString();
// This also navigates the page
If you want to update the value of window.location.search
without navigating the browser to the new URL, you can use the history.pushState
API to accomplish this:
const url = new URL(window.location.href);
url.searchParams.set('user', 'joshua');
history.pushState({}, '', url.toString());
It's also worth noting that the pushState
method creates back-button / history entries. If you only want to modify the URL with zero side-effects, use history.replaceState
.
Setting Query String Parameters in Bulk
You can set query string parameters in bulk, e.g. with a key-value pair object, by:
- Passing the values object when instantiating URLSearchParams
const searchParams = new URLSearchParams({key: value, key_2: value_2})
- Updating search params in a loop, with
myUrl.searchParams.set(key, val)
in each iteration
Why Use Object.defineProperty()?
Rather than just using myObj.prop = 'myVal
, using the Object.defineProperty()
method has some advantages:
- You can control options:
configurable
enumerable
writeable
- Etc.
- These options in turn affect how the object can be consumed and modified
You can even declare an accessor descriptor
, which lets you define a getter and setter function.
For more details, see the MDN page on the defineProperty.
Functions
Functions - Arguments Overview
There are two main ways to access arguments within JavaScript functions, and here is a quick comparison
⬇➡ | ||
Works with.... |
ONLY regular functions (not arrow functions) |
Both regular and arrow functions |
Syntax |
|
|
Typeof Arguments |
Array-like (lacks Array methods, but easily converted to Array and is iterable) |
Regular Array. |
🚨 WARNING: Arrow functions (e.g.
() => {}
) do not have access to thearguments
variable, but you can use rest parameters.
If you are capturing arguments to pass to another function, use the spread operator in both places, like so:
function takeArgs(...args) {
otherFunc(...args);
}
Or, use .apply()
:
function logArgs(...args) {
console.log.apply(console, args);
}
Functions - Different Declaration Types and Syntax
Declaring functions as object properties:
const myObj = {
/**
* Regular function as object property
*/
myFunction: function() {
//
},
/**
* Arrow function as object property
*
* Be warned: `this` will NOT refer to `myObj`, due to how arrow functions work. It will try to be resolved to outer scope.
*/
myArrowFunction: () => {
//
}
}
JavaScript Closure and Scope
Helpful resources:
- javascript.info/closure
- Pavlutin: Temporal Dead Zone
Dates
Quick notes:
- How can you compare dates in JavaScript?
- If you are comparing with
<
,>
,<=
or>=
, you don't need to do anything special - you can use the Date objects as they are - If you need to check for equality (e.g.
==
or!=
), you need to first convert the dates to their numeric form (ms since epoch) with.getTime()
- If you are comparing with