[프론트엔드 인터뷰]FE-Study 1주차 2회

null, undefined, undeclared의 차이점은 무엇인가요? 어떻게 이 상태들에 대한 확인을 할 것인가요?

Undefined는 접근 가능한 스코프에 변수가 선언되었으나 현재 아무런 값도 할당하지 않은 상태입니다.

var test
console.log(test) // undefined
console.log(typeof test) //undefined

Undeclared는 접근 가능한 스코프에 변수 선언조차 되어있지 않은 상태입니다.

console.log(test2) // Uncaught ReferenceError: test2 is not defined

console.log(typeof test2) // undefined

undefined와 null의 결정적인 차이점은 undefined는 타입이 결정되지않은 변수이고, null의 타입은 객체이며, 비어있는 변수라는 점 입니다.

var test3 //변수 선언
test3 = null // 선언한 변수 test3에 null값 할당
console.log(test3) //null
console.log(typeof test3) //변수 test3의 타입은 object 객체.
var foo = null // true
console.log(foo === null) // true
console.log(foo == undefined) // true 이렇게 사용하지 않는게 좋습니다.

==보단 ===를 써야하는 이유는 비교연산자 ==는 자료형이 다르면 자동형변환으로 자료형을 강제로 맞춰서 비교하는 연산자이기 때문입니다.

console.log(undefined == null) // true
console.log(undefined === null) // false

클로저는 무엇이며, 어떻게/왜 사용하나요?

클로저는 함수와 렉시컬 환경의 조합입니다. 함수의 렉시컬 스코프를 기억했다가 함수의 렉시컬 스코프를 벗어난 외부 스코프에서 실행될 때에도 자신의 렉시컬 스코프에 접근할 수 있는 것을 말합니다.
function foo() {
  var a = 1
  function bar() {
    console.log(a) //1
  }
  return bar
}
const baz = foo()
baz()

foo() 함수 렉시컬 스코프를 벗어난 외부 전역 스코프에서 baz 함수를 호출하면 자연스럽게 foo함수를 실행하고 bar 함수를 실행합니다. 그러면 렉시컬 스코프 체인을 통해 foo 함수의 a 에 접근이 가능합니다. 이것을 바로 클로저라고 합니다. 또한 전역 스코프가 아닌 어느 곳에서 호출되어도 bar()는 기억한 렉시컬 스코프 체인을 통해 변수a에 접근할 수 있습니다.

.forEach 루프와 .map() 루프 사이의 주요 차이점을 설명할 수 있나요?

forEach

  • 배열의 요소를 반복합니다.
  • 각 요소에 대해 콜백을 실행합니다.
  • 값을 반환하지 않습니다.
const a = [1, 2, 3]
const doubled = a.forEach((num, index) => {
  // 어떠한 작업을해도
})
// doubled = undefined

map

  • 배열의 요소를 반복합니다.
  • 각 요소에서 함수를 호출하여 결과로 새 배열을 작성하여 각 요소를 새 요소에 매핑합니다.
const a = [1, 2, 3]
const doubled = a.map(num => {
  return num * 2
})
// doubled = [2,4,6]

forEach와 map의 가장 큰 차이점은 map은 새로운 배열을 반환한다는 것입니다. 결과가 필요하지만 원본 배열을 변경하고 싶지 않으면 map을 사용하는게 좋고, 단순하게 배열 반복작업만 필요한 경우 forEach가 좋은 선택입니다.

익명 함수의 일반적인 사용 사례는 무엇인가요?

익명함수는 IIFE로 사용되어 지역 범위 내에서 일부 코드를 캡슐화하므로 선언된 변수가 전역범위로 누출되지 않습니다.

;(function() {
  // 코드
})()

간단하게 말하자면 익명함수는 재사용 하지 않는, 한번만 사용할 함수를 위한 개념으로, 따로 함수의 이름을 갖지 않습니다. 리터럴 방식으로 변수에 담겨 사용하는 함수입니다. 리터럴이란 아래와 같이 일반적으로 변수에 데이터를 넣을 때 사용하는 방식이 리터럴 방식입니다.

