You may have established a pattern of coding that utilizes a few key tools offered by your browser’s console. But have you dug any deeper lately? There are some powerful tools available to you, and they might just revolutionize the way you work.
The Comfort Zone
As developers, we like to find a way of working that make us feel efficient. The trap, though, is that we get comfortable with a certain workflow and assume there’s no better way. We’ve internalized an approach and don’t have to think about what we do.
A common developer workflow is to write code in an editor, save it, then switch to the browser and load the product to see if everything works. We then use the developer tools in the browser to tweak the CSS and maybe test how the products react to resizing and mobile emulation. We debug our scripts by adding a console.log()
statement wherever we need some insights — keeping a tight connection between line numbers and debugging.
This results in a bit of a mess. If you keep your browser tools open when surfing the Web, you’ll see a lot of messages in the code that shouldn’t end up in a final product. It not only makes it confusing for people who open them by accident, but can also be daunting for new developers to look at.
We only use a small percentage of the tools at our disposal, and we may miss out on opportunities to work better and with less effort. That’s okay, but let’s see how we can do better.
We’ll look at a few features of freely available developer tools you probably use but don’t know about. Specifically, these features are in the Chromium browser developer tools and some in Visual Studio Code. Let’s start by learning more about a perennial favorite — the Console.
Leveling Up Our Console Game
We’re conditioned early on in our development career to add a console.log(thing)
anywhere in our code to learn about what’s going on. Often that’s enough, but sometimes you still don’t get the right information, or it’s in some raw format that’s hard to understand.
You can see all the following tricks by grabbing this demo page on GitHub and opening it in your browser with your developer tools open. (Or view a [live demo on CodePen](on CodePen).
The first trick here is to add curly braces to variables you log. This not only prints out their value, but also the name of the variable. It makes it easier to track down in the log what value came from where.
let x = 2;
console.log(x) // 2
console.log({x}) // {x: 2}
Formatting logs
You can also use specifiers in your log message which start with a percentage sign. These allow you to log certain values in different formats:
%s
: logs as strings
%i
or %d
: logs as integers
%f
: logs as floating-point value
%o
: logs as an expandable DOM element
%O
: logs as an expandable JavaScript object
You can mix and match those. The order of replacement in the first string is the parameter order following it:
console.log('%ix %s developer', 10, 'console');
// 10x console developer
You can even convert with this, in case you get information that isn’t in a format you like:
console.log('%i', 12.34455241234324234); // 12
The %c
specifier allows you use CSS to style your log message in case you really want to stand out:
console.log('%cPay attention to me','color:firebrick;font-size:40px')
Logging is great, but there are more options you can use to make things more obvious and to avoid having to write functionality yourself.
Grouping logs
You can group your logs using console.group()
to show them as expandable and collapsible groups.
const label = 'The Millenium Falcon Crew';
console.group(label);
console.log('Leia');
console.log('Han');
console.log('Chewie');
console.log('Ben');
console.groupEnd(label);
You can nest groups, and you can use console.groupCollapsed()
to not expand them by default:
const extendedlabel = 'The Millenium Falcon Crew extended';
const meat = 'Humanoids';
const metal = 'Droids';
console.group(extendedlabel);
console.groupCollapsed(meat);
console.log('Leia');
console.log('Han');
console.log('Chewie');
console.log('Ben');
console.groupEnd(meat);
console.group(metal);
console.log('R2D2');
console.log('C3PO');
console.groupEnd(metal);
console.groupEnd(extendedlabel);
Allow for log filtering
Instead of console.log()
you can also use console.info()
, console.error()
and console.warn()
. This allows you to filter the messages you see in the console using the console sidebar or the levels selector. That way, you make it easier to find your own messages amongst those coming from third-party scripts and other scripts in you project.
Other useful console methods
You’ve probably created variables in your debugging journey that count how often a certain method was called or functionality executed. You can do the same thing by using the console.count()
and console.countReset()
methods. You can create as many of them as you want and distinguish by label:
console.count('Chocula'); // Chocula: 1
console.count(); // default: 1
console.count('Chocula'); // Chocula: 2
console.countReset('Chocula');
console.count(); // default: 2
console.count(); // default: 3
console.count('Chocula'); // Chocula: 1
You can also measure how long a certain part of your script takes using the console.time()
method:
console.time('go');
for(let i = 0; i < 200000; i+=1) {
let x = Math.random()*2000;
}
console.timeEnd('go'); // go: 11.7861328125 ms
Depending on the data you want to log, it also makes sense to use the right method for the job. Using console.dir()
displays not only the content but also the type of data you send it. If you want an XML representation of a node, for example, you can use console.dirxml()
. And console.table()
is excellent for showing JSON data as a sortable table.
Replacing Logging with Live Expressions
It’s tempting to use console.log()
to monitor things that change a lot. This isn’t only slow, but also pretty tough to follow. While it’s useful that the console automatically groups similar values as instances, it’s still a lot of movement, and you often find yourself only wanting to check the value instead of scrolling back to see where a certain change happened.
This is where live expressions come in. You can add live expressions by activating the eye button in the developer tools. You’re then asked to enter any valid JavaScript expression, and its value will show up above the console.
Try it out by activating the eye button and entering document.activeElement
. The expression should show body
as a value until you activate or focus on any other element in the page. You can see it in action on the SitePoint website in the following video:
[video width="718" height="334" mp4="https://uploads.sitepoint.com/wp-content/uploads/2021/03/1615178951sitepoint-active-element.mp4"][/video]
Each expression has an x
button next to it to stop monitoring the value. Live expressions are persistent and not connected to a certain domain or site. That’s why it makes sense to remove them after you’ve finished debugging to avoid errors.
Live expressions are wonderful little helpers to monitor changes in the data of your scripts without having to use the console. This is especially useful when you log very busy values — such as the mouse position. This is probably how you’d approach that:
let x = 0;
let y = 0;
document.addEventListener('mousemove', e => {
x = e.x;
y = e.y;
console.log({x}, {y});
});
The result is a really busy log, and it’s easy to miss important information. Using live expressions, you don’t have to grasp a fast-moving log, which most likely is also a faster debugging experience. You can see the difference in the following video.
[video width="800" height="410" mp4="https://uploads.sitepoint.com/wp-content/uploads/2021/03/1615179082log-vs-live-expression-smaller.mp4"][/video]
Try it out yourself: grab the demo code for moving the mouse with logging and moving the mouse without logging and try running both examples in the browser.
Using the Console to Manipulate the Current Document
The Console in the developer tools is so much more than a way to display a log. It’s a REPL that allows you to write and execute JavaScript and learn about available methods and properties of the current document using autocomplete. Just go to the Console in the developer tools, type doc
, and hit tab, and it will automatically convert it to document
. If you add a full stop, you’ll see all the available methods and properties. This is an engaging and simple way to learn about available methods and properties, and it allows you to write a lot of code in a short amount of time.
In addition to that, the Console is also an excellent way to interact with the current document and access parts of it. There’s a large number of convenience methods and shortcuts available to you as Console Utilities. Some of these are:
$_
stores the result of the last command. So if you were to type 2+2
and hit enter, typing $_
will give you 4.
$0
to $4
is a stack of the last inspected elements, where $0
is always the newest one.
$()
and $$()
are short for document.querySelector()
and document.querySelectorAll()
.
$x()
allows you to select DOM elements by XPATH.
copy()
copies whatever you give it to the clipboard.
clear()
clears the console.
getEventListeners(node)
lists all the event listeners of a node.
monitorEvents(node, events)
monitors and logs the events that happen on a node.
monitor(method)
creates a log item whenever a method is called.
Some of these methods are incredibly powerful and are things we probably have written ourselves as a series of console.log()
statements in the past.
Here are some ways you can use these methods:
monitorEvents(window, ['resize', 'scroll']);
monitorEvents($0, 'key');
This logs every time the window scrolls or gets resized. The second example is interesting, as it logs any key action on the currently selected element:
console.table($$('a'),['href','text'])
This retrieves all the links in the document (as $$('a')
is short for document.querySelectorAll('a')
) and displays them as a sortable table. The array as a second parameter of the table
method defines the columns of the table. Otherwise, each property of the link would become a column and that’s hard to navigate. The fun bit is that the table is not only sortable, but you can copy and paste it — for example, into Excel.
Instead of writing complex JavaScript to filter these results, you can use the power of CSS selectors. For example, if you want to have a table of the src
and alt
information of all images in the document that aren’t inline images, you can use the following:
console.table($$('img:not([src^=data])'), ['src','alt'])
However, the most fun with this is to write scripts that run in the context of the page.
For example, when you use Markdown to generate HTML, most page generators will create automatic IDs on headings to allow for deep linking to that part of the document. A # New Stuff
heading will turn into <h1 id="new-stuff">New stuff</h1>
. I needed to batch-create a lot of short URLs pointing to these deep links and didn’t want to create them by hand.
So I started to write a script for the Console to do that for me:
let out = '';
$$('#main [id]').filter(
elm => {return elm.nodeName.startsWith('H')}
).forEach(elm => {
out += `${elm.innerText}
${document.location.href}#${elm.id}
`
});
copy(out);
The result is a block of text with the text content of each heading followed by the full URL pointing to it.
This also shows an interesting extra feature of the $$
shortcut. A document.querySelectorAll('#main [id]').filter()
would result in an error, as the returned value isn’t an Array
but a NodeList
. You’d need to cast it to an Array
with [...document.querySelectoAll('#main [id]').filter()]
or Array.from(document.querySelectoAll('#main [id]').filter())
, which has been an annoyance for people coming from jQuery to JavaScript for quite a while. The $$
convenience method allows for all the Array methods directly.
Generally, you have a lot of power to access and change anything on the rendered page in the browser from the Console. And you also have the added benefit of using the Elements tab of the developer tools to get the correct path. Activate the ...
menu next to each node and select from the context menu’s copy menu to grab what you need.
Moving from Console to Sources
Once you’ve found some fun ways to manipulate web sites, you’ll soon run into the limitations of the Console as a script environment. It can be a bit tough to code on the Console, as you’re working in a single-line environment. Hitting Enter immediately executes what you typed and often you do that by accident. There’s a trick, though. You can write multi-line scripts by using Shift + Enter instead.
In general, the Console makes for a great testing environment but for a poor editing experience. Luckily enough, there’s also a full editor available in the Sources panel. There, you can inspect the code of the current page and write more complex scripts to interact with it.
Continue reading
Beyond Console.log() – Level up Your Debugging Skills
on SitePoint.