SWE

h232ch·2024년 2월 9일
0
post-thumbnail

Introduction

Unlike typical software engineer job interviews, front end interviews are focus on the front end domain(HTML, CSS, Javascript, Angular, React just to name a few) knowledges and applying them to real-world scenarios. It doesn't mean you don't have to be familiar with basic data structures and algorithmic concepts. There is just less emphasis on them.

General tips for front end interviews

You need to be extremely strong in your front end fundamentals and contantly display mastery of them to your interviewer

Be extremely familiar with the following concepts

  • CSS : Specificity, Box model, Layout, Positioning
  • Javascript : this keyword, Prototypes, closures, Async-style code, Promises, Timers(setTimeout, setInterval)
  • Javascript design pattern : Observer pattern, Module pattern
  • HTML : Event delegation (it was useful in almost every interview), DOM traversal, DOM Manipulation, Form validation and submission
  • Vanilla JS, or jQuery at the very least. Not all interview allow you to use React or Angular as they want to see mastery of the fundamentals

Front end interview formats

Trivia questions

Short questions which test knowledge and have clear non-subjective answers.

  • Javascript
    • What is a closure
    • what is the diffence between a promise and a callback
    • Explain this keyword in JavaScript
  • CSS
    • What is the CSS box model?
    • Various CSS position properties and its differences.

Javascript coding

This is the front end version of LeetCode-style algorithm questions. Implement a function in Javascript, which can be a utility function found in Lodash/Underscore(e.g throttle) or a polyfill for the JavaScript language/DOM APIs(e.g Array.prototype.filter(), promise.all(), document.getElementsByClassName()).

  • Implement Array.prototype functions: map, reduce, filter, sort.
  • Implement DOM APIs: document.getElementsByClassName, document.getElementsByTagName.
  • debounce/throttle.
  • Implement Promise/Promse-related APIs: Promise, Promise.all, Promise.any.

User interface coding

Build user interfaces(can be a UI component, an app, or a game) using HTML, CSS and Javascript.

  • Components
    • Autocomplete (very popular)
    • Photo gallery
    • Image carousel
    • Tooltip component
    • Star rating widget
  • Apps
    • Build tic-tac-toe
    • Build a chess board with movable pieces

Algorithmic coding

There are LeetCode-style argirithmic coding questions which evaluate your core data structures and algorithms skills. You can be asked any question on LeetCode and might be asked to complete them using JavaScipt.

System Design

Describe and discuss how you would build a component/app/game and its architecture. this is the front end version of system design questions. E.g. Describe how you build some of components like Emoji, Stars in a chat app, what APIs it would have, what component there are to the feature, how to ensure it has good performance, UX, etx.

  • Design an image carousel component
  • Design an email client application

Trivia

They ask trivia questions to test how well candidates understand the theory behind the domain. You can be aced by either having real solid understanding of the concepts or by pure memorization of answers.

Basic examples

  • Javascript
    • What is a closure
    • what is the diffence between a promise and a callback
    • Explain this keyword in JavaScript
  • CSS
    • What is the CSS box model?
    • Various CSS position properties and its differences.

You can find out various trivia question in here

Advanced examples

For more sinior candidates, expect to explain more adevanced stuff with no absolute answers.

  • How does React or Angular work? what is virtual DOM, and what problem does it solve?
  • Why did you sue library X over libray Y?

The best way understands the questions is to really understand the concepts and get some hands-on experience in applying them in a project. but if you don't have enough time, memorize the answers.

JavaScript questions

Explain event delegation

Event bubbling

When an event occurs in a specific element, the event will forward to ancestors of the element.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <link rel="stylesheet" href="common.css">
</head>
<body>
  <div>
    <p>TEST</p>
  </div>
  <script>
    const $p = document.querySelector('p');
    const $div = document.querySelector('div');
    const $body = document.querySelector('body');

    function Alert(message) {
      return function () {
        alert(message);
      };
    }

    $p.addEventListener('click', Alert('p tag event'));
    $div.addEventListener('click', Alert('div tag event'));
    $body.addEventListener('click', Alert('body tag event'));
  </script>
</body>
</html>

If you click the <p> tag's text, Event bubbling will be executed and the event will forward to ancestors of the element like below. You can check the alert() occurs three times.

p > div > body 

Target element
It's the first element which the event occurred in event bubbling. the p tag is the target element above example. You can access this with event.target.

You can understand a target element and a current element.

const $body = document.querySelector('body')

function Alert(event) {
  alert(`Target elem: ${event.target.tagName} 
         Current elem: ${this.tagName}`)
}

$body.addEventListener('click', Alert)

The this indicates $body and it's a <body> element which is declared by const $body = document.querySelector('body'). You can check the target element changed as you click body or descendant elements of this but the current element will not changed as the event occurred from the current element. The bubbling event will occur since you click the body or descendant elements of this.

Event capturing

When an event occurs in a specific element, the event forwards from an ancestor of the element which the event occurred. You can turn on the event capturing with {capture: true} or {true} on a event handler(addEventListener). As default value is {false} you need to turn on this option when you use event capturing.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <link rel="stylesheet" href="common.css">
</head>
<body>
<div>
  <p>TEST</p>
</div>
<script>
  const elements = document.querySelectorAll('*') // Select all the elements

  for (let elem of elements) {
    elem.addEventListener('click', e => alert(`캡쳐링: ${elem.tagName}`), true) // Capturing
    // elem.addEventListener('click', e => alert(`버블링: ${elem.tagName}`)) // Bubbling
  }

  // body.addEventListener('click', alert(${body.tagName}), true);
  // div.addEventListener('click', alert(${div.tagName}), true);
  // .........

</script>
</body>
</html>

Event delegation

<button id="parent">
  <span>Button 1</span>
  <span>Button 2</span>
  <span>Button 3</span>
</button>

<script>
document.getElementById('parent').addEventListener('click', function(event) {
  if (event.target.tagName === 'SPAN') {
    alert('Button clicked: ' + event.target.textContent);
  }
});
</script>

Without event delegation

<!-- Without Event Delegation Example -->
<button id="button1">Button 1</button>
<button id="button2">Button 2</button>
<button id="button3">Button 3</button>

<script>
// Individual event listeners for each button
document.getElementById('button1').addEventListener('click', function() {
  alert('Button 1 clicked');
});

document.getElementById('button2').addEventListener('click', function() {
  alert('Button 2 clicked');
});

document.getElementById('button3').addEventListener('click', function() {
  alert('Button 3 clicked');
});
</script>

Event delegation is a technique involving adding event listeners to a parent element instead of adding them to the descendant elements. The listener will fire whenever the event is triggered on the descendant elements due to event bubbling up the DOM. The benefits of this technique are:

  • Memory footprint goes down because only one single handler is needed on the parent element, rather than having to attach event handlers on each descendant.
  • There is no need to unbind the handler from elements that are removed and to bind the event for new elements.

Bind

// Example: Bind a click event handler to an element with ID 'myButton'
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log('Button clicked!');
});

Unbind

// Example: Unbind (remove) the click event handler from the element with ID 'myButton'
const button = document.getElementById('myButton');
function clickHandler() {
  console.log('Button clicked!');
}
button.addEventListener('click', clickHandler);

// Unbind the click handler after a certain condition is met
if (someCondition) {
  button.removeEventListener('click', clickHandler);
}

Benefits in detail (Optional)

Improved Performance:

<!-- Improved Performance Example -->
<button id="parent">
  <span>Button 1</span>
  <span>Button 2</span>
  <span>Button 3</span>
</button>

<script>
document.getElementById('parent').addEventListener('click', function(event) {
  if (event.target.tagName === 'SPAN') {
    alert('Button clicked: ' + event.target.textContent);
  }
});
</script>

Explanation:

  • The click event listener is added to a common ancestor (<button id="parent">) instead of individual <span> elements.
  • When a <span> inside the button is clicked, the event bubbles up to the parent button, and the listener checks if the clicked target is a <span>. If true, it displays an alert.
  1. Dynamic Content:
<!-- Dynamic Content Example -->
<button id="parent">Add Button</button>

<script>
document.getElementById('parent').addEventListener('click', function() {
  const newButton = document.createElement('button');
  newButton.textContent = 'New Button';
  document.body.appendChild(newButton);
});
</script>

