Joshua's Docs - General JavaScript Misc. Notes

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, since dog and cat 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 creating an array from scratch, prefilled with a value:
    • Use const myArr = Array(n).fill(val)
      • Where n = size of array, and val 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)
    • Don't forget to use .fill() before .map(), or else map will have no effect!
    • Relevant S/O

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

MDN: Array.prototype.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 objects
  • for...of is also generally faster
  • for...of allows exiting the loop early (via break), whereas forEach 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 like for (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 with Object.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 for document.querySelector, and $$ as an alias for document.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 of new URL(document.location.href), since document.location.toString() returns the full href value anyway.

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

⬇➡

arguments object

Rest Parameters

Works with....

ONLY regular functions (not arrow functions)

Both regular and arrow functions
Syntax
function myFunc() {
	console.log(arguments[0]);
	console.log(arguments[1]);
}

// Function can have defined arguments and still use `arguments`
// Regular functions
function myFunc(...args) {
	console.log(args[0]);
	console.log(args[1]);
}

// Arrow functions!
const myFunc = (...args) => {
	console.log(args[0]);
	console.log(args[1]);
}

// You can also mix with named arguments to capture only additional
const myFunc = (name, ...extraArgs) => {
	console.log(`Hello ${name}`);
	// extraArgs will *not* contain `name`
	console.log('Extra details provided:', extraArgs);
}
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 the arguments 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:


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()
Markdown Source Last Updated:
Mon May 06 2024 19:15:01 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Wed Aug 21 2019 00:46:11 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback