Previously: part 2 - html — part 3 - css
In this post I’m going to work through some of the JS questions posed in the Front-end Interview Questions repo.
Q. Explain event delegation. Describe event bubbling.
-
Bubbling
Javascript events occur on the element with which a user interacts, and then they progress up that element's ancestors until the
document
is reached. This bubbling action is what makes event delegation possible. Bubbling is the default behavior for events, however you can stop an event from moving up its ancestors by usingevent.stopPropagation()
.1 2 3 4 5 6 7 8
<div class="example-1"> <span class="grandpa"> <span class="pops"> <button class="btn btn-success btn-sm">please to click</button> </span> </span><br /> <textarea rows="4" cols="50"></textarea> </div>
1 2 3 4 5 6 7 8 9 10
(function(){ function handler( evt ) { var el = document.querySelector( '.example-1 textarea' ); el.innerHTML += this.nodeName + ' class="' + this.className + '"' + '\n'; } var els = document.querySelectorAll( '.example-1 button, .example-1 div' ); for( var i = 0; i < els.length; i++ ){ els[ i ].addEventListener( 'click', handler ); } })();
Output:
Using
stopPropagation()
, we can stop the event from traveling up to its ancestors. The only difference I've added here is a check to see who the target of the event is:1 2 3
if ( this.nodeName == 'BUTTON' ) { evt.stopPropagation(); }
Event delegation
Delegation relies upon bubbling and is an efficient way of handling events on many elements, without having to listen to each one.
1 2 3 4 5 6 7
<ul class="example-3"> <li><span>I'm text in a <code>span</code></span></li> <li>And I'm just in a <code>li</code></li> <li><button class="btn btn-success btn-sm">I have a pretty lame <code>button</code> el</button></li> <li><p>I'm text in a <code>p</code></p></li> </ul>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
(function() { // listen to the parent list var el = document.querySelector( '.example-3' ), cls = 'bg-info', prev; function handler( evt ) { // since there are children, we only want to work // with the nearest parent LI var target = evt.target.closest( 'li' ); if ( !target | prev == target ) { return; } if ( prev ) { prev.classList.remove( cls ); } target.classList.add( cls ); prev = target; } el.addEventListener( 'click', handler ); })();
Give it a try by clicking the LIs below:
- I'm text in a
span
- And I'm just in a
li
I'm text in a
p
Using the add button, we can dynamically add new items to the list without having to rebind the event handler. This makes debugging easier, your pages more performant, and can help prevent nasty memory issues down the road.
- I'm text in a
Q. Explain how
this
works in JavaScript-
The value of
this
depends on the context in which it's referenced. In a global context, outside of a function or another object, it refers to thewindow
object.Inside of a function when no
bind
,call
, orapply
has been used to invoke the function,this
will also refer to thewindow
object, unless you're operating in strict mode, in which case it will returnundefined
.When a function is invoked using
call
, orapply
, the object that's passed in as the first argument becomes thethis
context of that function:1 2 3 4 5 6 7 8
function blurt() { return this.a; } blurt(); // undefined, unless it was previously in the window object blurt.call( { a: 99 } ); // 99 blurt.apply( { a: 'hi mom!' } ); // hi mom! blurt(); // still undefined
When
this
is referenced in a function that is a member of an object, the context takes that of the object:1 2 3 4 5 6 7 8 9
var obj = { a: 'TIMMEH!', b: function() { return this.a; } }; obj.a; //'TIMMEH!' obj.b(); //'TIMMEH!' obj.a === obj.b(); // true
In event handlers,
this
will refer to the element from which the event fired.1 2 3
<p id="dum">I'm a paragraph with a <span class="text-danger">span</span> tag and a <button class="btn btn-success btn-sm">button</button>. You should click somewhere in here.</p>
1 2 3 4 5 6 7 8 9
(function() { var el = document.getElementById( 'dum' ), out = document.getElementById( 'this-out' ); el.addEventListener( 'click', function( evt ) { out.innerHTML = 'this.nodeName = ' + this.nodeName + '\n'; out.innerHTML += 'evt.target.nodeName = ' + evt.target.nodeName + '\n'; out.innerHTML += 'evt.currentTarget.nodeName = ' + evt.currentTarget.nodeName; }) })();
I'm a paragraph with a span tag and a . You should click somewhere in here.
Q. What do you think of AMD vs CommonJS?
-
I'm likely going to show my age, and confess that I feel about them the same way I do transpilers, the labyrinth of build systems required to do pretty much anything anymore, and the plethora of redundant libraries that are now available. That is, I find them unnecessarily complex, overly niche, and typically overkill when seen from the perspective of my previous development experience. Despite that, my obstinateness can take a backseat because I understand that there are valid applications of this technology.
I have very little practical experience with either technology, so explanations here are all derived from my research. CommonJS was originally intended for applications outside the browser, one notable implementation was in NodeJS. AMD was written to target the async capabilities of the browser, allowing an author to define dependencies, and providing a fairly simple and common API (to the developer) to create modules that operate on fairly discrete pieces of app functionality.
I think my criticism of these technologies lies in that it requires yet another layer of, not really abstraction, but domain knowledge in order to get them to work. Previously, we just had to make sure that in our concatenated site-wide JS, things were loaded in order: base libraries first that are used across the entire organization, and then app-specific code. Your app code could be written in a common pattern that didn't require additional knowledge of your loader.
And despite that, I see the benefits of using a loader. You have async loading, explicit definitions of dependencies, and a fairly obvious idea at a glance of what your code is doing since it's supposed to be small and have a targeted purpose. This is definitely an area where I need to play with the technology to get a greater familiarity with it.
Q. Explain why the following doesn't work as an IIFE:
function foo(){ }();
What needs to be changed to properly make it an IIFE?-
The reason it doesn't work is that it's syntactically incorrect. When cleaned up a little, it looks like:
1 2 3 4 5
// function declaration function foo() { // nothing in here } (); // do...what!?
When the parser hits that empty pair of parentheses, there's nothing there for it to execute. To make this a valid IIFE, you'd have to add some parens:
1 2 3
(function foo() { console.log( 'I executed all by myself!' ); })();
Note the order of the parens in the corrected version,
(function foo() {})();
. This can also be written as(function foo() {}());
. While this difference isn't typically an issue, it can be when you have IIFEs in minified code that rely upon parameters being passed in. If you're missing a semicolon, then the value ofundefined
can be passed in as a parameter, leading to run-time errors or unintended values being passed in. The short answer to this discussion though is just use your damn semicolons (and curlies, and parens, and anything else that removes ambiguity from your code). Q. What's the difference between a variable that is:
null
,undefined
or undeclared? How do you go about checking for these states?-
Let's start with undeclared
JS doesn't have an undeclared type. That is, if you're attempting to access a variable that hasn't been declared, you'll get a reference error:
1 2 3 4 5 6
if ( duh ) { // reference error, stop execution console.log( true ); } else { console.log( false ); }
undefined
variablesundefined
is a primitive type that is used for variables that have been declared, but not assigned a value, or variables that have been explicitly givenundefined
as their value:1 2 3 4 5
var duh; // declared, not given a value console.log( duh ); // undefined console.log( typeof duh === 'undefined' ); // true duh = undefined; console.log( typeof duh === 'undefined' ); // still true
null
variablesYou can think of
null
as the explicit absence of value, rather than an implicit absence. A developer might usenull
to indicate that an object is no longer needed by the application and can be marked for garbage collection. Or he might use it when querying a database, where the user may expect an array but instead has an object whose value isnull
, meaning there were no records returned.How do you check for these states?
null
has some neat quirks about it:1 2 3 4 5 6 7 8
var duh = null; duh; // null typeof duh; // "object" duh == undefined; // true duh === undefined; // false duh == null; // true duh === null; // true !duh; // true
So the best bet when dealing with
null
is to explicitly check for it using===
, so no type coercion takes place.As for
undefined
and undeclared variables, it's easiest to stick with usingtypeof
. In the event a variable has not been declared, it will not throw an error, allowing you to continue your app's execution. Q. What is a closure, and how/why would you use one?
-
Closures are often used to create a public/private interface for objects. Let's use the oft-cited example of counting the number of times a button was clicked.
1 2 3 4
<div> <button id="btn">CLICK ME!</button> <p id="count">You've clicked the button 0 times.</p> </div>
1 2 3 4 5 6 7 8 9
var button = document.getElementById( 'btn' ), count = 0; function update() { var msg = document.getElementById( 'count' ); msg.innerHTML = msg.innerHTML.replace( /\d+/g, ++count ); } button.addEventListener( 'click', update );
You've clicked the button 0 times.
There are some obvious problems with this script. First, it's relying on global variables and polluting our namespace. Second, it's a bit inefficient in that it's grabbing the
innerHTML
for our output message each time the function runs. An IIFE could solve those two problems easily, but there's another problem. The interesting and important parts of the function (our count and message) are easily edited by anyone who looks at the variable names. If we can protect those from the global namespace and only expose a simple public method to update the count, we'll be golden. This is where a closure comes in play. This example is pretty contrived as it could've all been easily contained within our IIFE (I'm still using globals here), but it does illustrate a public interface editing private vars.1 2 3 4 5
<div> <button id="btn2">CLICK ME TOO!</button> <button id="reset">reset</button> <p id="count2">You've clicked the button 0 times.</p> </div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
// The return value of the IIFE will be assigned to update var update = (function() { // private vars only edited within the scope of the IIFE var out = document.getElementById( 'count2' ), msg = out.innerHTML, count = 0; // handles all editing of private stuff function setMsg( ctr ) { count = ctr; out.innerHTML = msg.replace( /\d+/g, ctr ); } // our public interface. both rely upon a private // function to do all editing, simply passing in // values return { inc: function() { setMsg( count + 1 ); }, reset: function() { setMsg( 0 ); } }; })(); var button = document.getElementById( 'btn2' ), reset = document.getElementById( 'reset' ); button.addEventListener( 'click', update.inc ); reset.addEventListener( 'click', update.reset );
You've clicked the button 0 times.
Q. Can you describe the main difference between a
.forEach()
loop and a.map()
loop and why you would pick one versus the other?-
Similar to a
for
loop,.forEach()
is used to iterate over items in an array and execute a function of the value provided. After the execution of.forEach()
,undefined
is always returned, even if the original array is mutated.1 2 3 4
var arr = [0,1,2,3]; arr.forEach(function( val ) { console.log( val ); });
.map()
on the other hand always returns a new array with the items in the array modified by the callback provided:1 2 3 4 5
var arr = [0, 1, 2, 3]; // stupid note: this is my first fat arrow notation! var squares = arr.map( x => x ** 2 ); console.log( arr ); // [0, 1, 2, 3] console.log( squares ); // [0, 1, 4, 9]
As for which to use and when, use
.map()
when you want a new array of values derived from values in a source array, and use.forEach()
when you don't care what's returned from the callback, or use it if you prefer its syntax to afor
loop's. Q. What's a typical use case for anonymous functions?
-
Callbacks are probably the most-common use I've had for them in the past:
1 2 3 4 5 6 7 8 9 10 11 12 13
var arr = [0, 1, 2, 3]; // use an anonymous function as our callback to .map() var squares = arr.map( function( val ) { val = val ** 2; return val; }); // this could also be rewritten as: function square( val ) { val = val ** 2; return val; } var squares = arr.map( square );
In this case, moving
square()
to a named function would be somewhat useful, since it's a pretty generic piece of functionality and could be applied to areas outside that callback. But typically, callbacks are specific to that instance and don't really need a named or referenced function. I find anonymous functions also aid in readability, as you don't have to track down a function definition elsewhere in the code. Q. How do you organize your code? (module pattern, classical inheritance?)
-
I'm a big fan of the module pattern for its simplicity in understanding and implementation. I've previously used classical when at LinkedIn, but I much prefer working with modules. A pretty fantastic primer on modules is available from Ben Cherry, which despite being almost 8 years old, is still immensely useful.
Q. What's the difference between host objects and native objects?
-
Native objects are those defined by the ECMAScript engine: Object, Array, String, Function, etc. They exist regardless of where a program is running (e.g., server or browser).
Host objects are reliant upon the environment in which a program is running. In the browser, we have
window
,document
,history
. In an environment like NodeJS, we might havefs
to access the local filesystem, but no concept ofwindow
.
I’ll close this post for now. It’s gone on way too long, and there’s still a ton of questions to work through. More later!