Explanation:

  • Clicking the "Add Button" button dynamically creates a new <button> element (newButton) with the text "New Button."
  • The click event listener on the parent button is handling events for dynamically added elements without additional code.

If you don't use Event Delegation, You should make more code which makes the dynamic contents

// Example of adding dynamic content
function addNewItem() {
  const parentList = document.getElementById('parentList');
  const newItem = document.createElement('li');
  newItem.textContent = 'New Item';
  parentList.appendChild(newItem);
}
  1. Reduced Memory Consumption:
<!-- Reduced Memory Consumption Example -->
<div id="parent">
  <button>Button 1</button>
  <button>Button 2</button>
  <button>Button 3</button>
</div>

<script>
const parent = document.getElementById('parent');
parent.addEventListener('click', function(event) {
  if (event.target.tagName === 'BUTTON') {
    alert('Button clicked: ' + event.target.textContent);
  }
});
</script>

Explanation:

  • The click event listener is attached to the parent <div> instead of individual <button> elements.
  • This reduces memory consumption because there's only one event listener for multiple buttons, improving performance when dealing with a large number of elements.
  1. Simplified Code:
<!-- Simplified Code Example -->
<div id="parent">
  <button>Button 1</button>
  <button>Button 2</button>
  <button>Button 3</button>
</div>

<script>
const parent = document.getElementById('parent');
parent.addEventListener('click', function(event) {
  if (event.target.tagName === 'BUTTON') {
    alert('Button clicked: ' + event.target.textContent);
  }
});
</script>

Explanation:

  • The code is simplified by using a single event listener on the parent <div> to handle clicks on multiple <button> elements.
  • This approach is more readable and easier to maintain than attaching listeners to each individual button.
  1. Ease of Maintenance:
<!-- Ease of Maintenance Example -->
<div id="parent">
  <button>Button 1</button>
  <button>Button 2</button>
  <button>Button 3</button>
</div>

<script>
const parent = document.getElementById('parent');
parent.addEventListener('click', function(event) {
  if (event.target.tagName === 'BUTTON') {
    alert('Button clicked: ' + event.target.textContent);
  }
});

// Simulating dynamic addition of elements
setTimeout(() => {
  const newButton = document.createElement('button');
  newButton.textContent = 'New Button';
  parent.appendChild(newButton);
}, 2000);
</script>

Explanation:

  • The event listener on the parent <div> remains effective even after dynamically adding a new <button>.
  • The code is easier to maintain because you don't need to update event listeners when elements are added or removed dynamically.
  1. Avoiding Callback Hell:
<!-- Avoiding Callback Hell Example -->
<div id="parent">
  <button>Button 1</button>
  <div>
    <button>Button 2</button>
  </div>
  <button>Button 3</button>
</div>

<script>
const parent = document.getElementById('parent');
parent.addEventListener('click', function(event) {
  if (event.target.tagName === 'BUTTON') {
    alert('Button clicked: ' + event.target.textContent);
  }
});
</script>

Explanation:

  • The event listener on the parent <div> handles clicks on all <button> elements, even if they are nested inside other elements.
  • This avoids excessive nesting of callbacks, making the code more readable and manageable.

These examples illustrate how event delegation simplifies code, improves performance, and enhances maintainability, especially in scenarios with dynamic content or complex nested structures.

Reference

Explain how this works in JavaScript

In JavaScript, the this keyword is a special identifier that is used to refer to the current object or context within which a function is executed. The value of this is determined by how a function is called.

<script>
  const myObject = {
    data: "hello",
    me: this,
    myAnMethod: () => {
      console.log(this)
    },
    myMethod: function () {
      console.log(this)
    }
  }

  function ES5Fun() {
    this.data = "hello?";
    this.printName = () => {
      console.log(this.data);
    }
  }

  function ES6Fun() {
    this.data = "hello?";
    this.self = this;
    this.printName = function () {
      console.log(this.self.data);
    }
  }

  myObject.myAnMethod() // When `this` is called as object, `this` keyword refers to parent object
  myObject.myMethod()
  console.log(myObject.me) // It refers to global value `window` because myObject's parent is `window` object

  const es5 = new ES5Fun(); // When the function is called as constructor, `this` keyword refers itself.
  es5.printName();
  const es6 = new ES6Fun(); // When the function is called as constructor, `this` keyword refers itself.
  es6.printName();

</script>


There is Call Stack when script is executed. Global Execution Context is always located in the bottom of the Call Stack and If you exectue Inner Function of Global Function, It will be located onto the Global Function. That is why this keyword changes depending on where function is called.

Call Stack:

1. innerFunction()    <-- Currently running, the context of innerFunction
2. globalFunction()   <-- Below on the stack, the Global Execution Context

this keyword changes depending on where function is called. Understanding how the this keyword works is esssential in JavaScript programming.

Here are some key points about the this keyword:

Global Context:

  • In the global context (outside any function), this refers to the global object. In a browser environment, the global object is typically window.

      console.log(this); // Refers to the global object (e.g., window in a browser)

Function Context:

  • Inside a function, the value of this depends on how the function is called.

  • When a function is called as a method of an object, this refers to the object that the method was called on.

    const obj = {
      name: 'Example',
      logName: function() {
        console.log(this.name); // Refers to the obj object
      }
    };
    
    obj.logName(); // Output: Example

Constructor Functions:

  • When a function is used as a constructor with the new keyword, this refers to the newly created object.

    function Person(name) {
      this.name = name;
    }
    
    const person1 = new Person('John');
    console.log(person1.name); // Output: John

Event Handlers:

  • In event handler functions, this often refers to the element that triggered the event.

    document.getElementById('myButton').addEventListener('click', function() {
      console.log(this); // Refers to the button element
    });

Arrow Functions:

  • Arrow functions do not have their own this context. Instead, they inherit the this value from the enclosing scope (lexical scoping).

    const obj = {
      name: 'Example',
      logName: function() {
        setTimeout(() => {
          console.log(this.name); // Still refers to the obj object
        }, 1000);
      }
    };
    
    obj.logName(); // Output (after 1 second): Example

Understanding the this keyword is crucial for working with JavaScript, especially in object-oriented and event-driven programming. The value of this is dynamic and can change based on the way a function is invoked.

Context

The "context" in JavaScript refers to the environment or scope in which a piece of code is executed. The value of the this keyword is dynamically determined based on this context. Let's go through an example to better understand the concept of context:

// Global context
const globalObject = {
  globalProperty: 'I am in the global context',
  globalMethod: function() {
    console.log(this.globalProperty);
  }
};

function globalFunction() {
  console.log(this); // 'this' refers to the global object (window in a browser)
}

globalObject.globalMethod(); // Outputs: 'I am in the global context'
globalFunction();

// Object context
const myObject = {
  objectProperty: 'I am in the object context',
  objectMethod: function() {
    console.log(this.objectProperty);
  }
};

myObject.objectMethod(); // Outputs: 'I am in the object context'

// Changing context using call() and apply()
const anotherObject = {
  anotherProperty: 'I am in another object context'
};

globalObject.globalMethod.call(anotherObject); // Outputs: 'I am in another object context'

In this example:

Global Context:

  • The global context is the default context when code is executed globally. The value of this in the global context is the global object (window in a browser environment).
  • The globalObject.globalMethod method and the globalFunction function illustrate the use of this in the global context.

Object Context:

  • The myObject object defines its own context. When the objectMethod method is called, this inside the method refers to myObject.
  • The value of this is determined by how the method is called.

Changing Context using call() and apply():

  • The call() and apply() methods allow you to explicitly set the value of this for a function when it's called. In the example, globalObject.globalMethod.call(anotherObject) changes the context of globalObject.globalMethod to anotherObject.

Understanding the context is crucial because it influences the behavior of this within functions and methods. The context is dynamic and depends on how a function is invoked or a method is called.

Use cases

Let's dive into two common cases where the this keyword is used in JavaScript: within object methods and within event handlers.

Case 1: this in Object Methods
In JavaScript, when a function is a method of an object, the this keyword inside that function refers to the object itself. This allows the method to access and manipulate the properties of the object.

const myObject = {
  property: 'Hello',
  method: function() {
    console.log(this.property); // 'this' refers to myObject, outputs 'Hello'
  }
};

