JavaScript Fundamentals Interview Questions: Core Concepts and Practical Analysis â
Introduction â
JavaScript is the core foundation of frontend development and the most frequently tested topic in technical interviews. Mastering JavaScript's core concepts not only helps you stand out in interviews but also enables you to write higher-quality code in daily development. This section breaks down JavaScript's core knowledge into systematized interview questions, helping you deeply understand the principles and application scenarios of each concept.
1. Variables and Data Types â
Interview Question 1: JavaScript Data Type Conversion â
Question: What is the output of the following code?
console.log([] == ![]); // Question 1
console.log([] == ![]); // Question 2
console.log([] == ![]); // Question 3Answer:
true
true
falseExplanation: This question tests JavaScript's type conversion rules and operator precedence:
==vs===:==performs type coercion===does not perform type coercion, directly comparing values and types
Logical NOT operator
!:![]converts empty array to booleanfalse, then negates totrue![]converts tofalse, so[] == ![]is equivalent to[] == false, result istrue
Type conversion mechanism:
// Array to boolean
Boolean([]); // false
Boolean({}); // true
// Number to string
String(123); // "123"
// Boolean to number
Number(true); // 1
Number(false); // 0Extended knowledge:
- Falsy values:
false,0,"",null,undefined,NaN - Truthy values: All values except falsy values
- Type conversion priority:
!>==>===>&&>||
Interview Question 2: Implicit Type Conversion Pitfalls â
Question: What is the output of the following code?
console.log([] + 1 + "1" + 1);
console.log("foo" + +"bar");
console.log("foo" + +(+"bar"));Answer:
"11"
"fooNaNbar"
"foo2bar"Explanation: This question tests JavaScript's implicit type conversion and operator precedence:
[] + 1: Empty array converts to number0,0 + 1 = 11 + "1": Number plus string,1 + "1" = "11""foo" + + "bar":"foo" ++converts+to string, getting"foo+"`+"bar"string concatenation,"foo+" + "bar" = "fooNaNbar"
+ + +: Unary+, converts to numberNaN"foo" + NaN + "bar" = "foo2bar"
Key concepts:
- Implicit type conversion rules
- Operator precedence and associativity
+operator overloading behavior
2. Scope and Closures â
Interview Question 3: Closures and Variable Capture â
Question: What is the output of the following code?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}Answer:
3
3
3Explanation: This question tests JavaScript's scope and closure concepts:
vardeclaration:varhas function scope, but variables declared in loops are hoisted- Closure formation: Each
setTimeoutcallback function forms a closure, capturing variablei - Asynchronous execution timing:
setTimeoutis asynchronous, all callbacks execute after the loop finishes - Variable value issue: Since all callbacks share the same variable
i, andiequals3when async executes
Solutions:
// Method 1: Use let declaration
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Method 2: Use IIFE
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// Method 3: Use block scope
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}Interview Question 4: Practical Applications of Closures â
Question: What functionality does the following code implement? What problems exist?
function createCounter() {
var count = 0;
return function () {
count++;
return count;
};
}
var counter1 = createCounter();
var counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1
console.log(counter2()); // 2
console.log(counter1()); // 3
console.log(counter2()); // 4Answer: Implements two independent counters, but the problem is that both counters share the same count variable.
Explanation:
- Closure characteristics: Each returned function accesses the
countvariable of the outer function - Variable sharing issue: Due to
var's function scope characteristic, both counters actually share the same variable - Expected behavior: Developers might expect each counter to work independently
Correct implementation:
function createCounter() {
return function () {
// Each function has its own count variable
var count = 0;
return function () {
count++;
return count;
};
};
}
// Or use ES6 syntax
const createCounterES6 = () => {
let count = 0;
return () => {
count++;
return count;
};
};3. this Keyword â
Interview Question 5: this Binding Issues â
Question: What is the output of the following code?
var name = "global";
var obj = {
name: "object",
getName: function () {
return this.name;
},
getNameArrow: () => {
return this.name;
},
};
console.log(obj.getName()); // Question 1
console.log(obj.getNameArrow()); // Question 2
console.log(obj.getName.call({ name: "called" })); // Question 3
console.log(obj.getNameArrow.call({ name: "called" })); // Question 4
// Question 5
var getName = obj.getName;
console.log(getName()); // Question 5
console.log(getName.call({ name: "called" })); // Question 6Answer:
"object"
"global"
"called"
"global"
"object"
"called"Explanation: This question comprehensively tests JavaScript's this binding rules:
- Object method invocation:
thisinobj.getName()points toobj - Arrow functions: Arrow functions don't have their own
this, capturing outer scope'sthis call/applyinvocation: Explicitly specifiesthisbinding- Function assignment call: When a function is called as a method of an independent object,
thispoints to the global object (orundefinedin strict mode)
this binding rules summary:
- Default binding: Direct function call,
thispoints to global object orundefined - Implicit binding: Calling through an object,
thispoints to that object - Explicit binding: Using
call,apply,bindto specifythis - new binding: Using
newto call constructor,thispoints to newly created object - Arrow functions: Inherit
thisfrom outer scope
Interview Question 6: this in Constructors â
Question: What is the output of the following code?
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name);
};
var p1 = new Person("John");
p1.sayName(); // Output 1
var p2 = Person("Sarah");
p2.sayName(); // Output 2
// Using apply
p1.sayName.apply(p2); // Output 3Answer:
"John"
"Sarah"
"John"Explanation:
thisin constructors:thisinnew Person("John")points to the newly created object instancethisin prototype methods:thisinp1.sayName()points to the object calling the method,p1- Effect of
applymethod:p1.sayName.apply(p2)points method'sthistop2, but thenameproperty on prototype chain is stillp1's
4. Prototypes and Inheritance â
Interview Question 7: Prototype Chain Lookup â
Question: What is the output of the following code?
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hello, I'm ${this.name}`);
};
var person = new Person("John");
person.sayHello(); // Output 1
person.sayHello = function () {
console.log(`Hello, I'm ${this.name}, age ${this.age}`);
};
person.sayHello(); // Output 2Answer:
"Hello, I'm John"
"Hello, I'm John, age undefined"Explanation:
- Prototype chain lookup: When accessing object properties, first look in the object itself; if not found, look up the prototype chain
- Property shadowing: Adding
sayHellomethod topersonobject shadows the method with the same name on the prototype - Prototype sharing: All instances share the same prototype object
Interview Question 8: Implementing Prototypal Inheritance â
Question: How to manually implement JavaScript inheritance?
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child(name, age) {
// Inherit Parent's properties and methods
}
// Implement Child inheriting from ParentAnswer:
// Method 1: Prototypal inheritance
function ChildPrototype(name, age) {
Parent.call(this, name);
this.age = age;
}
ChildPrototype.prototype = Object.create(Parent.prototype);
ChildPrototype.prototype.constructor = ChildPrototype;
ChildPrototype.prototype.sayAge = function () {
console.log(`${this.age} years old`);
};
// Method 2: Constructor inheritance
function ChildConstructor(name, age) {
Parent.call(this, name);
this.age = age;
}
ChildConstructor.prototype = new Parent();
ChildConstructor.prototype.constructor = ChildConstructor;
ChildConstructor.prototype.sayAge = function () {
console.log(`${this.age} years old`);
};
// Method 3: Combination inheritance
function ChildComposition(name, age) {
Parent.call(this, name);
this.age = age;
}
ChildComposition.prototype = Object.assign(Object.create(Parent.prototype), {
constructor: ChildComposition,
sayAge: function () {
console.log(`${this.age} years old`);
},
});
// Method 4: ES6 Class inheritance
class ParentES6 {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class ChildES6 extends ParentES6 {
constructor(name, age) {
super(name);
this.age = age;
}
sayAge() {
console.log(`${this.age} years old`);
}
}Inheritance approaches comparison:
- Prototypal inheritance: Simple, but cannot pass arguments to parent constructor
- Constructor inheritance: Can pass arguments, but inheriting parent prototype creates unnecessary parent instances
- Combination inheritance: Combines benefits of both, most recommended approach
- ES6 Class inheritance: Concise syntax, still prototypal inheritance underneath
5. ES6+ Features â
Interview Question 9: Differences Between Arrow Functions and Regular Functions â
Question: What is the output of the following code?
const obj = {
name: "John",
age: 25,
};
function logName() {
console.log(this.name);
}
const arrowLogName = () => {
console.log(this.name);
};
logName.call(obj); // Question 1
arrowLogName.call(obj); // Question 2
// As object methods
const person = {
name: "Sarah",
logName: logName,
arrowLogName: arrowLogName,
};
person.logName(); // Question 3
person.arrowLogName(); // Question 4Answer:
"John"
undefined
"Sarah"
undefinedExplanation:
- Arrow function characteristics: Arrow functions don't have their own
this,arguments, and cannot be used as constructors - this binding:
- Regular function's
thisis determined by calling method - Arrow function's
thisis determined at definition time and cannot be changed
- Regular function's
- Lexical scope binding: Arrow function's
thisbinds to the lexical scope at definition
Interview Question 10: Destructuring and Spread Operator â
Question: What is the output of the following code?
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };
// Question 1
console.log([...arr, 4, 5]);
// Question 2
console.log([1, ...arr, 4, ...arr]);
// Question 3
console.log({ ...obj, c: 3 });
// Question 4
const [first, ...rest] = arr;
console.log(first, rest);
// Question 5
const { a, b, ...rest } = obj;
console.log(a, b, rest);Answer:
[1, 2, 3, 4, 5]
[1, 1, 2, 3, 4, 5]
{ a: 1, b: 2, c: 3 }
1 [2, 3]
1 2Explanation:
- Spread operator
...: Used to expand arrays or objects - Destructuring assignment: Extract values from arrays or objects and assign to variables
- Rest parameters: Use
...restsyntax to collect remaining parameters - Application scenarios: Function parameter handling, array operations, object merging, etc.
Interview Question 11: Promise and async/await â
Question: What is the output of the following code?
// Question 1
console.log("start");
setTimeout(() => {
console.log("timeout");
}, 0);
Promise.resolve().then(() => {
console.log("promise");
});
// Question 2
async function testAsync() {
console.log("start");
await new Promise((resolve) => {
setTimeout(() => {
console.log("timeout");
resolve();
}, 0);
});
console.log("promise");
}
testAsync();Answer:
start
timeout
promise
start
timeout
promiseExplanation:
- Event loop mechanism: Synchronous code > Microtasks (Promise.then) > Macrotasks (setTimeout)
- async/await essence: Syntactic sugar, still Promise underneath
- Execution order:
- Synchronous code executes immediately
- Microtasks execute immediately after current script completes
- Macrotasks execute in next event loop
6. Array and Object Operations â
Interview Question 12: Advanced Array Method Applications â
Question: Implement the following array operation methods
// Implement array flattening
function flatten(arr) {
// Input: [1, [2, 3], [4, [5, 6]]]
// Output: [1, 2, 3, 4, 5, 6]
}
// Implement array deduplication
function unique(arr) {
// Input: [1, 2, 2, 3, 4, 3, 5]
// Output: [1, 2, 3, 4, 5]
}
// Implement array grouping
function groupBy(arr, key) {
// Input: [{name: 'John', age: 25}, {name: 'Sarah', age: 25}, {name: 'Mike', age: 30}]
// Group by age
}Answer:
// Array flattening
function flatten(arr) {
return arr.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? flatten(val) : val);
}, []);
}
// Array deduplication
function unique(arr) {
return [...new Set(arr)];
}
// Or using filter
function unique2(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
}
// Array grouping
function groupBy(arr, key) {
return arr.reduce((acc, obj) => {
const groupKey = obj[key];
if (!acc[groupKey]) {
acc[groupKey] = [];
}
acc[groupKey].push(obj);
return acc;
}, {});
}Interview Question 13: Deep Copy vs Shallow Copy â
Question: What is the output of the following code?
const obj1 = {
name: "John",
info: {
age: 25,
address: {
city: "New York",
},
},
};
const obj2 = Object.assign({}, obj1);
const obj3 = JSON.parse(JSON.stringify(obj1));
const obj4 = { ...obj1 };
obj2.info.address.city = "London";
obj3.info.address.city = "Paris";
obj4.info.address.city = "Tokyo";
console.log(obj1.info.address.city); // Original object
console.log(obj2.info.address.city); // Object.assign
console.log(obj3.info.address.city); // JSON method
console.log(obj4.info.address.city); // Spread operatorAnswer:
New York
New York
Paris
TokyoExplanation:
- Shallow copy: Only copies first level properties, nested objects remain references
Object.assign()- Spread operator
...
- Deep copy: Recursively copies all levels of properties, creating completely independent objects
JSON.parse(JSON.stringify())- Recursive function implementation
Deep copy implementation:
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map((item) => deepClone(item));
}
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}7. Event Loop and Task Queues â
Interview Question 14: Execution Order of Macrotasks and Microtasks â
Question: What is the output order of the following code?
console.log("1");
setTimeout(() => {
console.log("2");
Promise.resolve().then(() => {
console.log("3");
});
}, 0);
Promise.resolve().then(() => {
console.log("4");
setTimeout(() => {
console.log("5");
}, 0);
});
console.log("6");Answer:
1
6
4
2
3
5Explanation:
- Call stack: Synchronous code executes first (1, 6)
- Microtask queue: Promise.then is a microtask, executes immediately after current macrotask completes
- Macrotask queue: setTimeout is a macrotask, executes in next event loop
Execution flow:
- Synchronous code: prints 1, 6
- First microtask: prints 4, adds setTimeout(5) to macrotask queue
- First macrotask: prints 2, adds Promise.then(3) to microtask queue
- Microtask: prints 3
- Next macrotask: prints 5
Macrotask and microtask classification:
// Macrotasks
// - setTimeout / setInterval
// - I/O operations
// - UI rendering
// - setImmediate (Node.js)
// Microtasks
// - Promise.then / catch / finally
// - async/await
// - MutationObserver
// - process.nextTick (Node.js)Interview Question 15: Execution Timing of async/await â
Question: What is the output of the following code?
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
async1();
new Promise((resolve) => {
console.log("promise1");
resolve();
}).then(() => {
console.log("promise2");
});
console.log("script end");Answer:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeoutExplanation:
- async/await essence: Code after
awaitis equivalent to executing inPromise.then - Execution order:
- Synchronous code: script start â async1 start â async2 â promise1 â script end
- Microtasks: async1 end â promise2
- Macrotask: setTimeout
8. Module Systems â
Interview Question 16: Differences Between ES Modules and CommonJS â
Question: What are the differences between ES6 modules (ES Modules) and CommonJS modules?
Answer:
| Feature | ES Modules | CommonJS |
|---|---|---|
| Load time | Compile-time (static) | Runtime (dynamic) |
| Export | Value reference | Value copy |
| this | undefined | Current module |
| Circular dependencies | Can handle | May have issues |
| Use cases | Browser, modern Node.js | Node.js (traditional) |
| Import syntax | import / export | require / module.exports |
Code examples:
// ES Modules
// module.js
export let counter = 0;
export function increment() {
counter++;
}
// main.js
import { counter, increment } from "./module.js";
console.log(counter); // 0
increment();
console.log(counter); // 1 (reference, syncs updates)
// ===================================
// CommonJS
// module.js
let counter = 0;
function increment() {
counter++;
}
module.exports = { counter, increment };
// main.js
const { counter, increment } = require("./module.js");
console.log(counter); // 0
increment();
console.log(counter); // 0 (copy, doesn't update)Interview Question 17: Circular Dependency Issues â
Question: How to handle circular dependencies in modules?
Answer:
// a.js
import { b } from "./b.js";
export const a = "a";
console.log("a.js:", b);
// b.js
import { a } from "./a.js";
export const b = "b";
console.log("b.js:", a);
// main.js
import "./a.js";ES Modules handling:
- Modules are "pre-processed" before execution, forming module records
- Imports are references, not value copies
- Even with circular dependencies, can correctly access exported variables
CommonJS handling:
- Modules are cached on first load
- With circular dependencies, may get incomplete export objects
- Need to pay attention to export timing and order
9. DOM Operations and Events â
Interview Question 18: Event Delegation Principles and Applications â
Question: What is event delegation? How to implement efficient event delegation?
Answer:
Event delegation: Utilize event bubbling mechanism to delegate child element event listeners to parent element for handling.
Advantages:
- Reduced memory consumption (only one listener needed)
- Dynamically added elements can also respond to events
- Improved performance
Implementation example:
// HTML structure
// <ul id="list">
// <li data-id="1">Item 1</li>
// <li data-id="2">Item 2</li>
// <li data-id="3">Item 3</li>
// </ul>
// Traditional approach (not recommended)
const items = document.querySelectorAll("#list li");
items.forEach((item) => {
item.addEventListener("click", function () {
console.log(this.dataset.id);
});
});
// Event delegation approach (recommended)
const list = document.querySelector("#list");
list.addEventListener("click", function (e) {
// Ensure clicked element is li
if (e.target.tagName === "LI") {
console.log(e.target.dataset.id);
}
});
// More generic event delegation function
function delegate(parent, selector, event, handler) {
parent.addEventListener(event, function (e) {
const target = e.target;
if (target.matches(selector)) {
handler.call(target, e);
}
});
}
// Usage
delegate(list, "li", "click", function (e) {
console.log(this.dataset.id);
});Interview Question 19: Preventing Event Bubbling and Default Behavior â
Question: How to prevent event bubbling and default behavior? What's the difference?
Answer:
// Prevent event bubbling
element.addEventListener("click", function (e) {
e.stopPropagation(); // Prevent event from bubbling up
console.log("clicked");
});
// Prevent default behavior
link.addEventListener("click", function (e) {
e.preventDefault(); // Prevent link navigation
console.log("link clicked but not navigated");
});
// Prevent both bubbling and default behavior
button.addEventListener("click", function (e) {
e.stopPropagation();
e.preventDefault();
// Or shorthand
return false; // In traditional event handling
});Differences:
stopPropagation(): Prevents event from bubbling up in DOM treepreventDefault(): Prevents element's default behavior (e.g., form submission, link navigation)stopImmediatePropagation(): Prevents event bubbling and other listeners on same element from executing
10. ES6+ Data Structures â
Interview Question 20: Differences Between Map and Object â
Question: What are the differences between Map and regular objects? When to use Map?
Answer:
| Feature | Map | Object |
|---|---|---|
| Key types | Any type | String or Symbol |
| Key order | Insertion order | Not guaranteed (ES2015+ partially ordered) |
| Size | map.size | Object.keys(obj).length |
| Iteration | Directly iterable | Needs Object.keys() |
| Performance | Better for frequent add/delete | Sufficient for simple cases |
| Serialization | No JSON support | Supports JSON |
Code examples:
// Map usage
const map = new Map();
// Keys can be any type
map.set("string", "String key");
map.set(1, "Number key");
map.set(true, "Boolean key");
map.set({}, "Object key");
console.log(map.size); // 4
console.log(map.get(1)); // 'Number key'
console.log(map.has("string")); // true
// Iteration
for (let [key, value] of map) {
console.log(key, value);
}
// Convert to array
const arr = [...map]; // [[key1, value1], [key2, value2], ...]
const keys = [...map.keys()];
const values = [...map.values()];
// Object usage
const obj = {
string: "String key",
1: "Number key converts to string",
true: "Boolean key converts to string",
};
console.log(obj[1]); // 'Number key converts to string'
console.log(obj["1"]); // Same as aboveUse cases:
- Use Map: Need non-string keys, frequent add/delete, need to maintain insertion order
- Use Object: Simple key-value storage, need JSON serialization, interacting with JSON APIs
Interview Question 21: Applications of WeakMap and WeakSet â
Question: What are the characteristics of WeakMap and WeakSet? What scenarios are they suitable for?
Answer:
WeakMap characteristics:
- Keys must be objects
- Keys are weak references, don't prevent garbage collection
- Not iterable, no
sizeproperty
WeakSet characteristics:
- Values must be objects
- Values are weak references
- Not iterable
Application scenarios:
// 1. Store private data
const privateData = new WeakMap();
class Person {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}
const person = new Person("John");
console.log(person.getName()); // 'John'
// After person object is destroyed, corresponding data in privateData will be collected
// 2. Cache computation results
const cache = new WeakMap();
function complexCalculation(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = /* complex calculation */ obj.value * 2;
cache.set(obj, result);
return result;
}
// 3. DOM node associated data
const elementData = new WeakMap();
function attachData(element, data) {
elementData.set(element, data);
}
function getData(element) {
return elementData.get(element);
}
// When DOM node is removed, associated data will be automatically collected
// 4. Using WeakSet to track objects
const visitedObjects = new WeakSet();
function traverse(obj) {
if (visitedObjects.has(obj)) {
return; // Avoid circular references
}
visitedObjects.add(obj);
// Process object...
}11. Performance Optimization â
Interview Question 22: Implementing Debounce and Throttle â
Question: Please implement debounce and throttle functions.
Answer:
// Debounce: Execute only after n seconds of event trigger; if triggered again within n seconds, restart timer
function debounce(func, wait) {
let timeout;
return function (...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
// Use cases: search box input, window resize
const handleInput = debounce(function (e) {
console.log("Search:", e.target.value);
}, 500);
document.querySelector("#search").addEventListener("input", handleInput);
// Throttle: Execute only once within n seconds, repeated triggers won't restart timer
function throttle(func, wait) {
let timeout;
let previous = 0;
return function (...args) {
const context = this;
const now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
};
}
// Timestamp version (immediate execution)
function throttleTimestamp(func, wait) {
let previous = 0;
return function (...args) {
const now = Date.now();
if (now - previous > wait) {
func.apply(this, args);
previous = now;
}
};
}
// Timer version (delayed execution)
function throttleTimeout(func, wait) {
let timeout;
return function (...args) {
if (!timeout) {
timeout = setTimeout(() => {
func.apply(this, args);
timeout = null;
}, wait);
}
};
}
// Use cases: scroll events, mouse movement
const handleScroll = throttle(function () {
console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", handleScroll);Application scenarios comparison:
| Function | Characteristics | Application scenarios |
|---|---|---|
| Debounce | Execute after last trigger | Search input, form validation, window resize |
| Throttle | Execute at fixed intervals | Scroll loading, mouse movement, playback progress |
Interview Question 23: Common Memory Leak Scenarios â
Question: What are common memory leak scenarios in JavaScript? How to avoid them?
Answer:
Common memory leak scenarios:
// 1. Accidental global variables
function createLeak() {
leak = "I'm a global variable"; // Not using var/let/const
this.anotherLeak = "Another leak"; // In non-strict mode, this points to window
}
// Solution: Use strict mode, declare variables
("use strict");
function noLeak() {
const safe = "Safe local variable";
}
// 2. Forgotten timers
const data = fetchLargeData();
const timer = setInterval(() => {
const node = document.getElementById("node");
if (node) {
node.innerHTML = JSON.stringify(data);
}
}, 1000);
// Solution: Clear timers in time
function cleanup() {
clearInterval(timer);
}
// 3. Closure references
function outer() {
const largeData = new Array(1000000);
return function inner() {
console.log("Using closure");
// largeData will always be referenced, cannot be collected
};
}
const fn = outer();
// Solution: Remove reference when not needed
fn = null;
// 4. DOM references
const elements = {
button: document.getElementById("button"),
};
function removeButton() {
document.body.removeChild(elements.button);
// elements.button still references removed DOM
}
// Solution: Delete reference
function removeButtonProperly() {
document.body.removeChild(elements.button);
delete elements.button;
}
// 5. Event listeners not removed
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
document.addEventListener("click", this.handleClick);
}
handleClick() {
console.log("clicked");
}
destroy() {
// Must remove event listeners
document.removeEventListener("click", this.handleClick);
}
}Methods to detect memory leaks:
- Chrome DevTools Memory Profiler
- Performance Monitor to monitor memory usage
- Using
performance.memoryAPI
12. Advanced Function Techniques â
Interview Question 24: Function Currying Implementation â
Question: What is function currying? Please implement a generic currying function.
Answer:
Currying: Process of converting a multi-parameter function into a series of single-parameter functions.
// Simple example
function add(a, b, c) {
return a + b + c;
}
// After currying
function curriedAdd(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6
// Generic currying function implementation
function curry(fn) {
return function curried(...args) {
// If enough arguments, directly call original function
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// Otherwise return a new function to continue collecting arguments
return function (...nextArgs) {
return curried.apply(this, [...args, ...nextArgs]);
};
};
}
// Usage example
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
// Practical application scenarios
const multiply = (a, b, c) => a * b * c;
const curriedMultiply = curry(multiply);
const double = curriedMultiply(2);
const doubleAndTriple = double(3);
console.log(doubleAndTriple(4)); // 24Interview Question 25: Implementing bind, call, apply â
Question: Manually implement Function.prototype.bind, call, and apply methods.
Answer:
// Implement call
Function.prototype.myCall = function (context, ...args) {
// If no context passed, default to global object
context = context || globalThis;
// Create unique property name to avoid overwriting
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
// Call function
const result = context[fnSymbol](...args);
// Delete temporary property
delete context[fnSymbol];
return result;
};
// Implement apply
Function.prototype.myApply = function (context, args = []) {
context = context || globalThis;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
};
// Implement bind
Function.prototype.myBind = function (context, ...bindArgs) {
const fn = this;
return function (...callArgs) {
// Merge arguments
const args = [...bindArgs, ...callArgs];
// If called with new, this points to new object
if (this instanceof fn) {
return new fn(...args);
}
// Otherwise use specified context
return fn.apply(context, args);
};
};
// Test
const obj = { name: "John" };
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
console.log(greet.myCall(obj, "Hello", "!")); // Hello, John!
console.log(greet.myApply(obj, ["Hello", "!"])); // Hello, John!
const boundGreet = greet.myBind(obj, "Hello");
console.log(boundGreet("!")); // Hello, John!13. Error Handling â
Interview Question 26: Performance Impact of try-catch â
Question: What performance impact does try-catch have? How to handle errors gracefully?
Answer:
Impact of try-catch:
- Modern JavaScript engines have optimized try-catch performance
- Main overhead is in stack tracing when throwing errors
- Don't overuse in performance-critical code
Best practices:
// 1. Use conditional checks instead of try-catch
// Not recommended
function getUserAge(user) {
try {
return user.profile.age;
} catch (e) {
return null;
}
}
// Recommended
function getUserAge(user) {
return user?.profile?.age ?? null;
}
// 2. Centralized error handling
class ErrorHandler {
static handle(error, context = "") {
console.error(`[${context}]`, error);
// Handle based on error type
if (error instanceof TypeError) {
// Type error handling
} else if (error instanceof NetworkError) {
// Network error handling
}
// Report error
this.report(error);
}
static report(error) {
// Send to error tracking service
}
}
// 3. Custom error classes
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError("Email cannot be empty", "email");
}
}
try {
validateUser({});
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Field ${error.field} validation failed: ${error.message}`);
}
}
// 4. Promise error handling
async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new NetworkError("Request failed", response.status);
}
return await response.json();
} catch (error) {
ErrorHandler.handle(error, "fetchData");
throw error; // Re-throw for caller to handle
}
}
// 5. Global error capture
window.addEventListener("error", (event) => {
console.error("Global error:", event.error);
ErrorHandler.handle(event.error, "global");
});
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled Promise rejection:", event.reason);
ErrorHandler.handle(event.reason, "unhandledPromise");
});Summary â
JavaScript interview questions typically revolve around these core concepts:
1. Data Types and Conversion: Understand JavaScript's type system and implicit conversion rules
2. Scope and Closures: Master function scope, closure formation and applications
3. this Keyword: Understand this binding rules in different calling methods
4. Prototypes and Inheritance: Master prototype chain, inheritance implementation approaches and application scenarios
5. ES6+ Features: Familiarize with arrow functions, destructuring, Promise, async/await, etc.
6. Array and Object Operations: Proficiently use array methods and object operation techniques
7. Event Loop Mechanism: Understand execution order and timing of macrotasks and microtasks
8. Module Systems: Master differences and applications of ES Modules and CommonJS
9. DOM Operations and Events: Familiarize with event delegation, event bubbling and capture mechanisms
10. Data Structures: Understand characteristics and applications of Map, Set, WeakMap, WeakSet
11. Performance Optimization: Master debounce, throttle, memory management and other optimization techniques
12. Advanced Function Techniques: Understand currying, implementation principles of bind/call/apply
13. Error Handling: Master graceful error handling approaches and best practices
Interview suggestions:
- Understand principles: Not only know the results, but understand the underlying principles
- Practice hands-on: Write code yourself to deepen understanding
- Think beyond: Consider edge cases in different scenarios
- Connect to practice: Relate knowledge points to actual development scenarios
- Systematic learning: Learn systematically by topics, not fragmentary memorization
- Focus on depth: Deeply understand core concepts, not just memorize answers
Mastering these JavaScript core concepts will help you demonstrate solid technical foundation in interviews and avoid common pitfalls and errors in daily development.