Kiến thức cho phỏng vấn Javascript developer

January 2, 2018 (6y ago)

JavaScript is the most popular programming language and has been since 2014, according to Stack Overflow 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.

Undefined is the absence of a definition. 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’.

Take a look at the following example:

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.

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

The first 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.

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.

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

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 final 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:

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.

To copy a compound value by value, you need to makea copy of it. The reference does not point to the original value.

Scope

Scope refers to the execution context. It defines 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.

You may think of Scopes as a series of doors decreasing in size (from biggest to smallest). A short person that fits through the smallest door — innermost scope — also fits 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 defined.

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

let and const are not hoisted.

Function Expression vs. Function Declaration

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.

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 define a scope in which variables declared with either let or const are confined in.

A common misconception is that const is immutable. It cannot be reassigned, but its properties can be changed!

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.

The above example covers the two things you need to know about closures:

  1. Refers to variables in outer scope. The returned function access themessage 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 benefits 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.

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 fix it?

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.

Another solution would be declaring the i variable with let, which creates the same result.

Immediate Invoked Function Expression (IIFE)

An IIFE is a function expression that is called immediately after you define 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 final parenthesis() are executing the function expression.

On IIFE you are calling the function exactly when you are defining it.

Using IIFE:

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 thisinto function and the difference 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.

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.

'this' keyword

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

The value of thisis usually determined by a functions execution context. Execution context simply means how a function is called.

The keyword thisacts 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 first one that applies:

Note: This also happens when a standalone function is called from within an outer function scope.

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 benefits are:

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 prototypeto be the prototypeof 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.

What is the difference between invoking a function with the new keyword and without it?

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 different contexts in which the word prototype is used.

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 first 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 first one it finds.

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 defined on the object.

Inherited properties were inherited through prototype chain.

Object.create(obj**) **— Creates a new object with the specified prototypeobject and properties.

Inheritance by reference

An inherited property is a copy by reference of the prototype object’sproperty 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.

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: inflexible 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 different kinds of prototypal inheritance:

You can find 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 flexible and reusable.

Here is a nice video on this topic by Mattias Petter Johansson

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 first one removed from it, and the first 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 specific 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 first 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. 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 timebefore moving that function into the Callback Queue. Its execution will only take place on a future ‘tick’ in the Event Loop.