myObject.method(); // Calling the method

In this example, myObject has a property called property and a method called method. When method is called using myObject.method(), the this keyword inside the method refers to myObject, so this.property is equivalent to myObject.property, and it outputs 'Hello'.

Case 2: this in Event Handlers
In the context of event handlers, such as those used with addEventListener, the this keyword refers to the DOM element to which the event handler is attached. This allows the event handler to work with the specific element that triggered the event.

<button id="myButton">Click me</button>
<script>
  const myButton = document.getElementById('myButton');

  myButton.addEventListener('click', function() {
    console.log(this.id); // 'this' refers to myButton, outputs 'myButton'
  });
</script>

In this example, there is an HTML button with the ID 'myButton'. The JavaScript code attaches a click event listener to the button. Inside the event listener function, this refers to the button element (myButton). So, this.id is equivalent to myButton.id, and it outputs 'myButton' when the button is clicked.

These examples illustrate how the this keyword dynamically references the context in which a function is executed, whether it's a method of an object or an event handler attached to a DOM element.

Event handler this working procedure

Event Handling with addEventListener:

  • When you use addEventListener to attach an event handler function to a DOM element, the event handler function is not executed immediately. It is executed later, in response to a specific event (such as a click) on the DOM element.

Anonymous Function as Event Handler:

  • In the example I provided:

    const myButton = document.getElementById('myButton');
    
    myButton.addEventListener('click', function() {
      console.log(this.id); // 'this' refers to myButton
    });
  • The anonymous function provided to addEventListener becomes the event handler.

  • When the 'click' event occurs on the myButton element, the anonymous function is executed.

Dynamic this Binding:

  • The key point is that the value of this inside the event handler function is dynamically bound to the DOM element on which the event occurred.
  • In this case, this inside the anonymous function refers to the myButton element because the event handler is attached to myButton.

Accessing Element Properties:

  • Inside the event handler function, this.id accesses the id property of the myButton element.

The event handler function is executed in the context of the DOM element to which the event listener is attached (in this case, myButton). This dynamic binding of this allows you to work with the specific element that triggered the event within the event handler function.

Why you should be careful when you use this keyword with arrow function

One common scenario where using an arrow function as an event handler is not suitable when you need to access the dynamically bound this value inside the handler. Arrow functions don't have their own this context; instead, they inherit it from the surrounding lexical scope. This means they are not ideal for situations where the value of this should be determined dynamically based on the object or element on which the method or event handler is called.

Let's consider an example where you have an object method that you want to use as an event handler:

const myObject = {
  data: "Hello from myObject",
  handleClick: function() {
    console.log(this.data);
  }
};

document.getElementById('myButton').addEventListener('click', myObject.handleClick);

In this example, myObject.handleClick is a regular function, and it is used as an event handler for a button click. When the button is clicked, handleClick is executed, and inside the function, this refers to myObject, allowing you to access the data property dynamically.

Now, let's see what happens if you use an arrow function:

const myObject = {
  data: "Hello from myObject",
  handleClick: () => {
    console.log(this.data); // 'this' is not dynamically bound to myObject
  }
};

document.getElementById('myButton').addEventListener('click', myObject.handleClick);

In this case, the arrow function used as an event handler does not have its own this binding. Instead, it inherits this from the surrounding lexical scope, which, in this context, is likely to be the global object. This results in this.data being undefined or not referring to the myObject as expected.

In summary, when the value of this needs to be dynamically bound based on the object or element on which the method or event handler is called, it's more appropriate to use a regular function rather than an arrow function.

How lexical scope works?

When you use an arrow function as an inner function within an object, it inherits the this value from its lexical scope, which is the scope in which the arrow function is defined. In the context of an object method, this can lead to unexpected behavior because the arrow function does not have its own this context. Instead, it looks up the chain to find the this value from its enclosing lexical scope.

Let's look at an example to illustrate this behavior:

const myObject = {
  data: "Hello from myObject",
  regularFunction: function() {
    console.log(this.data); // 'this' refers to myObject
  },
  arrowFunction: () => {
    console.log(this.data); // 'this' inherits from the lexical scope (likely global)
  }
};

myObject.regularFunction(); // Output: Hello from myObject
myObject.arrowFunction();   // Output: undefined or an error (depending on the environment)

In the example above, regularFunction is a regular function, and it correctly binds this to the object (myObject). However, arrowFunction is an arrow function, and its this value is inherited from the surrounding lexical scope, which is likely the global scope. As a result, attempting to access this.data in arrowFunction would either result in undefined or throw an error, depending on the environment.

To avoid such issues, especially when using this within object methods, it's recommended to use regular functions instead of arrow functions. Regular functions have their own dynamic this binding, ensuring that this refers to the object owning the method.

Arrow function with lexical scope (setTimeout)

The behavior you observe with setTimeout and arrow functions is related to how closures and lexical scoping work in JavaScript. When an arrow function is used with setTimeout, it captures the this value from its lexical scope at the time of its creation, and this captured value is used when the function is later executed. Let's look at an example to illustrate this:

const myObject = {
  data: "Hello from myObject",
  arrowFunction: function() {
    setTimeout(() => {
      console.log(this.data);
    }, 1000);
  }
};

myObject.arrowFunction();

In this example, arrowFunction is a regular function, and it contains an arrow function within the setTimeout. The arrow function inside setTimeout captures the this value from its lexical scope, which is the same as the this value of the outer arrowFunction. As a result, when the arrow function inside setTimeout is executed after the timeout period, it still has access to the correct this value, which is the myObject instance.

This behavior is different from regular functions used in a similar context. If you replaced the arrow function inside setTimeout with a regular function, you would need to use techniques like storing this in a variable (const self = this;) to ensure proper access to the object's properties.

const myObject = {
  data: "Hello from myObject",
  regularFunction: function() {
    const self = this;
    setTimeout(function() {
      console.log(self.data);
    }, 1000);
  }
};

myObject.regularFunction();

In summary, arrow functions inside setTimeout or other asynchronous callbacks in this context work well because they capture the this value from their lexical scope, avoiding the need for additional workarounds like storing this in a variable. This behavior is a result of how closures and lexical scoping function in JavaScript.

Arrow function with lexical scope (EventListener)

Certainly! The use of arrow functions in event listeners is similar to the setTimeout example. Arrow functions can be beneficial in event listeners, as they capture the this value from their lexical scope, avoiding common issues related to this binding. Here's an example:

const myObject = {
  data: "Hello from myObject",
  arrowFunction: function() {
    // Adding an event listener with an arrow function
    document.getElementById('myButton').addEventListener('click', () => {
      console.log(this.data);
    });
  }
};

myObject.arrowFunction();

In this example, the arrowFunction method of myObject sets up a click event listener on an HTML button with the id 'myButton'. The arrow function inside the event listener captures the this value from its lexical scope, which is the same as the this value of the outer arrowFunction method (i.e., the myObject instance).

This allows you to access this.data within the arrow function without having to use workarounds like storing this in a variable.

On the other hand, if you were to use a regular function in the event listener, you would typically need to handle the this context explicitly, often by storing it in a variable:

const myObject = {
  data: "Hello from myObject",
  regularFunction: function() {
    // Adding an event listener with a regular function
    const self = this;
    document.getElementById('myButton').addEventListener('click', function() {
      console.log(self.data);
    });
  }
};

myObject.regularFunction();

In this case, the self variable is used to store the correct this value, ensuring that you can access self.data within the regular function.

In summary, when using event listeners, arrow functions can simplify your code by automatically capturing the correct this value from their lexical scope, making it more convenient to work with object methods.

If arrow function can't find this keyword?

Yes, that's correct. If an arrow function doesn't find the this keyword in its immediate lexical scope (the scope in which it was defined), it will keep looking up the scope chain until it finds a this value. If the arrow function is defined at the top level (not inside any function or object), it will ultimately inherit the this value from the global object.

Here's an example to illustrate this behavior:

const globalObject = this; // In a browser environment, this would refer to the window object

const myObject = {
  data: "Hello from myObject",
  arrowFunction: () => {
    console.log(this === globalObject); // Output: true
    console.log(this.data); // Output: undefined (or an error if 'data' is not a property of the global object)
  }
};

myObject.arrowFunction();