let a = 10
const b = 20

리터럴 방식으로 사용되는 익명 함수는, 변수에 저장하게 됩니다.

const sayHello = fucntion() {
  console.log("hello");
}

sayHello(); // hello

여기서 함수가 이름을 갖는 것과 변수에 저장되는 것은 다릅니다. 왜냐하면 호이스팅 관점에서 보았을때 차이점이 나타납니다.

아래는 일반함수가 스코프 최상단으로 끌어올려지는 호이스팅에 의하여 SayHello(); 함수 두개가 정상적으로 실행되는 모습입니다.

SayHello() // "hello!" 가 정상적으로 출력됨.

function SayHello() {
  console.log('hello!')
}

SayHello() // "hello!" 가 정상적으로 출력됨.

반면 리터럴 방식으로 사용되는 익명 함수의 경우, 호이스팅시 함수를 담는 변수의 선언부만 위로 올라가고, 익명 함수 자체는 변수가 호출되었을 때 실행되기 때문에, 선언부가 호출 위치보다 위에 있어야 합니다.

sayHello() // Uncaught ReferenceError: Cannot access 'sayHello' before initialization

const sayHello = function() {
  console.log('hello')
}

sayHello() // 위에서 에러가 나서 출력이 나오지 않음

// 위 코드가 실행될 때
// const sayHello;

// sayHello(); // sayHello의 초기화가 진행되지 않았다.

// sayHello = function() {
//   console.log("hello");
// }

// sayHello(); // 초기화가 진행된 후 호출되었으니, 원래대로는 출력가능
//

결론

  1. 일반 함수는 전역적이며, 전체가 다 호이스팅 되므로 호출의 위치와 구현의 위치간에 연관관계가 없고, 재사용될 기능에 주로 사용된다.
  2. 익명 함수는 선언부만 호이스팅 되며 호출의 위치와 구현의 위치간의 순서가 알맞아야하고, 한번만 사용하는 기능에 사용된다.
위 말만 보면 익명 함수가 오히려 번거로운 것처럼 보일 수 있지만, 이는 메모리 관리에 효과적인 방안이 될 수 있다.

일반 함수는 자바스크립트를 초기에 읽어올 때 모두 호이스팅된다고 하였다. 만약, 전체 자바스크립트 내에서 단 한번만 쓰이는 함수가 일반 함수로 구현되어 있다고 가정해보자. 이 함수는 자신이 사용될 단 한번을 기다리며 불필요하게 메모리를 차지하고 있어야 한다. 메모리 사용량이 성능에 중요한 영향을 미칠 수 있는 웹 애플리케이션에서, 이는 메모리 낭비라고 볼 수 있다.

따라서 단 한번만 사용되는(재사용이 필요없는) 함수의 경우, 불필요한 시간동안 메모리를 차지하지 않도록 익명함수로 구현한다면, 정확히 해당 함수가 필요한 위치에서만 해당 함수가 구현되고 사라지면서 메모리를 아낄 수 있게 된다.

출처 https://dev-note-97.tistory.com/273

코드를 어떻게 구성하나요?

과거에는 Backbone 모델을 만들고 그 모델에 메소드를 연결하는 등 OOP 접근 방식을 장려하는 모델에 Backbone을 사용했습니다. 모듈 패턴은 여전히 훌륭하지만, 요즘에는 React/Redux 기반의 Flux 아키텍처를 사용합니다. 이 아키텍처는 단방향 프로그래밍 방식을 권장합니다. 저는 평범한 객체를 사용하여 응용 프로그램의 모델을 표현하고 이러한 객체를 조작하는 유틸리티 순수 함수를 작성합니다. 상태는 다른 Redux 응용 프로그램에서와 마찬가지로 action 및 reducer를 사용하여 조작됩니다.