As I am preparing for a technical job interview for a frontend engineer position, I have decided to brush up on my frontend domain knowledge by writing this article.
Agenda
- JavaScript Foundational Concepts
- ES5 vs. ES6
- TypeScript vs. JavaScript
- React Features
- Vue Features
- Recommended Resource
1. JavaScript Foundational Concepts:
1.1. DOM Methods
DOM method is an action or function that can be performed on elements within the Document Object Model (DOM) of a web page. The DOM represents the structure of an HTML document as a tree of objects, where each part of the document (like elements, attributes, text) is a node in this tree.
-
document.getElementsByTagName()
- This method returns a live
HTMLCollection
of all child elements with the specifiedtagName
. - The
tagName
argument should be a string representing the HTML tag name (e.g., "div", "p", "a", "img"). - It selects all elements that match the given tag name, regardless of their class or ID.
- This method returns a live
const divs = document.getElementsByTagName('div');
-
document.getElementsByClassName()
- This method returns a live
HTMLCollection
of all child elements that have all of the specifiedclassName
. - The
className
argument should be a string representing one or more class names, separated by spaces if multiple classes are targeted (e.g., 'my-class', 'main btn'). - It selects elements based on their assigned CSS classes.
- This method returns a live
// select all elements with the class 'highlight'
const highlightedElements = document.getElementsByClassName('highlight');
// select all elements with both 'card' and 'active' classes
const activeCards = document.getElementsByClassName('card active');
1.2. Array Methods
Array.prototype.**()
is a higher-order array method in JavaScript that provide functional ways to manipulate arrays.
-
Array.prototype.map()
creates a new array by applying a provided function to each element in the calling array. The new array will have the same length as the original array, but its elements will be the results of the callback function. It is used for transforming each element of an array.
const nums = [1, 2, 3];
const doubleNums = nums.map(num => num * 2); // return [2, 4, 6]
-
Array.prototype.filter()
creates a new array containing only the elements for which the provided callback function returns a truthy value. It is used for selecting a subset of elements from an array based on a condition. The new array's length may be less than or equal to the original array's length.
const nums = [1, 2, 3, 4, 5];
const oddNums = nums.filter(num => num % 2 !== 0); // return [1, 3, 5]
-
Array.prototype.reduce()
executes a reducer callback function on each element of the array, resulting in a single output value. This value can be a number, a string, an object, an array, or any other type. It is used for accumulating a single value from an array. It takes an optionalinitialValue
for the accumulator.
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((accumulator, current) => accumulator + current, 0); // return 15
Summary
-
map
is for transforming (one-to-one mapping of elements). -
filter
is for selection (creating a subset based on a condition). -
reduce
is for aggregation (reducing an array to a single value).
1.3. ==
vs. ===
==
(Abstract Equality): this operator compares values for equality after performing type coercion. If the two operands are different types, JavaScript will try to convert one or both operands to a common type before making the comparison.===
(Strict Equality): this operator compares both the value and the type of the operands. No type coercion is performed. For the comparison to be true, both the value and the data type must be identical.
For reliable and predictable code, it's a best practice to use the strict equality operator (===
) as it prevents unexpected behavior from implicit type conversions.
1.4. Bearer
Bearer
is used in the Authorization header to specify the type of token being sent in the Authentication header for authentication. It tells the server that the value following Bearer
is a bearer token, which is a security token granting access to protected resources.
How the Bearer
is beneficial
-
Standardized Authentication:
Bearer
tokens are widely used in APIs for secure, stateless authentication. - Simplicity: the client only needs to include the token; the server validates it without extra steps.
- Security: tokens can be short-lived and easily revoked, reducing risk if compromised.
Summary
Using Bearer
makes authentication secure, simple, and compatible with modern API standards.
2. ES5 vs. ES6:
ES6 introduced numerous new features and syntax enhancements aimed at making JavaScript development more efficient, readable, and maintainable compared to ES5.
2.1. Key Differences
-
Variable Declarations: ES5 primarily used 'var' for variable declarations, which have function-level scope. ES6 introduced
let
for block-scoped variables andconst
for block-scoped constant variables, improving scope management and preventing accidental reassignments.-
var
is the old way of declaring variables.var
declarations are function-scoped and can be re-declared and re-assigned. This can lead to unexpected behavior and is generally avoided in modern development. -
let
declarations are block-scoped. This means a variable declared withlet
is only accessible within the block of code (e.g., inside{}
) it was defined in. You can re-assign alet
variable, but you cannot re-declare it in the same scope. -
const
is for declaring variables that are block-scoped and cannot be re-assigned. The value must be initialized at the time of declaration. While the variables themselves can't be re-assigned, if the value is an object or array, its properties or elements can still be mutated.
-
Arrow Functions: ES6 introduced arrow functions, providing a more concise syntax for writing function expressions and handling
this
context more predictably. ES5 relied on traditionalfunction
keyword syntax.Classes: ES6 introduced the
class
keyword, offering a more familiar and structured syntax for defining object-oriented structures, similar to other object-oriented languages. ES5 simulated class-like behavior using constructor functions and prototypes.Promises: ES6 introduced
Promises
for handling asynchronous operations, providing a cleaner and more structured approach compared to the callback-based approach, often leading to "callback hell" in ES5.
2.2. Promise vs. async/await
Promises
Promises are objects that represent the result of asynchronous processing and its value, whether the process succeeded (resolved) or failed (rejected).
- Pending: this initial state; the operation has not yet completed.
- Fulfilled: the operation completed successfully.
- Rejected: the operation failed.
You chain .then()
and .catch()
methods to a Promise to handle the successful and failed outcomes, respectively.
fetch('https://api.example.com/data')
.then(res => {
if (!res.ok) {
throw new Error('Network response was not okay');
}
return res.json();
})
.then(data => console.log(data))
.catch(error => console.error('There was a problem with the fetch operation:', error));
-
Promise.all()
is used to wait for all promises to be completed. -
Promise.any()
is used to return a resolved (succeeded) value once any of the initial promises is resolved (succeeded).
async/await
The async/await is a modern JS feature that provides a cleaner, more readable way to work with Promises.
- The
async
is used to declare an asynchronous function. This function automatically returns a Promise. - The
await
is used inside anasync
function to pause the execution of the function until the Promise it's waiting on is settled (either fulfilled or rejected).
Error handling with async/await
is done using a traditional try...catch
block, which is familiar to developers from synchronous code.
async function fetchData() {
try {
const res = await fetch('https://api.example.com/data');
if (!res.ok) {
throw new Error('Network response was not okay');
}
const data = await res.json();
console.log(data);
} catch (error) {
console.error('There was a problem with the fetch operation:', error);
}
}
fetchData();
Summary
async/await
doesn't replace Promises; it provides a more intuitive and powerful way to use them. It's the preferred method for handling modern asynchronous JavaScript, as it makes the code much cleaner and easier to read.
2.3 Why do we need asynchronous functions?
We need asynchronous functions to perform operations that take time without blocking the main program threads. This is crucial for maintaining a responsive user experience, especially in applications that handle tasks like fetching data from a server, reading a file from the disk, or executing a long-running calculation.
Problem with synchronous code
In a synchronous programming mode, code is executed line by line. That is, when a function or operation is called, the program waits for it to complete before moving to the next line. If a long-running task is initiated, the entire program becomes unresponsive. For a web browser, this means the user interface freezes—the user can't click buttons, type in a field, or see animations.
Solution: asynchronous functions
Asynchronous functions allow the program to initiate a task and continue executing other code without waiting for the task to finish. The program is notified once the task is complete, at which point it can handle the result. This non-blocking behavior is essential for modern applications.
3. TypeScript vs. JavaScript:
TypeScript (TS) is a superset of JavaScript (JS) that adds static typing to the language. This means you can define the types of variables, function parameters, and return values, which helps catch errors during development rather than at runtime. JS, on the other hand, is a dynamically typed language, which means type checking happens only when the code is executed.
3.1. Why Choose TypeScript?
TS is particularly valuable for large, complex, or long-term projects. The static typing provides a safety net that helps prevent a common class of bugs. It also makes the code more self-documenting, as the type definitions can explain what kind of data a function expects and returns. Many popular frameworks and libraries were built with TS, and it is widely used in enterprise-level applications.
3.2. Type Interface
In TypeScript, a type interface refers to an interface declaration, which is used to define the shape or structure of an object, specifying the properties and methods that an object must have.
Why the interfaces are useful
Interfaces are a powerful tool for ensuring code consistency and safety. They help enforce that any object you use in a specific context has a predictable shape.
Type Checking: the compiler uses interfaces to check if an object conforms to the defined structure at compile-time. If it doesn't, you get an immediate error, preventing a common class of bugs.
Code Documentation: an interface acts as a form of self-documentation. Other developers can look at an interface and immediately understand what an object is supposed to look like.
Predictability and Autocomplete: when your IDE knows the structure of an object through an interface, it can offer smart code completion and helpful suggestions, making development faster and less prone to typos.
How the interface works
// 1. Define the interface
interface User {
id: number;
name: string;
email?: string; // The '?' makes this property optional
}
// 2. Use the interface to define a variable
const user1: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com'
};
// 3. This would cause a compile-time error because 'id' is missing
const user2: User = {
name: 'Bob',
email: 'bob@example.com'
};
In this example, the User
interface dictates that any object of type User
must have a number
called id
and a string
called name
. If you try to create an object that doesn't meet this contract, the TypeScript compiler will immediately flag an error.
4. React Features:
4.1. Prop Drilling
Prop drilling is a term used in component-based frameworks like React to describe the process of passing data from a parent component down to a deeply nested child component.
How prop drilling works
Imagine you have a component tree like this:
App
-> ParentComponent
-> ChildComponent
-> GrandchildComponent
Suppose the App
component holds a piece of data like a username that is only needed by the GrandchildComponent
. To get the username from the App
component to the GrandchildComponent
, you have to drill it down through the intermediate components.
-
App
passes theuser
prop toParentComponent
. -
ParentComponent
receives theuser
prop, but doesn't use it. It simply passes it on toChildComponent
. -
ChildComponent
also receives theuser
prop and doesn't use it again. It passes it on toGrandchildComponent
. - Finally,
GrandchildComponent
receives theuser
prop and uses it to display the username.
4.2. Props vs. State
In React, props are data passed from a parent component to a child component, acting as read-only arguments, while state is data managed internally by a component that can change over time, triggering a re-render. Props provide data to customize child components, whereas state allows a component to manage its own local, dynamic information, such as user inputs or API data.
4.3. Virtual DOM
The Virtual DOM (VDOM) is a lightweight, in-memory representation of the real DOM (Document Object Model). It's a key concept in frameworks like React that helps optimize performance.
How VDOM improves performance in frameworks
Instead of directly manipulating the real DOM, React first makes changes to the VDOM. When the state of a component updates, React creates a new VDOM tree and compares it to the previous one in a process called diffing. It calculates the minimal number of changes needed to sync the real DOM with the new VDOM. Finally, it batches these changes and updates the real DOM only where necessary.
This approach is faster because:
- Manipulating a JS object (the DOM) in memory is significantly quicker than directly interacting with the browser's DOM.
- It reduces the number of costly DOM manipulations, as only the changed parts of the UI are updated.
4.4. React Hooks
React hooks are functions that allow "hooking into" React state and lifecycle features from functional components. They enable the use of state and other React features in functional components, which previously were only available in class components.
useState(): the
useState()
hook allows functional components to manage and update state. It is fundamental for creating interactive and dynamic user interfaces.useRef(): the
useRef()
hook allows the creation of a mutable reference object that persists across component re-renders without causing the component to re-render when its value changes.
5. Vue Features:
5.1. Ref Function
In Vue.js, a reactive reference refers to a value that any changes are made to this value automatically trigger updates in the UI. The primary way to create such a reference is using the ref
function.
-
ref
is used to create a reactive reference to a single value. This value can be a primitive type (like string, number, or boolean) or an object/array. - When you create a reactive reference with
ref
, Vue wraps the value in a special object that allows it to detect changes. - To access or modify the actual value held by a
ref
, you must use the.value
property (username.value = newValue
).
5.2. v-model
Directive
The v-model
directive in Vue.js is a fundamental feature that enables two-way data binding. It simplifies the synchronization of data between a component's state and form input elements.
For example, when it comes to two-way data binding, v-model
automatically handles both updating the UI when the data changes and updating the data when the UI element like an input field is modified.
5.3. defineEmits()
In Vue 3, defineEmits()
is a compiler macro used to declare the custom events that a component can emit (the mechanism by which a child component sends a custom event and optionally data to its parent component). It serves as a way to explicitly define the interface for a component's outgoing communication, making it clearer what events a parent component can listen for.
How emit and defineEmits()
works
- When you call
emit('event-name')
, it triggers theevent-name
event. - The parent component listens for this event (e.g.,
@event-name="passwordWasSet = true"
) and can react, such as showing a success message or changing the UI state.
5.4. computed
Property
In Vue.js, computed
properties play a crucial role in password validation by providing a reactive, efficient way to derive validation states or messages based on other reactive data, such as the password input itself.
How computed
works in password validation
-
Deriving Validation Status: you define a
computed
property that returns a boolean indicating whether the password meets certain criteria (e.g.,isPasswordValid
,passwordMatch
).
import { defineComponent } from 'vue';
export default defineComponent({
data() {
return {
password: '' as string,
passwordConfirm: '' as string
};
},
computed: {
passwordMatch(): boolean {
return this.password === this.passwordConfirm && this.password.length > 0;
},
isPasswordStrong(): boolean {
// Example password strength rule:
return this.password.length >= 8 && /[A-Z]/.test(this.password);
}
}
});
5.5. v-if
and v-for
-
v-if
: Conditionally renders an element or block. If the condition istrue
, the element or block is rendered to the DOM; iffalse
, it is removed. This is useful for showing or hiding UI parts based on states (like error messages).
Conjunction with v-else-if
and v-else
It can be used in conjunction with v-else-if
and v-else
to create conditional blocks similar to if/else if/else
statements in programming.
<p v-if="isVisible">This paragraph is conditionally rendered.</p>
<p v-else-if="isSecondary">This is a secondary condition.</p>
<p v-else>This is the fallback.</p>
-
v-for
: Renders a list of elements by iterating over an array or object. For each item, it creates a new DOM. This is useful for displaying dynamic lists such as password criteria or error messages.
Key attribute
When using v-for
, it is recommended to provide a unique key
attribute to each iterated element for efficient list updates.
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
5.6. aria-invalid
Attribute
aria-invalid
is an ARIA (Accessible Rich Internet Applications) attribute used to indicate that the entered value is invalid.
How the aria-invalid
attribute works
- If
aria-invalid="true"
is present on an input, screen readers will announce that the field is invalid, helping users understand which fields require correction. - If omitted or set to
false
, the field is considered valid.
<label for="email">Email:</label>
<input type="email" id="email" aria-invalid="true">
<span id="email-error-message">Please enter a valid email address.</span>
6. Recommended Resource:
With refreshing my frontend domain knowledge at this time, I came across a really convenient resource called GreatFrontEnd. I highly recommend it not only for those who are preparing for technical interviews but also for anyone just starting their frontend journey, as it's a well-structured guide that highlights the essential features and concepts to before stepping into a professional role.
Conclusion
Mastering the fundamentals of JavaScript, TypeScript, React, and Vue gives you the confidence to tackle both interviews and real-world projects. From understanding async code to handling props, refs, and computed values, these core skills are the backbone of modern frontend engineering. Keep practicing, stay curious, and you’ll keep leveling up as a developer.
Top comments (0)