In this example, the arrow function arrowFunction is defined within the context of the myObject object. However, since arrow functions don't have their own this binding, they inherit the this value from their lexical scope, which is the global scope in this case.

So, if an arrow function within an object doesn't find this in its immediate lexical scope (the object it's defined in), it will continue to look up the scope chain until it finds a this value. If it reaches the top level without finding a specific this value, it will inherit the this value from the global object.

Lexical scope works with closures of JavaScript

Yes, the behavior of arrow functions with regard to this and lexical scoping is closely related to how closures work in JavaScript. Closures occur when a function is defined within another function, and the inner function has access to the variables of the outer (enclosing) function, even after the outer function has finished executing.

Arrow functions, being lexically scoped, capture variables from their enclosing scope. This makes them work seamlessly with closures, allowing them to retain references to variables in their lexical scope, including this.

Let's look at an example that demonstrates the relationship between arrow functions, closures, and this:

function outerFunction() {
  const outerVar = "I'm from outerFunction";

  const arrowFunction = () => {
    console.log(outerVar);
    console.log(this === window); // 'this' still refers to the global object
  };

  return arrowFunction;
}

const closureFunction = outerFunction();
closureFunction(); // Output: I'm from outerFunction followed by 'this' being the global object

In this example, arrowFunction is defined inside outerFunction. The arrow function captures the outerVar variable from its lexical scope (which is outerFunction), forming a closure. When closureFunction is invoked, it logs the value of outerVar and checks if this is the global object. Even though the arrow function is executed outside the lexical scope of outerFunction, it still has access to the variables from its enclosing scope due to the closure mechanism.

So, in summary, arrow functions work well with closures, and their lexical scoping behavior helps maintain access to variables from their enclosing scopes, including this.

About closures

Certainly! Closures are a fundamental concept in JavaScript and occur when a function is defined within another function. A closure allows the inner function to retain access to the outer function's variables and parameters even after the outer function has finished executing. This creates a "closed-over" scope that "encloses" the variables it needs.

Let's break down the key components and characteristics of closures:

  1. Lexical Scoping:

    • JavaScript uses lexical scoping, which means that the scope of a variable is determined by its location in the source code.
    • Closures take advantage of lexical scoping to maintain access to variables from their containing (enclosing) scope.
  2. Inner Function:

    • The inner function is the function defined within another function, creating a closure.
    • The inner function has access to its own local variables, the variables of the outer function, and the global variables.
  3. Enclosing Scope:

    • The enclosing scope refers to the scope in which the outer function and its variables exist.
    • The inner function has access to the variables of its enclosing scope, even after the outer function has completed execution.
  4. Access to Outer Variables:

    • Closures allow the inner function to access and manipulate the variables of the outer function, even if the outer function has finished executing.

Here's a simple example to illustrate closures:

function outerFunction(outerVar) {
  // Inner function defined within outer function
  function innerFunction(innerVar) {
    console.log(outerVar + innerVar);
  }

  // Returning the inner function
  return innerFunction;
}

// Creating a closure by calling outer function
const closureInstance = outerFunction("Hello, ");

// Using the closure to access outer and inner variables
closureInstance("world!"); // Output: Hello, world!

In this example, outerFunction takes a parameter outerVar and defines innerFunction within it. innerFunction has access to both outerVar and its own parameter innerVar. When outerFunction is called, it returns the inner function, creating a closure. The closure, represented by closureInstance, retains access to the variables of its enclosing scope (outerVar) even after outerFunction has completed execution.

Closures are powerful and commonly used in JavaScript for various purposes, such as data encapsulation, private variables, and creating functions with persistent state. They play a crucial role in many design patterns and help developers write more modular and maintainable code.

Lexical Scope

  1. Lexical Scope:

    • Lexical scope is a property of how variable names are resolved in JavaScript.
    • It is determined by the physical placement of code in the source code file.
    • Variables are accessible within the block or function where they are declared and in any nested blocks or functions.
    function outerFunction() {
      const outerVar = "I am from outerFunction";
    
      function innerFunction() {
        console.log(outerVar); // innerFunction has access to outerVar due to lexical scope
      }
    
      innerFunction();
    }
    
    outerFunction();
  2. Closures:

    • Closures occur when a function is defined within another function, and the inner function has access to the variables of the outer function even after the outer function has completed execution.
    • Closures provide a way for inner functions to "close over" and retain references to the variables in their lexical scope.
    function outerFunction(outerVar) {
      function innerFunction(innerVar) {
        console.log(outerVar + innerVar); // innerFunction has access to outerVar from its enclosing scope
      }
    
      return innerFunction;
    }
    
    const closureInstance = outerFunction("Hello, ");
    closureInstance("world!"); // Output: Hello, world!

In this example, outerFunction returns innerFunction, and closureInstance becomes a closure. When closureInstance is invoked with the argument "world!", it still has access to the outerVar parameter from the lexical scope of the outerFunction. This ability to access variables from an outer (enclosing) scope even after the outer function has finished executing is a characteristic of closures.

You are correct, and I appreciate your attention to detail. In the first example, the term "closure" is often used more narrowly to refer specifically to situations where an inner function persists beyond the lifetime of its outer function and still has access to its enclosing scope.

In the provided example of outerFunction and innerFunction, while innerFunction has access to outerVar due to lexical scope, it doesn't meet the strict definition of a closure because it is executed immediately within the context of outerFunction, and it doesn't persist beyond that execution.

A more typical closure example might involve returning the inner function from the outer function, creating a persistent closure:

function outerFunction() {
  const outerVar = "I am from outerFunction";

  // Return inner function, creating a closure
  return function innerFunction() {
    console.log(outerVar); // innerFunction has access to outerVar from its enclosing scope
  };
}

const closureInstance = outerFunction();
closureInstance(); // Output: I am from outerFunction

Here, closureInstance is a closure because it persists beyond the execution of outerFunction and still has access to the outerVar variable. The return value of outerFunction is a function (the innerFunction), and it carries with it the reference to the variables from its enclosing scope.

In summary, while the first example demonstrates lexical scope, the use of the term "closure" is more precise when discussing situations where an inner function persists beyond the execution of its outer function.

Enclosing Scope

Absolutely, let's clarify the concept of enclosing scope:

Enclosing Scope:

  • The enclosing scope refers to the scope in which a function is defined.
  • When a function is created, it captures not only its own local scope but also the scopes of all the functions and blocks in which it is lexically defined.
  • Closures, which are formed when a function is defined within another function, have access to variables from their enclosing scope even after the outer function has completed execution.

Let's illustrate with an example:

function outerFunction(outerVar) {
  // innerFunction is defined inside outerFunction, forming a closure
  function innerFunction(innerVar) {
    console.log(outerVar + innerVar); // innerFunction has access to outerVar from its enclosing scope
  }

  return innerFunction; // outerFunction returns innerFunction, creating a closure
}

const closureInstance = outerFunction("Hello, ");
closureInstance("world!"); // Output: Hello, world!

In this example, innerFunction is defined inside outerFunction, creating a closure. The enclosing scope of innerFunction is the scope of outerFunction. When outerFunction is invoked with the argument "Hello, ", it returns innerFunction. The returned function (closureInstance) still has access to the outerVar parameter from its enclosing scope, even though outerFunction has completed execution. The closure allows innerFunction to "remember" the environment in which it was created, including its enclosing scope.

So, the enclosing scope is essentially the scope in which a function is declared or defined, and closures allow functions to retain access to that enclosing scope even when the outer function has finished executing.

Lexical Scope vs Enclosing Scope

