Skip to content

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?

javascript
console.log([] == ![]); // Question 1
console.log([] == ![]); // Question 2
console.log([] == ![]); // Question 3

Answer:

true
true
false

Explanation: This question tests JavaScript's type conversion rules and operator precedence:

  1. == vs ===:

    • == performs type coercion
    • === does not perform type coercion, directly comparing values and types
  2. Logical NOT operator !:

    • ![] converts empty array to boolean false, then negates to true
    • ![] converts to false, so [] == ![] is equivalent to [] == false, result is true
  3. Type conversion mechanism:

javascript
// Array to boolean
Boolean([]); // false
Boolean({}); // true

// Number to string
String(123); // "123"

// Boolean to number
Number(true); // 1
Number(false); // 0

Extended 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?

javascript
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. [] + 1: Empty array converts to number 0, 0 + 1 = 1
  2. 1 + "1": Number plus string, 1 + "1" = "11"
  3. "foo" + + "bar":
    • "foo" + +converts+to string, getting"foo+"`
    • +"bar" string concatenation, "foo+" + "bar" = "fooNaNbar"
  4. + + +: Unary +, converts to number NaN
    • "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?

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

Answer:

3
3
3

Explanation: This question tests JavaScript's scope and closure concepts:

  1. var declaration: var has function scope, but variables declared in loops are hoisted
  2. Closure formation: Each setTimeout callback function forms a closure, capturing variable i
  3. Asynchronous execution timing: setTimeout is asynchronous, all callbacks execute after the loop finishes
  4. Variable value issue: Since all callbacks share the same variable i, and i equals 3 when async executes

Solutions:

javascript
// 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?

javascript
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()); // 4

Answer: Implements two independent counters, but the problem is that both counters share the same count variable.

Explanation:

  1. Closure characteristics: Each returned function accesses the count variable of the outer function
  2. Variable sharing issue: Due to var's function scope characteristic, both counters actually share the same variable
  3. Expected behavior: Developers might expect each counter to work independently

Correct implementation:

javascript
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?

javascript
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 6

Answer:

"object"
"global"
"called"
"global"
"object"
"called"

Explanation: This question comprehensively tests JavaScript's this binding rules:

  1. Object method invocation: this in obj.getName() points to obj
  2. Arrow functions: Arrow functions don't have their own this, capturing outer scope's this
  3. call/apply invocation: Explicitly specifies this binding
  4. Function assignment call: When a function is called as a method of an independent object, this points to the global object (or undefined in strict mode)

this binding rules summary:

  • Default binding: Direct function call, this points to global object or undefined
  • Implicit binding: Calling through an object, this points to that object
  • Explicit binding: Using call, apply, bind to specify this
  • new binding: Using new to call constructor, this points to newly created object
  • Arrow functions: Inherit this from outer scope

Interview Question 6: this in Constructors ​

Question: What is the output of the following code?

javascript
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 3

Answer:

"John"
"Sarah"
"John"

Explanation:

  1. this in constructors: this in new Person("John") points to the newly created object instance
  2. this in prototype methods: this in p1.sayName() points to the object calling the method, p1
  3. Effect of apply method: p1.sayName.apply(p2) points method's this to p2, but the name property on prototype chain is still p1's

4. Prototypes and Inheritance ​

Interview Question 7: Prototype Chain Lookup ​

Question: What is the output of the following code?

javascript
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 2

Answer:

"Hello, I'm John"
"Hello, I'm John, age undefined"

Explanation:

  1. Prototype chain lookup: When accessing object properties, first look in the object itself; if not found, look up the prototype chain
  2. Property shadowing: Adding sayHello method to person object shadows the method with the same name on the prototype
  3. Prototype sharing: All instances share the same prototype object

Interview Question 8: Implementing Prototypal Inheritance ​

Question: How to manually implement JavaScript inheritance?

javascript
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 Parent

Answer:

javascript
// 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?

javascript
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 4

Answer:

"John"
undefined
"Sarah"
undefined

Explanation:

  1. Arrow function characteristics: Arrow functions don't have their own this, arguments, and cannot be used as constructors
  2. this binding:
    • Regular function's this is determined by calling method
    • Arrow function's this is determined at definition time and cannot be changed
  3. Lexical scope binding: Arrow function's this binds to the lexical scope at definition

Interview Question 10: Destructuring and Spread Operator ​

Question: What is the output of the following code?

javascript
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 2

Explanation:

  1. Spread operator ...: Used to expand arrays or objects
  2. Destructuring assignment: Extract values from arrays or objects and assign to variables
  3. Rest parameters: Use ...rest syntax to collect remaining parameters
  4. 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?

javascript
// 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
promise

Explanation:

  1. Event loop mechanism: Synchronous code > Microtasks (Promise.then) > Macrotasks (setTimeout)
  2. async/await essence: Syntactic sugar, still Promise underneath
  3. 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

javascript
// 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:

javascript
// 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?

javascript
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 operator

Answer:

New York
New York
Paris
Tokyo

Explanation:

  1. Shallow copy: Only copies first level properties, nested objects remain references
    • Object.assign()
    • Spread operator ...
  2. Deep copy: Recursively copies all levels of properties, creating completely independent objects
    • JSON.parse(JSON.stringify())
    • Recursive function implementation

Deep copy implementation:

javascript
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?

javascript
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
5

Explanation:

  1. Call stack: Synchronous code executes first (1, 6)
  2. Microtask queue: Promise.then is a microtask, executes immediately after current macrotask completes
  3. 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:

javascript
// 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?

javascript
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
setTimeout

Explanation:

  1. async/await essence: Code after await is equivalent to executing in Promise.then
  2. 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:

FeatureES ModulesCommonJS
Load timeCompile-time (static)Runtime (dynamic)
ExportValue referenceValue copy
thisundefinedCurrent module
Circular dependenciesCan handleMay have issues
Use casesBrowser, modern Node.jsNode.js (traditional)
Import syntaximport / exportrequire / module.exports

Code examples:

javascript
// 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:

javascript
// 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:

javascript
// 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:

javascript
// 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 tree
  • preventDefault(): 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:

FeatureMapObject
Key typesAny typeString or Symbol
Key orderInsertion orderNot guaranteed (ES2015+ partially ordered)
Sizemap.sizeObject.keys(obj).length
IterationDirectly iterableNeeds Object.keys()
PerformanceBetter for frequent add/deleteSufficient for simple cases
SerializationNo JSON supportSupports JSON

Code examples:

javascript
// 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 above

Use 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 size property

WeakSet characteristics:

  • Values must be objects
  • Values are weak references
  • Not iterable

Application scenarios:

javascript
// 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:

javascript
// 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:

FunctionCharacteristicsApplication scenarios
DebounceExecute after last triggerSearch input, form validation, window resize
ThrottleExecute at fixed intervalsScroll 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:

javascript
// 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.memory API

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.

javascript
// 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)); // 24

Interview Question 25: Implementing bind, call, apply ​

Question: Manually implement Function.prototype.bind, call, and apply methods.

Answer:

javascript
// 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:

javascript
// 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.

Last updated: