Sie sind auf Seite 1von 34

The De nitive JavaScript Handbook for a

developer interview

JavaScript is the most popular programming language and has been


since 2014, according to Stack Over ow Survey. It is no wonder that
over 1/3rd of all developer jobs require some JavaScript knowledge. So,
if you plan to work as a developer in the near future, you should be
familiar with this extremely popular language.

The post’s purpose is to bring together all JavaScript concepts that are
frequently brought up in developer interviews. It was written so you
can review everything you need to know about JavaScript in a single
place.

Types & Coercion


There are 7 built-in types: null , undefined  , boolean , number ,
string , object and symbol (ES6).

All of these are types are called primitives, except for object .
1 typeof 0 // number
2 typeof true // boolean
3 typeof 'Hello' // string
4 typeof Math // object
5 typeof null // object !!

• Null vs. Unde ned

Unde ned is the absence of a de nition. It is used as the default value


for uninitialized variables, function arguments that were not provided
and missing properties of objects. Functions return undefined when
nothing has been explicitly returned.

Null is the absence of a value. It is an assignment value that can be


assigned to a variable as a representation of ‘no-value’.

• Implicit coercion

Take a look at the following example:

1 var name = 'Joey';


2 if (name) {
3 console.log(name + " doesn't share food!") // Joey d
4 }

In this case, the string variable name is coerced to true and you have
‘Joey doesn’t share food!’ printed in our console. But how do you know
what will be coerced to true and what will be coerced to false?
Falsy values are values that will be coerced to false when forced a
boolean coercion on it.

Falsy values: "" , 0 , null , undefined , NaN , false .

Anything not explicitly on the falsy list is truthy — boolean coerced to


true.

1 Boolean(null) // false
2 Boolean('hello') // true
3 Boolean('0') // true
4 Boolean(' ') // true
5 Boolean([]) // true

Yes. You read it right. Empty arrays, objects and functions are boolean
coerced to true!

• String & Number coercion

The rst thing you need to be aware of is the + operator. This is a


tricky operator because it works for both number addition and string
concatenation.

But, the *, / , and - operators are exclusive for numeric operations.
When these operators are used with a string, it forces the string to be
coerced to a number.
1 1 + "2" = "12"
2 "" + 1 + 0 = "10"
3 "" - 1 + 0 = -1
4 "-9\n" + 5 = "-9\n5"
5 "-9\n" - 5 = -14
6 "2" * "3" = 6
7 4 + 5 + "px" = "9px"
8 "$" + 4 + 5 = "$45"
9 "4" 2 = 2

• == vs. ===

It is widely spread that == checks for equality and === checks for
equality and type. Well, that is a misconception.

In fact, == checks for equality with coercion and === checks for
equality without coercion — strict equality.

1 2 == '2' // True
2 2 === '2' // False
3 undefined == null // True
4 undefined === null // False

Coercion can be tricky. Take a look at the following code:

1 let a = '0';
2 console.log(Boolean(a)); // True
3 let b = false;
4 console log(Boolean(b)); // False
What would you expect for the following comparison?
console.log(a == b); (1)

This comparison actually returns True. Why?


What really happens under the hood is that if you are comparing a
boolean with something other than a boolean , JavaScript coerces
that boolean to a number and compares. (2)

This comparison is now between a number and a string . JavaScript


now coerces that string to a number and compares both numbers.
(3)

In this case, the nal comparison 0 == 0 is True.

'0' == false (1)


'0' == 0 (2)
0 == 0 (3)

For a fully comprehension on how such comparisons are performed,


you can check ES5 documentation here.

For a cheat sheet, you can click here.

Some tricky comparisons to look out for:


1 false == "" // true
2 false == [] // true
3 false == {} // false
4 "" == 0 // true
5 "" == [] // true
6 "" == {} // false
7 0 == [] // true

Value vs. Reference


Simple values (also known as primitives) are always assigned by value-
copy: null , undefined  , boolean , number , string and ES6
symbol .

Compound values always create a copy of the reference on assignment:


objects, which includes arrays, and functions.

1 var a = 2; // 'a' hold a copy of the value 2.