Certainly, let's break down the differences between lexical scope and enclosing scope:

  1. Lexical Scope:

    • Definition: Lexical scope refers to how variable names are resolved in JavaScript based on the physical placement of code in the source code file.

    • Determination: The scope of a variable is determined by its location in the source code, and it is based on the physical structure of the code.

    • Access Rules: Variables are accessible within the block or function where they are declared and in any nested blocks or functions. Inner functions have access to the variables of their outer functions.

    • Example:

      function outerFunction() {
        const outerVar = "I am from outerFunction";
      
        function innerFunction() {
          console.log(outerVar); // innerFunction has access to outerVar due to lexical scope
        }
      
        innerFunction();
      }
      
      outerFunction();
  2. Enclosing Scope (Closures):

    • Definition: Enclosing scope refers to the scope in which a function is defined. Closures are formed when a function is defined within another function, and the inner function has access to the variables of the outer function even after the outer function has finished executing.

    • Determination: It's determined by the structure of functions and the relationships between them. Closures "enclose" variables from their enclosing scopes.

    • Access Rules: Closures allow inner functions to maintain access to variables from their enclosing scopes, even when the outer function has completed execution.

    • Example:

      function outerFunction(outerVar) {
        // innerFunction is defined inside outerFunction, forming a closure
        function innerFunction(innerVar) {
          console.log(outerVar + innerVar); // innerFunction has access to outerVar from its enclosing scope
        }
      
        return innerFunction; // outerFunction returns innerFunction, creating a closure
      }
      
      const closureInstance = outerFunction("Hello, ");
      closureInstance("world!"); // Output: Hello, world!

In summary, lexical scope is a fundamental aspect of how JavaScript resolves variable names based on the physical placement of code, whereas enclosing scope, often discussed in the context of closures, specifically refers to the scope in which a function is defined. Closures leverage lexical scope to create persistent references to variables in their enclosing scopes.

How do closures work?

function outerFunc() {
  const name = "haha";
  function innerFunc() {
    console.log(name)
  }

  innerFunc()
}
outerFunc();

In the provided code, you are indeed using a function inside another function (innerFunc inside outerFunc), but it doesn't meet the strict definition of a closure because the inner function (innerFunc) is executed immediately within the context of the outer function (outerFunc). In other words, there's no persistent reference to innerFunc that survives beyond the execution of outerFunc.

Closures typically involve returning an inner function from an outer function, creating a persistent reference to that inner function. In your example, since innerFunc is invoked immediately and not returned or stored for later use, it doesn't create a closure.

Here's an example illustrating the creation of a closure:

function outerFunc() {
  const name = "haha";

  // Return the inner function, creating a closure
  return function innerFunc() {
    console.log(name);
  };
}

const closureInstance = outerFunc(); // closureInstance is now a closure
closureInstance(); // Output: haha

In this revised example, outerFunc returns innerFunc, creating a closure. The returned function (closureInstance) retains access to the name variable even though outerFunc has completed its execution.

My opinion

Enclosing scope(closures) refers lexical scope to access and store variables to use the variable even if outer functions are finished. lexical scope variables are determined by its location in the source code, and they are based on the phisical structure of the code. inner function can access the value of outer function without closures. if the value of outer function which accessed from inner function need to be used even if the outer function is finished, closures occur to help it. closures access the value and store it, to used the values even if the outer function is finished and popped from the stack of memory.

Can you give an example of one of the ways that working with this has changed in ES6?

ES6 allows you to use arrow functions which uses the enclosing lexical scope. This is usually convenient, but does prevent the caller from controlling context via .call or .apply—the consequences being that a library such as jQuery will not properly bind this in your event handler functions. Thus, it's important to keep this in mind when refactoring large legacy applications.

Regular function (the this value can be explicitly set using .call)

function regularFunction() {
    console.log(this.value);
  }

  const obj = {value: 42};

  regularFunction.call(obj); // Explicitly setting the 'this' context to 'obj'

Arrow function (the this value can be explicitly set using .call)

const arrowFunction = () => {
    console.log(this.value);
  };

  const obj = {value: 42};

  arrowFunction.call(obj); // 'this' inside arrow function is not affected by call()

This behavior can be both an advantage and a limitation. It's an advantage when you want to maintain the lexical scoping behavior, especially in situations like callback functions where you often want to capture the this value from the surrounding context. However, it can be a limitation if you specifically need to control the this value using .call() or .apply(), as arrow functions don't allow that kind of control. In such cases, regular functions would be more appropriate.

It's not possible to capture this in callback function located in function when it comes to ES5. At ES6, You can capture this in callback function located in function because Arrow functions inherit the this value from their surrounding lexical scope.

You should make this self variable to use this in callback functions. because callback funtions' this can't refer its own object so it refer global object.

setTimout creates its own excution context, That's why setTimeout callback function can't refer ES5Object.

ES5

// ES5 object constructor function
function ES5Object() {
  this.value = 0;

  // Method using a regular function expression
  this.incrementES5 = function() {
    var self = this; // Workaround to capture 'this'

    setTimeout(function() {
      self.value++;
      console.log('ES5:', self.value);
    }, 1000);
  };
}

const es5Instance = new ES5Object();
es5Instance.incrementES5(); // Works with the workaround

ES5 Call Stack

Call Stack:
  1. setTimeout callback (Anonymous function)
  2. incrementES5 (ES5Object instance)
  3. Global Execution Context

At this point, inside the setTimeout callback function, this.value++ encounters an issue with this, as it does not refer to the ES5Object instance. It might refer to the global object (or be undefined in strict mode).

To fix this issue, developers often use workarounds like the self variable or the .bind() method. In the example, we didn't use such a workaround, so the this.value++ operation might not behave as expected.

ES6

// ES6 object constructor function
function ES6Object() {
  this.value = 0;

  // Method using an arrow function
  this.incrementES6 = function() {
    setTimeout(() => {
      this.value++;
      console.log('ES6:', this.value);
    }, 1000);
  };
}

const es6Instance = new ES6Object();
es6Instance.incrementES6(); // Works without the need for a workaround

ES6 Call Stack

Call Stack:
  1. setTimeout callback (Arrow function)
  2. incrementES6 (ES6Object instance)
  3. Global Execution Context

At this point, inside the arrow function callback, this.value++ works correctly because the arrow function inherits the correct this value from the surrounding incrementES6 method. There is no need for a workaround like the self variable used in the ES5 example.

Explain how prototypal inheritance works

All JavaScript objects have a proto property with the exception of objects created with Object.create(null), that is a reference to another object, which is called the object's "prototype". When a property is accessed on an object and if the property is not found on that object, the JavaScript engine looks at the object's proto, and the proto's proto and so on, until it finds the property defined on one of the protos or until it reaches the end of the prototype chain. This behavior simulates classical inheritance, but it is really more of delegation than inheritance.

You can check an object's prototype

const animal = {
  sound: 'generic sound',
  makeSound: function () {
    console.log(this.sound);
  }
};

// Child object inheriting from the parent
const cat = Object.create(animal);
cat.sound = 'meow';

cat.makeSound(); // Outputs: 'meow'

function Animal(name) {
  this.name = name;
}

// Get the prototype of the object using __proto__
const prototypeOfCat = cat.__proto__;

console.log(prototypeOfCat); // This will be the prototype of the `cat` object

What is prototypal inheritance
Prototypal inheritance is a type of inheritance mechanism in JavaScript where objects can inherit properties and methods from other objects through their prototype chain. In JavaScript, each object has an internal property called [[Prototype]] (commonly referred to as "dunder proto" or "proto" for short), which points to another object, known as its prototype.

When you access a property or method on an object, and that property or method is not found on the object itself, JavaScript looks up the prototype chain by following the [[Prototype]] links until it finds the property or method or until it reaches the end of the chain (usually the Object.prototype). This process allows objects to inherit properties and methods from their prototypes.

Here's a simple example to illustrate prototypal inheritance:

// Parent object (prototype)
const animal = {
  sound: 'generic sound',
  makeSound: function() {
    console.log(this.sound);
  }
};

// Child object inheriting from the parent
const cat = Object.create(animal);
cat.sound = 'meow';

cat.makeSound(); // Outputs: 'meow'

In this example:

  • animal is the parent object (prototype) with a property sound and a method makeSound.
  • cat is a child object created using Object.create(animal). This establishes a prototypal relationship between cat and animal.
  • cat has its own property sound, which shadows the sound property in the prototype.
  • When makeSound is called on cat, JavaScript first looks for makeSound in cat, doesn't find it, then looks up the prototype chain to the animal prototype where it finds the method.

Prototypal inheritance is a fundamental concept in JavaScript, and it provides a flexible and dynamic way to share and reuse code. It differs from classical inheritance, as seen in languages like Java or C++, where classes play a central role. In JavaScript, objects are linked to other objects through their prototypes, forming a chain of inheritance. The introduction of the class syntax in ECMAScript 2015 (ES6) made working with prototypes more syntactically similar to classical inheritance, but the underlying mechanism is still prototypal.

