1. JavaScript Fundamentals
-
Question: What are variables in JavaScript and what are the differences between
var
,let
, andconst
?- Variables: Containers that store data. You can declare a variable and assign values to it for use in your code.
-
var x let x const
-
var
- Function scope: If declared inside a function, it can be accessed anywhere within that function.
- Can be redeclared, which may lead to unexpected behavior.
-
let
- Block scope: Within a function, it can only be accessed if within the same block { }.
- Cannot be redeclared (i.e., declaring the same variable again).
- Can reassign a value to the variable.
-
const
- Block scope: Within a function, it can only be accessed if within the same block { }.
- Cannot be redeclared.
- Cannot reassign a value, except if it is an array or object (properties and elements can be modified).
-
var
Note: In
var
,let
, andconst
, a variable declared inside a function can only be accessed within that function. However, withvar
, it does not need to be within the same code block {}.
function testVar() { if (true) { var x = 10; // var is visible throughout the function } console.log(x); // Works and prints 10 } function testLet() { if (true) { let y = 20; // let is only visible within the block {} } console.log(y); // Error! y is not available outside the block {} } testVar(); // Works and prints 10 testLet(); // Causes an error
-
Question: How do functions work in JavaScript? What is the difference between a regular function and an arrow function?
- Functions: Are blocks of code designed to perform tasks. They can be called from different parts of the code to avoid repetition and ensure better organization.
Feature | Regular Functions | Arrow Functions |
---|---|---|
Syntax | function name(args) { ... } |
(args) => { ... } |
this Context |
this is dynamic and depends on how the function is called |
this is lexical, inherited from the scope in which it was defined |
arguments Object |
Available within the function | Not available; uses the rest operator (...args ) to access arguments |
Usage with new |
Can be used as a constructor (can be called with new ) |
Cannot be used as a constructor (throws an error when using new ) |
Methods and super |
Can be used as a method in objects and can access super in classes |
Cannot access super and should not be used as an object method |
Concise Syntax | Typically more detailed | More concise, ideal for simple functions |
2. Closures
-
Question: What is a closure in JavaScript, and what is it used for?
- An inner function that has access to the variables declared in the outer function.
- Common uses: Creating private variables, encapsulating code, and maintaining state.
- Question: Provide an example of how closures can be used to create private variables.
function createCounter() {
let counter = 0; // private variable
return function() {
counter++; // The inner function can access and modify 'counter'
return counter;
};
}
const myCounter = createCounter(); // creates a counter
console.log(myCounter()); // Prints 1
3. Execution Context and this
3. Execution Context and this
-
Question: Explain how it works and how the value of
this
is determined in different scenarios.-
this
refers to the object of the context, and its value depends on how a function is called:- In the global context, outside of any function:
this
refers to the global object (e.g.,window
in the browser). - Inside a method:
this
refers to the object that owns the method. - In DOM events:
this
refers to the element that triggered the event. - With
call
,apply
, orbind
: you can explicitly set the value ofthis
.
- In the global context, outside of any function:
-
Examples:
- Global Context
console.log(this); // In the browser, this will log the global object `window`
- Inside a Method
const person = {
name: 'John',
greet: function() {
console.log(this.name); // `this` refers to the `person` object
}
};
person.greet(); // Output: John
- In DOM Events
<button id="myButton">Click me</button>
<script>
document.getElementById('myButton').addEventListener('click', function() {
console.log(this); // `this` refers to the button element that triggered the event
});
</script>
-
Using
call
,apply
, orbind
function greet() {
console.log(`Hello, ${this.name}`);
}
const person = { name: 'Jane' };
greet.call(person); // Output: Hello, Jane
greet.apply(person); // Output: Hello, Jane
const boundGreet = greet.bind(person);
boundGreet(); // Output: Hello, Jane
4. Prototypes and Inheritance
-
Question: What is prototyping in JavaScript? How does prototypical inheritance work?
- Prototyping is a concept that allows JavaScript objects to inherit properties and methods from other objects. Instead of using classes as in Object-Oriented Programming (OOP), JavaScript uses prototypes.
Question: What is the difference between
proto
andprototype
?
Explanation:
__proto__
: It is an internal property of an object that points to its prototype. You can use it to access the prototype of an object.prototype
: It is a property of constructor functions. It is used to define properties and methods that will be shared among all instances created by that constructor.
Examples:
- Prototypical Inheritance
// Constructor function
function Animal(name) {
this.name = name;
}
// Adding a method to the Animal prototype
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
const dog = new Animal('Rex');
dog.speak(); // Output: Rex makes a noise.
-
__proto__
vsprototype
// Constructor function
function Car(make) {
this.make = make;
}
// Adding a method to the Car prototype
Car.prototype.drive = function() {
console.log(`${this.make} is driving.`);
};
const myCar = new Car('Toyota');
console.log(myCar.__proto__ === Car.prototype); // Output: true
// `prototype` is used in the constructor function
console.log(Car.prototype); // Output: { drive: [Function (anonymous)] }
-
__proto__
is used to inspect or manipulate the prototype chain, whereasprototype
is used to set up properties and methods for objects created by a constructor.
5. Event Loop and Asynchrony
-
Question: Explain how the event loop works in JavaScript.
- Event Loop: Manages the execution of asynchronous code and ensures that asynchronous operations are handled without blocking the main thread of the code.
-
Question: What is the difference between callbacks, promises, and
async/await
?- Callbacks: Functions passed as arguments, traditionally used to handle asynchronous operations, but can lead to hard-to-maintain code.
function fetchData(callback) { setTimeout(() => { callback('Data received'); }, 1000); } fetchData((data) => { console.log(data); // "Data received" });
-
Promises: Represent the completion of an asynchronous operation and allow chaining operations with
.then()
,.catch()
, and.finally()
.
fetchData() .then((data) => { console.log(data); // "Data received" }) .catch((error) => { console.error(error); }); function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Data received'); }, 1000); }); }
-
Async/Await: A more modern syntax that simplifies asynchronous code, allowing you to write asynchronous code in a more synchronous and readable manner.
-
await: Pauses the execution of the
async
function until the Promise is resolved.
async function showData() { try { const data = await fetchData(); console.log(data); // "Data received" } catch (error) { console.error(error); } } showData(); function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Data received'); }, 1000); }); }
-
await: Pauses the execution of the
6. Array and Object Manipulation
-
Question: How can you iterate over arrays and objects in JavaScript? What are the most commonly used methods? Explain the difference between
map
,filter
, andreduce
.Arrays:
-
for
loop
const numbers = [1, 2, 3, 4, 5]; for (let i = 0; i < numbers.length; i++) { console.log(numbers[i]); }
-
forEach
: Executes a function for each item in the array
const fruits = ['apple', 'banana', 'orange']; fruits.forEach((fruit) => { console.log(fruit); });
-
map
: Creates a new array with the results of applying a function to each item in the array. Transforms data.
const numbers = [1, 2, 3]; const squares = numbers.map(num => num * num); console.log(squares); // [1, 4, 9]
-
filter
: Creates a new array with the elements that pass a test. Filters data.
const numbers = [1, 2, 3, 4, 5]; const evenNumbers = numbers.filter(num => num % 2 === 0); console.log(evenNumbers); // [2, 4]
-
reduce
: Applies a reducing function to each item in the array, resulting in a single value. Accumulates values, sums.
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((accumulator, number) => accumulator + number, 0); console.log(sum); // 10
-
-
Objects:
-
for in
loop: Iterates over the properties of an object.
const person = { name: 'John', age: 30, city: 'São Paulo' }; for (let key in person) { console.log(`${key}: ${person[key]}`); }
-
Object.keys
: Returns an array with the names of the properties of an object.
const person = { name: 'Ana', age: 25 }; Object.keys(person).forEach(key => { console.log(`${key}: ${person[key]}`); });
-
Object.values
: Returns an array with the values of the properties of an object.
const person = { name: 'Lucas', age: 28 }; Object.values(person).forEach(value => { console.log(value); });
-
Object.entries
: Returns an array with the [key, value] pairs of an object.
const person = { name: 'Beatriz', age: 22 }; Object.entries(person).forEach(([key, value]) => { console.log(`${key}: ${value}`); });
-
7. Scope and Hoisting
-
Question: What is scope in JavaScript? What is the difference between global scope, function scope, and block scope?
- Scope is the context in which variables and functions are declared and accessed.
- Global: Accessible throughout the entire code.
- Function: Accessible only within the function. Good for encapsulating and keeping things clean.
- Block: Accessible only within a block { } (e.g., declared within an if statement, or within a function). Adds an additional level of encapsulation.
- Scope is the context in which variables and functions are declared and accessed.
-
Question: What is hoisting in JavaScript?
-
Hoisting is a feature of JavaScript's compilation phase. During this phase, JavaScript prepares the code for execution. This involves moving variable and function declarations to the top of their scope to ensure they are available for use throughout the code.
- Functions: The declaration is fully hoisted, making the function available throughout the scope.
-
Variables (
var
): Only the declaration is hoisted. The variable isundefined
until the initialization line. -
Variables (
let
andconst
): The declaration is hoisted, but cannot be accessed before the declaration line due to the "temporal dead zone."
- The goal is to ensure functions are available anywhere in the scope, allowing greater flexibility in the order of function declarations and calls.
-
Hoisting is a feature of JavaScript's compilation phase. During this phase, JavaScript prepares the code for execution. This involves moving variable and function declarations to the top of their scope to ensure they are available for use throughout the code.
8. DOM Manipulation
-
Question: How can you manipulate the DOM in JavaScript? What are the most common methods used for this?
- DOM manipulation is the process of manipulating the content of an HTML document using JavaScript.
- Common Methods:
- Select Elements:
getElementById(id)
getElementsByClassName(className)
getElementsByTagName(tagName)
querySelector(selector)
querySelectorAll(selector)
- Change Content:
-
innerHTML
: Sets or returns the HTML content. -
textContent
: Sets or returns the text content.
-
- Change Attributes:
setAttribute(name, value)
getAttribute(name)
- Add or Remove Classes:
classList.add(className)
classList.remove(className)
-
classList.toggle(className)
: Adds if not present, and removes if present.
- Create and Insert Elements:
createElement(tagName)
appendChild(child)
insertBefore(newNode, referenceNode)
- Remove Elements:
removeChild(child)
remove()
- Select Elements:
-
Question: What is the difference between
innerHTML
andtextContent
?-
innerHTML
manipulates the HTML content.textContent
manipulates the text content.
-
9. State Management and Data Flow
-
Question: How do you manage the state of an application in JavaScript, especially in frameworks like React?
- State management reflects how data is stored and manipulated in an application. In React, there are various methods:
- Local: Using the
useState
hook. Shares data within a functional component. - Global: Using Context API or Redux. Shares data between components.
- Local: Using the
- State management reflects how data is stored and manipulated in an application. In React, there are various methods:
-
Question: What is the unidirectional data flow pattern and how does it apply to frontend development?
-
Unidirectional Data Flow is a pattern where data flows in one direction through the application. This means that information flows top-down, and changes in state are propagated back to components that need to be updated.
-
Parent and Child Components:
- Data is passed from the parent component to child components via props.
- Child components should not modify data directly; instead, they can request an update from the parent component.
-
State Updates:
- When an event occurs (like a click), the state is updated. State updates cause React to re-render the affected components, reflecting the changes in the user interface.
-
Parent and Child Components:
- The unidirectional data flow pattern is a fundamental approach in modern frontend development, especially in frameworks and libraries like React. It provides a predictable and organized way to manage state and update the user interface.
-
Unidirectional Data Flow is a pattern where data flows in one direction through the application. This means that information flows top-down, and changes in state are propagated back to components that need to be updated.
10. Testing in JavaScript
-
Question: What are some best practices for writing tests in JavaScript?
- Keep your tests simple and isolated: Each test should check a single behavior or unit of code. This makes it easier to identify problems and maintain the tests.
- Name your tests clearly: Use descriptive names so it's easy to understand what the test is checking. This helps quickly identify what is breaking when a test fails.
- Write tests before code (TDD): Test-Driven Development (TDD) is a practice where you write the tests before implementing the code. This can help define what the code should do clearly and ensure you cover all use cases.
- Use meaningful asserts: Make sure that the assertions in your tests check for expected behavior and not just the presence of data.
- Keep tests fast and independent: Tests should be quick so they can be run frequently. Also, they should be independent so that the execution of one test does not affect others.
- Maintain good isolation: Use mocks and stubs to isolate the code under test and avoid external dependencies, such as network calls or interactions with databases.
- Organize your tests: Keep your tests well-organized in folders and files that reflect the structure of your code. This makes it easier to locate and run tests.
- Adequate test coverage: While test coverage should not be the only quality metric, have adequate coverage to ensure that important parts of your code are being tested.
- Review and update tests regularly: Update your tests when you make changes to the code. Outdated tests can give a false sense of security.
- Document complex tests: When tests are complex or have special logic, document the reason and what is being checked. This helps understand the purpose of the test in the future.
-
Question: Can you explain the difference between unit tests, integration tests, and end-to-end (E2E) tests?
-
Unit Tests
- Objective: Verify the functionality of individual units of code, such as functions, methods, or classes.
- Scope: Test isolated components and check if each unit performs its tasks correctly.
- Example: Test a function that calculates the total value of a shopping cart based on items and discounts applied.
- Common Tools: Jest, Mocha, Jasmine.
-
Code Example:
// Function to be tested function sum(a, b) { return a + b; } // Unit test using Jest test('sum of 1 and 2 should be 3', () => { expect(sum(1, 2)).toBe(3); });
-
Integration Tests
- Objective: Verify the interaction between different units of code or components.
- Scope: Test the integration of various parts of the system to ensure they work correctly together.
- Example: Test if a function that makes API calls and manipulates data interacts correctly with another function that updates the user interface.
- Common Tools: Jest (can also be used for integration), Mocha, Cypress.
-
Code Example:
// Function that uses a fictitious API async function fetchData() { const response = await fetch('https://api.example.com/data'); return response.json(); } // Integration test using Jest and mocks test('fetchData should return data from the API', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ key: 'value' }), }) ); const data = await fetchData(); expect(data.key).toBe('value'); });
-
End-to-End (E2E) Tests
- Objective: Test the complete flow of the system, from the user interface to the backend and interactions with databases or external services.
- Scope: Test the system as a whole to ensure all components work together as expected.
- Example: Test the entire process of a purchase on an e-commerce site, from selecting an item to completing the purchase and receiving a confirmation.
- Common Tools: Cypress, Selenium, TestCafe.
-
Code Example (Cypress):
// End-to-end test using Cypress describe('Purchase Flow', () => { it('should complete a purchase successfully', () => { cy.visit('https://www.example.com'); cy.get('.product').first().click(); cy.get('.add-to-cart').click(); cy.get('.checkout').click(); cy.get('.order-confirmation').should('contain', 'Thank you for your purchase!'); }); });
-
11. Types and Type Coercion
-
Question: What are the primitive types in JavaScript?
- Number: Represents numbers, both integers and floating-point. Example: 42, 3.14.
- String: Represents sequences of characters. Example: "hello", 'world'.
- Boolean: Represents logical values, which can be true or false.
- Undefined: Represents the absence of value. A variable that has been declared but not initialized has the value undefined.
- Null: Represents intentional absence of value. It is a value that should be explicitly assigned to a variable to indicate that it has no value.
- Symbol: Represents unique and immutable identifiers. Used to create object properties that are guaranteed to be unique.
- BigInt: Represents very large integers that cannot be represented by the Number class. Example: 1234567890123456789012345678901234567890n.
-
Code Example:
// Primitive types let num = 42; // Number let str = "Hello"; // String let bool = true; // Boolean let undef; // Undefined let nul = null; // Null let sym = Symbol('unique'); // Symbol let bigInt = 1234567890123456789012345678901234567890n; // BigInt
-
Question: How does type coercion work in JavaScript?
- Type coercion in JavaScript refers to the automatic or explicit conversion between different data types. It can be implicit (automatic) or explicit (forced by the code).
-
Implicit
-
Arithmetic Operations: When using the
+
operator with a string and a number, the number is converted to a string, and the operation is performed as concatenation.
let result = "The number is " + 5; // "The number is 5"
-
Comparative Operations: When comparing a number with a string, the string is converted to a number.
console.log(5 == '5'); // true, because '5' is converted to 5
-
Boolean Context: When a value is used in a context that expects a boolean, such as an
if
statement, JavaScript converts the value totrue
orfalse
.
if ("hello") { console.log("This will run"); // The non-empty string is converted to true }
-
-
Explicit Coercion
- You can force type coercion manually using conversion functions. Type coercion can help avoid bugs and write more predictable and robust code.
-
String: To convert a value to a string, you can use
String()
or thetoString()
method.
let num = 123; let str = String(num); // "123"
-
Number: To convert a value to a number, you can use
Number()
,parseInt()
, orparseFloat()
.
let str = "456"; let num = Number(str); // 456
-
Boolean: To convert a value to a boolean, you can use
Boolean()
.
let num = 0; let bool = Boolean(num); // false
12. ES6+ and Modern Features
- Question: What are the new features introduced in ES6 and later versions?
ES6 (2015) introduced several significant improvements to JavaScript. Here are some of the key features:
- Let and Const: Variable declarations with block scope.
-
Arrow Functions: Concise syntax for functions, with lexically bound
this
.
const add = (a, b) => a + b;
-
Classes: Prototype-based syntax for creating objects and inheritance.
class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}`; } }
-
Template Literals: Strings with variable interpolation and multi-line support.
const name = 'Alice'; const message = `Hello, ${name}!`;
-
Destructuring Assignment: Extracting values from arrays or objects into distinct variables.
const [a, b] = [1, 2]; const { x, y } = { x: 10, y: 20 };
-
Default Parameters: Default values for function parameters.
function greet(name = 'Guest') { return `Hello, ${name}`; }
-
Rest Parameters: Collecting multiple parameters into an array.
function sum(...numbers) { return numbers.reduce((total, num) => total + num, 0); }
-
Spread Operator: Expanding elements of arrays or properties of objects.
const arr1 = [1, 2]; const arr2 = [...arr1, 3, 4];
-
Promises: For handling asynchronous operations.
const fetchData = () => new Promise((resolve, reject) => { setTimeout(() => resolve('Data fetched'), 1000); });
-
Modules: Importing and exporting modules.
// module.js export const pi = 3.14; // app.js import { pi } from './module.js';
-
Enhanced Object Literals: Improved syntax for objects.
const name = 'Alice'; const person = { name, greet() { return `Hello, ${this.name}`; } };
ES7 (ECMAScript 2016) and ES8 (ECMAScript 2017) introduced additional features:
-
ES7 (2016)
-
Exponentiation Operator:
**
for exponentiation.
const result = 2 ** 3; // 8
-
-
ES8 (2017)
-
Async/Await: Syntax for writing asynchronous code in a more synchronous manner.
async function fetchData() { const response = await fetch('https://api.example.com'); const data = await response.json(); return data; }
-
- **Object.entries() and Object.values()**: Methods for iterating over the keys and values of objects.
```javascript
const obj = { a: 1, b: 2 };
console.log(Object.entries(obj)); // [['a', 1], ['b', 2]]
console.log(Object.values(obj)); // [1, 2]
```
-
ES9 (ECMAScript 2018) and ES10 (ECMAScript 2019) brought further improvements:
- Rest/Spread Properties: In objects, similar to usage with arrays.
const obj = { a: 1, b: 2 }; const clone = { ...obj }; // { a: 1, b: 2 }
-
Asynchronous Iteration: Using
for-await-of
to iterate over asynchronous objects.
async function* asyncGen() { yield 1; yield 2; } for await (let value of asyncGen()) { console.log(value); }
-
Optional Catch Binding: The
catch
block parameter can be omitted.
try { // Code } catch { // Handle error without using the error object }
-
flat() and flatMap(): Methods for flattening arrays and mapping arrays.
const arr = [1, [2, [3, 4]]]; console.log(arr.flat(2)); // [1, 2, 3, 4]
-
Question: Explain how
destructuring
andspread/rest operators
work.Destructuring
Destructuring allows you to extract values from arrays or objects and assign them to distinct variables. It can be used with both arrays and objects.
- Destructuring with Arrays:
const numbers = [1, 2, 3]; const [first, second] = numbers; // first = 1, second = 2
- Destructuring with Objects:
const person = { name: 'Alice', age: 25 }; const { name, age } = person; // name = 'Alice', age = 25
You can also rename variables and set default values:
const { name: fullName = 'Unknown', age = 30 } = person;
Spread/Rest Operators
Spread Operator:
The spread operator (
...
) is used to expand elements of arrays or properties of objects into new arrays or objects.- In Arrays:
const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
- In Objects:
const obj1 = { a: 1, b: 2 }; const obj2 = { c: 3 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3 }
Rest Parameters:
The rest parameter (
...
) allows you to accept an indefinite number of arguments as an array.- In Functions:
function sum(...numbers) { return numbers.reduce((total, num) => total + num, 0); }
Here,
numbers
is an array containing all arguments passed to thesum
function.
13. JavaScript Security
- Question: What are some of the main security concerns when writing JavaScript for web applications?
- Question: What are XSS and CSRF, and how can you mitigate these types of attacks?
Main Security Concerns in JavaScript for Web Applications
-
Cross-Site Scripting (XSS):
- Description: XSS occurs when an attacker injects malicious scripts into web pages that are executed in the user’s browser. This can lead to cookie theft, session data compromise, or unwanted actions performed on behalf of the user.
-
Prevention:
- Output Escaping: Always escape and sanitize dynamic data inserted into HTML to prevent scripts from executing.
- Use Content Security Policy (CSP): Set up a CSP to restrict the execution of unauthorized scripts.
- Data Validation and Sanitization: Validate and sanitize data both on the client side and the server side.
-
Cross-Site Request Forgery (CSRF):
- Description: CSRF occurs when an attacker tricks an authenticated user into performing unauthorized actions on a site where they are logged in. For example, a user might be tricked into submitting a request that changes account settings or performs a financial transaction unknowingly.
-
Prevention:
- CSRF Tokens: Generate and verify unique CSRF tokens for each request. Include the token in forms and request headers.
-
Referer and Origin Checking: Check the
Referer
orOrigin
headers of requests to ensure they originate from your own site. - Secure Request Methods: Use secure HTTP methods like POST for critical operations and avoid allowing sensitive actions to be performed using GET methods.
-
Code Injection:
- Description: Code injection occurs when an attacker inserts malicious code into an application, often through input fields. This can lead to attacks such as SQL injection if not properly handled.
-
Prevention:
- Prepared Statements: Use parameterized queries and ORM for database interactions.
- Input Sanitization and Validation: Validate and sanitize all input data to prevent malicious code from being processed.
-
Session Security:
- Description: Ensuring user sessions are secure is crucial to prevent attacks such as session hijacking.
-
Prevention:
-
Secure Cookies: Use cookies with the
HttpOnly
,Secure
, andSameSite
flags to protect session cookies. - Session Management: Implement session token expiration and rotation.
-
Secure Cookies: Use cookies with the
-
Data Security:
- Description: Protecting sensitive data is essential to ensure user data privacy and integrity.
-
Prevention:
- Encryption: Encrypt sensitive data in transit (using HTTPS) and at rest (in storage).
- Password Management: Use secure hashing algorithms, such as bcrypt, for storing passwords.
Top comments (0)