2 var b = a; // 'b' is always a copy of the value
3 b++;
4 console.log(a); // 2
5 console.log(b); // 3
6 var c = [1,2,3];
7 var d = c; // 'd' is a reference to the shared
8 d.push( 4 ); // Mutates the referenced value (obj
9 console.log(c); // [1,2,3,4]
10 console.log(d); // [1,2,3,4]
To copy a compound value by value, you need to make a copy of it. The
reference does not point to the original value.

1 const copy = c.slice() // 'copy' references to a new


2 console.log(c); // [1,2,3,4]
3 console.log(copy); // [1,2,3,4]
4 console log(c === copy); // false

Scope
Scope refers to the execution context. It de nes the accessibility of
variables and functions in the code.

Global Scope is the outermost scope. Variables declared outside a


function are in the global scope and can be accessed in any other scope.
In a browser, the window object is the global scope.

Local Scope is a scope nested inside another function scope. Variables


declared in a local scope are accessible within this scope as well as in
any inner scopes.
1 function outer() {
2 let a = 1;
3 function inner() {
4 let b = 2;
5 function innermost() {
6 let c = 3;
7 console.log(a, b, c); // 1 2 3
8 }
9 innermost();
10 console.log(a, b); // 1 2 — 'c' is not defi
11 }

You may think of Scopes as a series of doors decreasing in size (from


biggest to smallest). A short person that ts through the smallest door
— innermost scope — also ts through any bigger doors — outer
scopes.

A tall person that gets stuck on the third door, for example, will have
access to all previous doors — outer scopes — but not any further doors
— inner scopes.

Hoisting
The behavior of “moving” var and function declarations to the top
of their respective scopes during the compilation phase is called
hoisting.

Function declarations are completely hoisted. This means that a


declared function can be called before it is de ned.
1 console.log(toSquare(3)); // 9
2
3 function toSquare(n){
4 return n*n;

Variables are partially hoisted. var declarations are hoisted but not its
assignments.

let and const are not hoisted.

1 { /* Original code */
2 console.log(i); // undefined
3 var i = 10
4 console.log(i); // 10
5 }
6
7 { /* Compilation phase */
8 var i;
9 console.log(i); // undefined
10 i = 10
11 console.log(i); // 10
12 }
13 // ES6 let & const
14 {
15 console.log(i); // ReferenceError: i is not defined
16 t i 10
Function Expression vs. Function
Declaration
• Function Expression
A Function Expression is created when the execution reaches it
and is usable from then on — it is not hoisted.

1 var sum = function(a, b) {


2 return a + b;
3 }

• Function Declaration
A Function Declaration can be called both before and after it was
de ned — it is hoisted.

1 function sum(a, b) {
2 return a + b;
3 }

Variables: var, let and const


Before ES6, it was only possible to declare a variable using var .
Variables and functions declared inside another function cannot be
accessed by any of the enclosing scopes — they are function-scoped.

Variables declared inside a block-scope, such as if statements and


for loops, can be accessed from outside of the opening and closing
curly braces of the block.

Note: An undeclared variable — assignment without var , let or


const — creates a var variable in global scope.

1 function greeting() {
2 console.log(s) // undefined
3 if(true) {
4 var s = 'Hi';
5 undeclaredVar = 'I am automatically created in glo
6 }
7 console.log(s) // 'Hi'
8 }

ES6 let and const are new. They are not hoisted and block-scoped
alternatives for variable declaration. This means that a pair of curly
braces de ne a scope in which variables declared with either let or
const are con ned in.

1 let g1 = 'global 1'


2 let g2 = 'global 2'
3 { /* Creating a new block scope */
4 g1 = 'new global 1'
5 let g2 = 'local global 2'
6 console.log(g1) // 'new global 1'
7 console.log(g2) // 'local global 2'
8 console.log(g3) // ReferenceError: g3 is not defin
9 let g3 = 'I am not hoisted';
A common misconception is that const is immutable. It cannot be
reassigned, but its properties can be changed!

1 const tryMe = 'initial assignment';


2 tryMe = 'this has been reassigned'; // TypeError: Ass
3 // You cannot reassign but you can change it…
4 const array = ['Ted', 'is', 'awesome!'];
5 array[0] = 'Barney';
6 array[3] = 'Suit up!';
7 console.log(array); // [“Barney”, “is”, “awesome!”
8 const airplane = {};

Closure
A closure is the combination of a function and the lexical environment
from which it was declared. Closure allows a function to access
variables from an enclosing scope — environment — even after it leaves
the scope in which it was declared.

1 function sayHi(name){
2 var message = `Hi ${name}!`;
3 function greeting() {
4 console.log(message)
5 }
6 return greeting
7 }
The above example covers the two things you need to know about
closures:

1. Refers to variables in outer scope.


The returned function access the message variable from the
enclosing scope.

2. It can refer to outer scope variables even after the outer function
has returned. 
sayHiToJon is a reference to the greeting function, created
when sayHi was run. The greeting function maintains a
reference to its outer scope — environment — in which message

exists.

One of the main bene ts of closures is that it allows data


encapsulation. This refers to the idea that some data should not be
directly exposed. The following example illustrates that.

By the time elementary is created, the outer function has already


returned. This means that the staff variable only exists inside the
closure and it cannot be accessed otherwise.
1 function SpringfieldSchool() {
2 let staff = ['Seymour Skinner', 'Edna Krabappel'];
3 return {
4 getStaff: function() { console.log(staff) },
5 addStaff: function(name) { staff.push(name) }
6 }
7 }
8
9 let elementary = SpringfieldSchool()
10 console.log(elementary) // { getStaff: ƒ, addSt
11 l l ( t ff) // R f E t f

Let’s go deeper into closures by solving one of the most common


interview problems on this subject:
What is wrong with the following code and how would you x it?

1 const arr = [10, 12, 15, 21];


2 for (var i = 0; i < arr.length; i++) {
3 setTimeout(function() {
4 console.log(`The value ${arr[i]} is at index: ${i}`
5 }, (i+1) * 1000);

Considering the above code, the console will display four identical
messages "The value undefined is at index: 4" . This happens
because each function executed within the loop will be executed after
the whole loop has completed, referencing to the last value stored in
i , which was 4.
This problem can be solved by using IIFE, which creates a unique scope
for each iteration and storing each value within its scope.

1 const arr = [10, 12, 15, 21];


2 for (var i = 0; i < arr.length; i++) {
3 (function(j) {
4 setTimeout(function() {
5 console.log(`The value ${arr[j]} is at index: ${j
6 }, j * 1000);

Another solution would be declaring the i variable with let , which


creates the same result.

1 const arr = [10, 12, 15, 21];


2 for (let i = 0; i < arr.length; i++) {
3 setTimeout(function() {
4 console.log(`The value ${arr[i]} is at index: ${i}`
5 }, (i) * 1000);

Immediate Invoked Function Expression


(IIFE)
An IIFE is a function expression that is called immediately after you
de ne it. It is usually used when you want to create a new variable
scope.
The (surrounding parenthesis) prevents from treating it as a function
declaration.

The nal parenthesis() are executing the function expression.

On IIFE you are calling the function exactly when you are de ning it.

1 var result = [];


2 for (var i=0; i < 5; i++) {
3 result.push( function() { return i } );
4 }
5 console.log( result[1]() ); // 5
6 console.log( result[3]() ); // 5
7 result = [];
8 for (var i=0; i < 5; i++) {
9 (function () {
10 var j = i; // copy current value of i
11 lt h( f ti () { t j } )

Using IIFE:

• Enables you to attach private data to a function.

• Creates fresh environments.

• Avoids polluting the global namespace.

Context
Context is often confused as the same thing as Scope. To clear things
up, lets keep the following in mind:
Context is most often determined by how a function is invoked. It
always refers to the value of this in a particular part of your code.
Scope refers to the visibility of variables.

Function calls: call, apply and bind


All of these three methods are used to attach this into function and
the di erence is in the function invocation.

.call() invokes the function immediately and requires you to pass in


arguments as a list (one by one).

.apply() invokes the function immediately and allows you to pass in


arguments as an array.

.call() and  .apply() are mostly equivalent and are used to borrow
a method from an object. Choosing which one to use depends on which
one is easier to pass the arguments in. Just decide whether it’s easier to
pass in an array or a comma separated list of arguments.

Quick tip: Apply for Array — Call for Comma.


1 const Snow = {surename: 'Snow'}
2 const char = {
3 surename: 'Stark',
4 knows: function(arg, name) {
5 console.log(`You know ${arg}, ${name} ${this.suren
6 }
7 }

Note: If you pass in an array as one of the arguments on a call function,


it will treat that entire array as a single element. 
ES6 allows us to spread an array as arguments with the call function.

char.knows.call(Snow, ...["nothing", "Jon"]); // You know


nothing, Jon Snow

.bind() returns a new function, with a certain context and


parameters. It is usually used when you want a function to be called
later with a certain context.

That is possible thanks to its ability to maintain a given context for


calling the original function. This is useful for asynchronous callbacks
and events.

.bind() works like the call function. It requires you to pass in the
arguments one by one separated by a comma.
1 const Snow = {surename: 'Snow'}
2 const char = {
3 surename: 'Stark',
4 knows: function(arg, name) {
5 console.log(`You know ${arg}, ${name} ${this.surena
6 }

'this' keyword
Understanding the keyword this in JavaScript, and what it is
referring to, can be quite complicated at times.

The value of this is usually determined by a functions execution


context. Execution context simply means how a function is called.

The keyword this acts as a placeholder, and will refer to whichever


object called that method when the method is actually used.

The following list is the ordered rules for determining this. Stop at the
rst one that applies:

• new binding — When using the new keyword to call a function,


this is the newly constructed object.
1 function Person(name, age) {
2 this.name = name;
3 this.age =age;
4 console.log(this);
5 }

• Explicit binding — When call or apply are used to call a function,


this is the object that is passed in as the argument.
Note:  .bind() works a little bit di erently. It creates a new
function that will call the original one with the object that was
bound to it.

1 function fn() {
2 console.log(this);
3 }
4 var agent = {id: '007'};
5 fn.call(agent); // { id: '007' }
6 fn.apply(agent); // { id: '007' }

• Implicit binding — When a function is called with a context (the


containing object), this is the object that the function is a
property of.
This means that a function is being called as a method.
1 var building = {
2 floors: 5,
3 printThis: function() {
4 console.log(this);
5 }
6 }

• Default binding — If none of the above rules applies, this is the


global object (in a browser, it’s the window object).
This happens when a function is called as a standalone function.
A function that is not declared as a method automatically becomes
a property of the global object.

1 function printWindow() {
2 console.log(this)
3 }
4 printWindow(); // window object

Note: This also happens when a standalone function is called from


within an outer function scope.
1 function Dinosaur(name) {
2 this.name = name;
3 var self = this;
4 inner();
5 function inner() {
6 alert(this); // window object — the functio
7 console.log(self); // {name: 'Dino'} — referencin
8 }

• Lexical this — When a function is called with an arrow function


=>, this receives the this value of its surrounding scope at
the time it’s created.
this keeps the value from its original context.

1 function Cat(name) {
2 this.name = name;
3 console.log(this); // { name: 'Garfield' }
4 ( () => console.log(this) )(); // { name: 'Garfield
5 }

Strict Mode
JavaScript is executed in strict mode by using the “use strict”

directive. Strict mode tightens the rules for parsing and error handling
on your code.

Some of its bene ts are:


• Makes debugging easier — Code errors that would otherwise
have been ignored will now generate errors, such as assigning to
non-writable global or property.

• Prevents accidental global variables — Assigning a value to an


undeclared variable will now throw an error.

• Prevents invalid use of delete — Attempts to delete variables,


functions and undeletable properties will now throw an error.

• Prevents duplicate property names or parameter values —


Duplicated named property in an object or argument in a function
will now throw an error.

• Makes eval() safer — Variables and functions declared inside an


eval() statement are not created in the surrounding scope.

• “Secures” JavaScript eliminating this coercion — Referencing a


this value of null or unde ned is not coerced to the global
object. This means that in browsers it’s no longer possible to
reference the window object using this inside a function.

`new` keyword
The new keyword invokes a function in a special way. Functions
invoked using the new keyword are called constructor functions.

So what does the new keyword actually do?

1. Creates a new object.


2. Sets the object’s prototype to be the prototype of the constructor
function.

3. Executes the constructor function with this as the newly created


object.

4. Returns the created object. If the constructor returns an object,


this object is returned.

1 // In order to better understand what happens under the


2 function myNew(constructor) {
3 var obj = {}
4 Object.setPrototypeOf(obj, constructor.prototype);
5 return constructor.apply(obj, [...arguments].slice(1)

What is the di erence between invoking a function with the new

keyword and without it?

1 function Bird() {
2 this.wings = 2;
3 }
4 /* invoking as a normal function */
5 let fakeBird = Bird();
6 console.log(fakeBird); // undefined
7 /* invoking as a constructor function */

Prototype and Inheritance


Prototype is one of the most confusing concepts in JavaScript and one
of the reason for that is because there are two di erent contexts in
which the word prototype is used.

• Prototype relationship
Each object has a prototype object, from which it inherits all of its
prototype’s properties.
.__proto__ is a non-standard mechanism (available in ES6) for
retrieving the prototype of an object (*). It points to the object’s
“parent” — the object’s prototype. 
All normal objects also inherit a  .constructor property that
points to the constructor of the object. Whenever an object is
created from a constructor function, the  .__proto__ property
links that object to the  .prototype property of the constructor
function used to create it.
(*) Object.getPrototypeOf() is the standard ES5 function for
retrieving the prototype of an object.

• Prototype property 
Every function has a  .prototype property. 
It references to an object used to attach properties that will be
inherited by objects further down the prototype chain. This object
contains, by default, a  .constructor property that points to the
original constructor function. 
Every object created with a constructor function inherits a
constructor property that points back to that function.
1 function Dog(breed, name){
2 this.breed = breed,
3 this.name = name
4 }
5 Dog.prototype.describe = function() {
6 console.log(`${this.name} is a ${this.breed}`)
7 }
8 const rusty = new Dog('Beagle', 'Rusty');
9
10 /* .prototype property points to an object which has c
11 properties to be inherited by objects created by this
12 console.log(Dog.prototype) // { describe: ƒ , constru
13
14 /* Object created from Dog constructor function */

Prototype Chain
The prototype chain is a series of links between objects that reference
one another.

When looking for a property in an object, JavaScript engine will rst try
to access that property on the object itself.

If it is not found, the JavaScript engine will look for that property on
the object it inherited its properties from — the object’s prototype.

The engine will traverse up the chain looking for that property and
return the rst one it nds.
The last object in the chain is the built-in Object.prototype , which has
null as its prototype. Once the engine reaches this object, it returns
undefined .

Own vs Inherited Properties


Objects have own properties and inherited properties.

Own properties are properties that were de ned on the object.

Inherited properties were inherited through prototype chain. Inherited


properties are not enumerable (not accessible in a for / in loop).

1 function Car() { }
2 Car.prototype.wheels = 4;
3 Car.prototype.airbags = 1;
4
5 var myCar = new Car();
6 myCar.color = 'black';
7
8 /* Check for Property including Prototype Chain: */
9 console.log('airbags' in myCar) // true
10 console.log(myCar.wheels) // 4
11 l l ( C ) // d fi d

Object.create(obj) — Creates a new object with the speci ed prototype


object and properties.
1 var dog = { legs: 4 };
2 var myDog = Object.create(dog);
3
4 console.log(myDog.hasOwnProperty('legs')) // false
5 console.log(myDog.legs) // 4

Inheritance by reference
An inherited property is a copy by reference of the prototype object’s
property from which it inherited that property.

If an object’s property is mutated on the prototype, objects which


inherited that property will share the same mutation. But if the
property is replaced, the change will not be shared.

1 var objProt = { text: 'original' };


2 var objAttachedToProt = Object.create(objProt);
3 console.log(objAttachedToProt.text) // original
4
5 objProt.text = 'prototype property changed';
6 console.log(objAttachedToProt.text) // prototype prop
7

Classical Inheritance vs. Prototypal Inheritance


In classical inheritance, objects inherit from classes — like a blueprint or
a description of the object to be created — and create sub-class
relationships. These objects are created via constructor functions using
the new keyword.
The downside of classical inheritance is that it causes:
in exible hierarchy
tight coupling problems
fragile base class problems
duplication problems
And the so famous gorilla/banana problem — “What you wanted was a
banana, what you got was a gorilla holding the banana, and the entire
jungle.”

In prototypal inheritance, objects inherit directly from other objects.


Objects are typically created via Object.create() , object literals or
factory functions.

There are three di erent kinds of prototypal inheritance:

• Prototype delegation — A delegate prototype is an object which is


used as a model for another object. When you inherit from a
delegate prototype, the new object gets a reference to the
prototype and its properties. 
This process is usually accomplished by using Object.create() .

• Concatenative inheritance — The process of inheriting properties


from one object to another by copying the object’s prototype
properties, without retaining a reference between them. 
This process is usually accomplished by using Object.assign() .

• Functional inheritance — This process makes use of a factory


function(*) to create an object, and then adds new properties
directly to the created object. 
This process has the bene t of allowing data encapsulation via
closure.
(*)Factory function is a function that is not a class or constructor
that returns an object without using the new keyword.

1 const person = function(name) {


2 const message = `Hello! My name is ${name}`;
3 return { greeting: () => console.log(message) }
4 }
5 const will = person("Will");

You can nd a complete article on this topic by Eric Elliott here.

Favor composition over class inheritance


Many developers agree that class inheritance should be avoided in most
cases. In this pattern you design your types regarding what they are,
which makes it a very strict pattern.

Composition, on the other hand, you design your types regarding what
they do, which makes it more exible and reusable.

Asynchronous JavaScript
JavaScript is a single-threaded programming language. This means
that the JavaScript engine can only process a piece of code at a time.
One of its main consequences is that when JavaScript encounters a
piece of code that takes a long time to process, it will block all code
after that from running.
JavaScript uses a data structure that stores information about active
functions named Call Stack. A Call Stack is like a pile of books. Every
book that goes into that pile sits on top of the previous book. The last
book to go into the pile will be the rst one removed from it, and the
rst book added to the pile will be the last one removed.

The solution to executing heavy pieces of code without blocking


anything is asynchronous callback functions. These functions are
executed later — asynchronously.

The asynchronous process begins with an asynchronous callback


functions placed into a Heap or region of memory. You can think of the
Heap as an Event Manager. The Call Stack asks the Event Manager to
execute a speci c function only when a certain event happens. Once
that event happens, the Event Manager moves the function to the
Callback Queue. Note: When the Event Manager handles a function,
the code after that is not blocked and JavaScript continues its
execution.

The Event Loop handles the execution of multiple pieces of your code
over time. The Event Loop monitors the Call Stack and the Callback
Queue.

The Call Stack is constantly checked whether it is empty or not. When it


is empty, the Callback Queue is checked if there is a function waiting to
be invoked. When there is a function waiting, the rst function in the
queue is pushed into the Call Stack, which will run it. This checking
process is called a ‘tick’ in the Event Loop.
Let’s break down the execution of the following code to understand
how this process works:

1 const first = function () {


2 console.log('First message')
3 }
4 const second = function () {
5 console.log('Second message')
6 }
7 const third = function() {
8 console.log('Third message')
9 }
10
11 first();
12 setTimeout(second, 0);

1. Initially the Browser console is clear and the Call Stack and Event
Manager are empty.

2. first() is added to the Call Stack.

3. console.log("First message") is added to the Call Stack.

4. console.log("First message") is executed and the Browser


console displays “First message”.

5. console.log("First message") is removed from the Call Stack.

6. first() is remove from the Call Stack.


7. setTimeout(second, 0) is added to the Call Stack.

8. setTimeout(second, 0) is executed and handled by the Event


Manager. And after 0ms the Event Manager moves second() to
the Callback Queue.

9. setTimeout(second, 0) is now completed and removed from the


Call Stack.

10. third() is added to the Call Stack.

11. console.log("Third message") is added to the Call Stack.

12. console.log("Third message") is executed and the Browser


console displays “Third message”.

13. console.log("Third message") is removed from the Call Stack.

14. Call Stack is now empty and the second() function is waiting to
be invoked in the Callback Queue.

15. The Event Loop moves second() from the Callback Queue to the
Call Stack.

16. console.log("Second message") is added to the Call Stack.

17. console.log("Second message") is executed and the Browser


console displays “Second message”.

18. console.log("Second message") is removed from the Call Stack.

19. second() is removed from the Call Stack.


Note: The second() function is not executed after 0ms. The time you
pass in to setTimeout function does not relate to the delay of its
execution. The Event Manager will wait the given time before moving
that function into the Callback Queue. Its execution will only take place
on a future ‘tick’ in the Event Loop.

Thanks and congratulations for reading up to this point! If you have


any thoughts on this, feel free to leave a comment.

You can follow me on Twitter.

Das könnte Ihnen auch gefallen