Difference of local variable and property
The main differences between local variables and properties in the context of JavaScript are related to their scope, lifetime, and how they are accessed. Here are the key distinctions:

  1. Scope:

    • Local Variable: Declared using var, let, or const within a block or function scope. It is only accessible within the scope where it's declared.
    • Property: Belongs to an object and is accessed using dot notation (object.property) or bracket notation (object['property']).
  2. Lifetime:

    • Local Variable: Lives within the scope where it's declared. It is created when the scope is entered and destroyed when the scope exits.
    • Property: Lives as long as the object it belongs to is alive. It is created when the object is created and removed when the object is garbage-collected.
  3. Access:

    • Local Variable: Accessed directly by its name within the same scope.
    • Property: Accessed using the object's name followed by dot notation or bracket notation.

Here's an example illustrating the differences:

// Local variable
function exampleFunction() {
  let localVar = 'I am a local variable';
  console.log(localVar);
}

exampleFunction(); // Outputs: 'I am a local variable'

// Property
const exampleObject = {
  property: 'I am a property',
};

console.log(exampleObject.property); // Outputs: 'I am a property'

In the example, localVar is a local variable inside the exampleFunction scope, and property is a property of the exampleObject. The local variable is only accessible within the function, while the property is accessible as part of the object.

In summary, local variables are used for temporary storage within a specific scope, while properties are used to store data or behavior as part of an object. The choice between using local variables and properties depends on the data's purpose and how it fits into the structure of your program.

Diffrence of function object and regular object
In JavaScript, both functions and objects are types of objects. However, they have distinct roles and behaviors. Let's explore the key differences between function objects and regular (or plain) objects:

  1. Role and Purpose:

    • Function Object: Primarily used for defining and executing reusable pieces of code. Functions can be invoked, take parameters, and return values.
    • Regular Object: Used for storing and organizing data. Regular objects have properties (key-value pairs) that can hold various data types, including other objects, and can have methods (functions as properties) for behavior.
  2. Invocation:

    • Function Object: Can be invoked (called) using parentheses (). Functions have an execution context and can take arguments.
    • Regular Object: Is not invoked. It is used for storing and accessing data through properties.
  3. Creation:

    • Function Object: Created using a function declaration, function expression, or the Function constructor.
    • Regular Object: Created using object literals {}, the Object constructor, or other object creation mechanisms.
  4. Behavior:

    • Function Object: Has executable code and may have properties (including other functions).
    • Regular Object: Typically holds data (properties) and may have methods (functions as properties) for behavior.

Here's a simple illustration:

// Function object
function myFunction() {
  console.log('I am a function!');
}

// Regular object
const myObject = {
  property1: 'I am a property',
  property2: 42,
  method: function() {
    console.log('I am a method!');
  }
};

myFunction(); // Invoking the function
console.log(myObject.property1); // Accessing a property of the object
myObject.method(); // Invoking a method of the object

In summary, function objects are primarily used for defining and executing code, while regular objects are used for organizing and storing data. Both can have properties, but functions have the additional capability of being invoked. JavaScript's flexible nature allows functions and objects to share some characteristics, blurring the lines between them.

What types of properties function can function objects have?
Function objects in JavaScript can have various types of properties, similar to regular objects. These properties can include:

  1. Data Properties:

    • Primitive Values: String, number, boolean, null, or undefined values.

    • Objects: Other objects or arrays.

      function myFunction() {
        this.name = 'John';         // String property
        this.age = 30;               // Number property
        this.isActive = true;        // Boolean property
        this.address = {             // Object property
          city: 'Example City',
          country: 'Example Country'
        };
      }
  2. Accessor Properties:

    • Properties defined using getter and setter methods.

      function myFunction() {
        let _value = 42;
      
        Object.defineProperty(this, 'value', {
          get: function() {
            return _value;
          },
          set: function(newValue) {
            _value = newValue;
          }
        });
      }
      
      const obj = new myFunction();
      console.log(obj.value);   // Accessing the getter
      obj.value = 100;          // Assigning a new value using the setter
  3. Methods:

    • Properties that are functions.

      function myFunction() {
        this.sayHello = function() {
          console.log('Hello!');
        };
      
        this.calculate = function(x, y) {
          return x + y;
        };
      }
      
      const obj = new myFunction();
      obj.sayHello();          // Invoking a method
      const result = obj.calculate(5, 7);  // Calling a method
  4. Inherited Properties:

    • Properties inherited from the prototype chain.

      function Animal(name) {
        this.name = name;
      }
      
      Animal.prototype.sayName = function() {
        console.log('My name is ' + this.name);
      };
      
      const cat = new Animal('Fluffy');
      cat.sayName();  // Inherited method

It's important to note that functions in JavaScript are a special type of object and can have properties and methods like any other object. The properties of a function object are often used for configuration, encapsulation, or to store data related to the function's behavior.

What do you think of AMD vs CommonJS?

Skip

Explain why the following doesn't work as an IFE: Function foo(){}();. What needs to be changed to properly make it an IIFE?

IIFE stands Immediately Invoked Function Expressions. Function foo(){}(); will be parsed as Function foo(){}; and () then Uncaught SyntaxError: Unexpected token error will occur. because JavaScript interprets the function foo() {} part as a function declaration, and it expects a block of code to follow.

You can fix the problem by wrapping this function within () like that (function foo(){ })() or (function foo() {} ()). JavaScript read them function expression within (function foo() {}) and is invoked by paretheses (). IIEF is not exposed in the global scope.

Why should you use IIFE
Immediately Invoked Function Expressions (IIFE) are commonly used in JavaScript for several reasons, each contributing to cleaner and more maintainable code. Here are some of the primary reasons why people use IIFE:

  1. Encapsulation and Scope Isolation:

    • IIFE creates a private scope for variables and functions, preventing them from polluting the global scope. This helps avoid naming conflicts with other scripts or libraries and promotes better code organization.
    (function() {
      // Private scope
      var localVar = 'I am a local variable';
      console.log(localVar);
    })();
    // localVar is not accessible here
  2. Avoiding Global Pollution:

    • Variables and functions declared inside an IIFE do not become global variables, reducing the risk of unintended global scope pollution. This is crucial in large projects and when working with multiple scripts.
    // Without IIFE
    var globalVar = 'I am a global variable';
    
    // With IIFE
    (function() {
      var localVar = 'I am a local variable';
      console.log(localVar);
    })();
    // localVar is not accessible here
  3. Module Pattern:

    • IIFE is often used as part of the module pattern, allowing developers to create modular and reusable pieces of code. This is especially useful in projects where modular design is a priority.
    var myModule = (function() {
      var privateVar = 'I am private';
    
      return {
        getPrivateVar: function() {
          return privateVar;
        }
      };
    })();
    
    console.log(myModule.getPrivateVar());  // Output: I am private
  4. Passing Arguments Safely:

    • IIFE allows for the safe passing of arguments without affecting the global scope. This can be useful when working with third-party libraries or when avoiding conflicts in a shared environment.
    (function(jQuery, lodash) {
      // Use jQuery and lodash without affecting the global scope
      // ...
    })(window.jQuery, window._);
  5. Executing Code Immediately:

    • IIFE executes code immediately after its declaration, making it useful for one-time setup tasks, initialization, or any code that needs to run immediately.
    (function() {
      // Initialization code
      // ...
    })();

In summary, IIFE is a versatile and widely used pattern in JavaScript for creating private scopes, avoiding global pollution, and facilitating modular and maintainable code. It's especially valuable in projects where code organization, encapsulation, and avoiding unintended side effects are essential considerations.

What's the difference between a variable that is : null, undefined or undecleared? How would you go about checking for any of these states?

In JavaScript, null, undefined, and undeclared are different states for variables.

  1. null: null is a special value in JavaScript that represents the intentional absence of any object value. It is a primitive value that indicates that a variable has been explicitly assigned to nothing. For example:

    let myVariable = null;
  2. undefined: undefined is a primitive value that indicates that a variable has been declared but has not been assigned a value. It also represents the absence of a value in a variable. For example:

    let myVariable;
    console.log(myVariable); // Output: undefined
  3. undeclared: An undeclared variable is one that has not been declared using the var, let, or const keywords. Accessing an undeclared variable will result in a reference error. For example:

    console.log(myVariable); // ReferenceError: myVariable is not defined

To check for any of these states in JavaScript, you can use conditional statements or type-checking functions:

  1. Checking for null: You can use a strict equality check (===) to determine if a variable is null.

    if (myVariable === null) {
        // Variable is null
    }
  2. Checking for undefined: You can use the typeof operator or a strict equality check to determine if a variable is undefined.

    if (typeof myVariable === 'undefined') {
        // Variable is undefined
    }
    
    // or
    
    if (myVariable === undefined) {
        // Variable is undefined
    }
  3. Checking for undeclared: Since attempting to access an undeclared variable results in a reference error, you can use a try-catch block to catch the error.

    try {
        console.log(myVariable);
    } catch (error) {
        if (error instanceof ReferenceError) {
            // Variable is undeclared
        }
    }

It's important to handle these states appropriately in your code to avoid unexpected behavior and errors.

When it comes to checking for null, undefined, or undeclared variables, the choice between using a try-catch statement and an if statement with strict equality checks depends on the specific context and requirements of your code. Here's a general guideline:

  1. Use if statement with strict equality checks for known scenarios:

    • If you know that a variable might have a value of null or undefined and you want to handle these cases explicitly, using an if statement with strict equality checks (===) is a suitable approach.
    • For example:
      if (myVariable === null) {
          // Handle the case where the variable is null
      } else if (myVariable === undefined) {
          // Handle the case where the variable is undefined
      } else {
          // Handle other cases
      }
  2. Use try-catch statement for potential errors:

    • If you're concerned about accessing undeclared variables or you want to handle errors gracefully without interrupting the execution of your code, using a try-catch statement is appropriate.
    • For example:
      try {
          console.log(undeclaredVariable); // This might throw a ReferenceError
      } catch (error) {
          if (error instanceof ReferenceError) {
              // Handle the error (e.g., log an error message)
              console.error('The variable is undeclared');
          }
      }
  3. Consider context and performance:

    • If you're simply checking for null or undefined in a known scenario and performance is a concern, using if statements with strict equality checks is generally more efficient than using a try-catch block.
    • However, if your code interacts with external dependencies or you anticipate potential errors, using try-catch can provide better error handling and prevent crashes.

In summary, use if statements with strict equality checks for known scenarios where you need to handle null or undefined values explicitly. Use try-catch statements for potential errors or when you want to handle exceptions gracefully without disrupting the flow of your code. Consider the context, requirements, and performance implications when choosing between these approaches.

What is a closure, and how/why would you use one?

A closure is a feature in JavaScript that allows a function to remember and access its lexical scope, even when that function is executed outside of that lexical scope. In simpler terms, a closure gives a function access to variables from its parent function, even after the parent function has finished executing.

Here's how a closure works:

  1. Definition: When a function is defined within another function, the inner function retains access to the variables, parameters, and functions defined in the outer (enclosing) function's scope, even after the outer function has returned.

  2. Access to Outer Scope: The inner function, also known as the closure, "closes over" the variables from its outer scope, allowing it to access and manipulate those variables whenever it's invoked, even if the outer function has already completed execution.

  3. Usage:

    • Data Encapsulation and Privacy: Closures are commonly used to create private variables and methods in JavaScript. By defining variables and functions within a closure, you can encapsulate data and behavior, ensuring that they are only accessible from within the closure and not from outside code.
    • Callback Functions: Closures are often used in asynchronous programming, such as callbacks, to maintain access to variables within the callback function's scope. This allows you to pass data and state between functions asynchronously while retaining the context in which the callback was defined.
    • Module Pattern: Closures are utilized in the module pattern to create modular and reusable code. By using closures to encapsulate private variables and methods, you can expose only the necessary functionality to the outside world, while keeping the internal implementation hidden.
    • Partial Application and Currying: Closures are also used in functional programming techniques like partial application and currying, where functions are returned from other functions with pre-configured arguments, allowing for more flexible and reusable code.

In summary, closures are a powerful feature in JavaScript that enable encapsulation, data privacy, and functional programming patterns. They provide a mechanism for managing scope and state in a flexible and expressive manner, making them invaluable in many programming scenarios.

Of course! Let's delve deeper into the usage of closures with easy-to-understand examples:

  1. Data Encapsulation and Privacy:

    Closures are commonly used to create private variables and methods in JavaScript. This means that certain variables or functions are accessible only within a specific scope, providing encapsulation and data privacy.

    function createCounter() {
        let count = 0; // Private variable
    
        return {
            increment: function() {
                count++;
                return count;
            },
            decrement: function() {
                count--;
                return count;
            }
        };
    }
    
    let counter = createCounter();
    console.log(counter.increment()); // Output: 1
    console.log(counter.increment()); // Output: 2
    console.log(counter.decrement()); // Output: 1

    In this example, the count variable is encapsulated within the createCounter function, and it can only be accessed and modified through the returned object's increment and decrement methods. This ensures that the count variable is not directly accessible from outside the createCounter function, providing data privacy.

  2. Callback Functions:

    Closures are often used in callback functions to maintain access to variables within the callback function's scope. This allows you to pass data and state between functions asynchronously while retaining the context in which the callback was defined.

    function greet(name) {
        setTimeout(function() {
            console.log('Hello, ' + name + '!');
        }, 1000);
    }
    
    greet('John');

    In this example, the setTimeout function receives an anonymous callback function as an argument. This callback function has access to the name variable defined in the outer scope of the greet function, even though the greet function has already finished executing. This is made possible by the closure, which "closes over" the name variable.

  3. Module Pattern:

    Closures can be utilized in the module pattern to create modular and reusable code. By using closures to encapsulate private variables and methods, you can expose only the necessary functionality to the outside world while keeping the internal implementation hidden.

    let counterModule = (function() {
        let count = 0; // Private variable
    
        function increment() {
            count++;
            return count;
        }
    
        function decrement() {
            count--;
            return count;
        }
    
        return {
            increment: increment,
            decrement: decrement
        };
    })();
    
    console.log(counterModule.increment()); // Output: 1
    console.log(counterModule.increment()); // Output: 2
    console.log(counterModule.decrement()); // Output: 1

    In this example, the counterModule is a self-invoking function that returns an object containing increment and decrement methods. These methods have access to the private count variable through closure, allowing them to modify its value while keeping it hidden from external code.

These examples demonstrate some common use cases of closures in JavaScript, including data encapsulation, callback functions, and the module pattern. Closures are a powerful tool for managing scope and state, enabling you to write modular, maintainable, and expressive code.

Can you describe the main difference between a .forEach loop and a .map() loop and why you would pick one versus the other?

Sure! Both .forEach() and .map() are methods used in JavaScript to iterate over arrays, but they serve different purposes and have different behaviors.

  1. .forEach(): This method executes a provided function once for each array element. It doesn't return anything. It simply iterates over the array and performs an action on each element.
const numbers = [1, 2, 3, 4, 5];

numbers.forEach((number) => {
  console.log(number * 2);
});

In this example, .forEach() will iterate over each element in the numbers array and log its doubled value to the console.

  1. .map(): This method creates a new array populated with the results of calling a provided function on every element in the calling array. It returns a new array with the same length as the original array, where each element is the result of the callback function.
const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map((number) => {
  return number * 2;
});

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

In this example, .map() will iterate over each element in the numbers array, double each element, and return a new array [2, 4, 6, 8, 10].

The main difference between .forEach() and .map() is that .forEach() is used when you want to perform an action on each element of the array without altering the original array, while .map() is used when you want to create a new array with modified elements based on the original array.

So, you would pick .forEach() if you just want to perform an action on each element, and you don't need a new array. On the other hand, you would pick .map() if you want to transform each element of the array and create a new array with those transformed values.

What's a typical use case for anonymous functions?

Anonymous functions, often referred to as "arrow functions" in JavaScript, are commonly used for various purposes due to their concise syntax and flexibility. Here are some typical use cases:

  1. Callback Functions: Arrow functions are frequently used as callback functions, especially in asynchronous operations such as handling events or making AJAX requests.

    button.addEventListener('click', () => {
        console.log('Button clicked');
    });
  2. Array Methods: Arrow functions are commonly used with array methods like map(), filter(), and reduce() to process arrays more concisely.

    const numbers = [1, 2, 3, 4, 5];
    const doubled = numbers.map(x => x * 2);
  3. Promises: In Promise chains, arrow functions are used to handle the resolved values or errors in a more readable and concise way.

    fetchData()
        .then(data => {
            // Handle successful data retrieval
        })
        .catch(error => {
            // Handle errors
        });
  4. Event Handlers: When attaching event handlers to DOM elements, arrow functions can be used for their concise syntax and lexical scoping behavior.

    button.addEventListener('click', () => {
        console.log('Button clicked');
    });
  5. Immediately Invoked Function Expressions (IIFE): Arrow functions are commonly used in IIFE for creating a private scope and avoiding global namespace pollution.

    (() => {
        // Do something privately
    })();
  6. Functional Programming: Arrow functions are often used in functional programming paradigms for their succinct syntax and compatibility with higher-order functions.

    const add = (a, b) => a + b;
    const multiply = (a, b) => a * b;

In all these cases, anonymous functions (arrow functions) provide a concise and expressive way to define functions inline without the need to explicitly name them, making code more readable and maintainable, especially for short and simple functions.

How do you organize your code? (module pattern, classical inheritance?)

Organizing code in JavaScript can be approached in various ways depending on the size and complexity of your project, as well as your personal preferences and the requirements of your team. Here are a few common patterns and techniques:

  1. Module Pattern: This is one of the most widely used patterns for structuring JavaScript code. It involves encapsulating related functions and variables into a single object, providing a way to keep the global namespace clean and prevent naming conflicts. Modules can be implemented using immediately invoked function expressions (IIFE) to create private scopes.

    var myModule = (function() {
        var privateVar = 10;
    
        function privateFunction() {
            console.log("Private function");
        }
    
        return {
            publicFunction: function() {
                console.log("Public function");
            }
        };
    })();
  2. ES6 Modules: With the introduction of ES6, JavaScript natively supports a module system. You can export functions, variables, or classes from one module and import them into another. This provides a more organized and scalable way to structure code.

    // module.js
    export function myFunction() {
        console.log("Exported function");
    }
    
    // main.js
    import { myFunction } from './module.js';
  3. Classical Inheritance: JavaScript supports prototypal inheritance rather than classical inheritance. However, you can emulate classical inheritance patterns using constructor functions and prototypes. This involves creating constructor functions and using the prototype property to define methods shared across instances.

    function Animal(name) {
        this.name = name;
    }
    
    Animal.prototype.sayName = function() {
        console.log("My name is " + this.name);
    };
    
    function Dog(name, breed) {
        Animal.call(this, name);
        this.breed = breed;
    }
    
    Dog.prototype = Object.create(Animal.prototype);
    Dog.prototype.constructor = Dog;
    
    Dog.prototype.bark = function() {
        console.log("Woof!");
    };
    
    var myDog = new Dog("Buddy", "Labrador");
  4. Object-Oriented Patterns: JavaScript allows you to create objects and use them as instances, similar to classical object-oriented languages. You can use object literals or constructor functions to create objects with properties and methods.

    var person = {
        firstName: "John",
        lastName: "Doe",
        fullName: function() {
            return this.firstName + " " + this.lastName;
        }
    };
    
    function Person(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    Person.prototype.fullName = function() {
        return this.firstName + " " + this.lastName;
    };
    
    var anotherPerson = new Person("Jane", "Doe");

Ultimately, the best approach depends on your specific project requirements, team preferences, and coding style. It's essential to choose a pattern that promotes readability, maintainability, and scalability for your codebase.

What's the difference between host objects and native objects?

Difference between: function Person(){}, var person = Person(), and var person = new Person()?

What's the difference between .call and .apply?

Explain Function.prototype.bind.

When would you use document.write()?

What's the difference between feature detection, feature inference, and using the UA string?

Explain Ajax in as much detail as possible.

What are the advantages and disadvantages of using Ajax?

Explain how JSONP works (and how it's not really Ajax).

Have you ever used JavaScript templating? If so, what libraries have you used?

Explain "hoisting".

Describe event bubbling.

What's the difference between an "attribute" and a "property"?

Why is extending built-in JavaScript objects not a good idea?

Difference between document load event and document DOMContentLoaded event?

What is the difference between == and ===?

Explain the same-origin policy with regards to JavaScript.

Make this work:

Why is it called a Ternary expression, what does the word "Ternary" indicate?

What is "use strict";? What are the advantages and disadvantages to using it?

Create a for loop that iterates up to 100 while outputting "fizz" at multiples of 3, "buzz" at multiples of 5 and "fizzbuzz" at multiples of 3 and 5.

Why is it, in general, a good idea to leave the global scope of a website as-is and never touch it?

Why would you use something like the load event? Does this event have disadvantages? Do you know any alternatives, and why would you use those?

Explain what a single page app is and how to make one SEO-friendly.

What is the extent of your experience with Promises and/or their polyfills?

What are the pros and cons of using Promises instead of callbacks?

Callback

A callback function is a function that is passed as an argument to another function and is intended to be called later, usually after an asynchronous operation or some other kind of event. Callbacks are a way to manage control flow in situations where centain operations take time to complete, and you want to speify what happens next.

function fetchData(callback) {
    // Simulating an asynchronous operation (e.g., fetching data from a server)
    setTimeout(() => {
      const data = {message: 'Data fetched successfully!'};
      // Calling the callback function with the fetched data
      callback(data);
    }, 1000);
  }

  function wait(sec) {
    let start = Date.now()
    let now = start;
    while (now - start < sec * 1000) {
      now = Date.now();
    }
  }


  console.log('start');

  // Using the callback function on asynchronous code (no wait)
  fetchData((result) => {
    console.log(result.message);
    // Do something with the fetched data
  });

  // Synchronous code (wait for 3 seconds)
  // wait(3);

  console.log('end')

the fetchData function doesn't wait to execute next code console.log('end') after the data received the callback function is called and execute the console.log(result.message) line like below.

start # it is executed immediately
end # it is executed immediately
Data fetched successfully! # it is executed immediately but takes 1 second.

If all the functions work as synchronous mode, we would face a problem. it would lead to performance issues especially on the environment of the brower which needs to show various contents to users fastly because it would work so slowly. We should use asynchrous mode for the performance. On asynchronous mode, you use a Callback function to process result which takes a time to get. You use Callback function to handle some events. For example handling a button click or user input.

Why use callback function?
1. Aynchronous Operation : Callbacks are commonly used with aynchronous operations, such as AJAX requests, file I/O, or timers(setTimeout). They allow you to specify what should happen after the operation completes.
2. Event Handling : Callbacks are often used in event-driven programming to define behavior when an event occurs. For example handling a button click or a user input.
3. Modular code : Callbacks can help in creating modular and reusable code. You can pass different callbaks to the same function to customize its behavior

let numbers = [1, 2, 3, 4, 5]; // Define an array
let doubled = []; // Define an empty array


numbers.forEach(function (num) { 
    doubled.push(num * 2); 
}); 

console.log(doubled); // [2, 4, 6, 8, 10]

What are some of the advantages/disadvantages of writing JavaScript code in a language that compiles to JavaScript?

What tools and techniques do you use for debugging JavaScript code?

What language constructions do you use for iterating over object properties and array items?

Explain the difference between mutable and immutable objects.

Explain the difference between synchronous and asynchronous functions.

What is event loop? What is the difference between call stack and task queue?

Explain the differences on the usage of foo between function foo() {} and var foo = function() {}

What are the differences between variables created using let, var or const?

What are the differences between ES6 class and ES5 function constructors?

Can you offer a use case for the new arrow => function syntax? How does this new syntax differ from other functions?

What advantage is there for using the arrow syntax for a method in a constructor?

What is the definition of a higher-order function?

Can you give an example for destructuring an object or an array?

ES6 Template Literals offer a lot of flexibility in generating strings, can you give an example?

Can you give an example of a curry function and why this syntax offers an advantage?

What are the benefits of using spread syntax and how is it different from rest syntax?

How can you share code between files?

Why you might want to create static class members?

Other Answers

CSS questions

HTML questions

Coding interview

System design interview

Behaviorial interview

Resume preparation

Interview questions

0개의 댓글

관련 채용 정보