Javascript의 기본 #1

LUCAS·2021년 4월 21일
2

이벤트 위임이 무엇인가?

이벤트 위임은 이벤트 리스너를 하위 요소에 추가하는 대신, 상위 요소에 추가하는 기법을 말합니다.
DOM이 이벤트 버블링으로 인해 하위 요소에서 이벤트가 발생될 때마다 실행됩니다.
이 기술의 이점은 다음과 같습니다.

  • 각 하위 항목에 이벤트 핸들러를 연결하지 않고, 상위 요소에 하나의 단일 핸들러만 필요로 하기 때문에 메모리 사용 공간이 줄어들며, 코드 복잡도가 개선될 수 있다.
  • 이벤트 리스너 대상 요소가 변경되어도 리스너에 새롭게 핸들러를 바인딩하지 않아도 된다.

Javascript에서 this란 무엇이고 어떻게 쓰이는가?

Javascript에서 this를 규정하는 방법은 크게 어렵지 않습니다.
간략히 설명하자면 함수가 호출되는 방식에 따라 달라지는데, 자세한 항목은 아래를 참고해봅시다.

  1. 함수를 호출할 때 new 키워드를 사용할 경우
    -> 함수 내에서 this는 완전히 새로운 객체를 갖게 됩니다.
function Constructor() {
  console.log(this);
  this.value = 10;
  console.log(this);
}

new Constructor();

// -> {}
// -> { value: 10 }

자세한 내용은 이 것을 참조하십시오.

  1. 함수를 호출할 때 apply, call 혹은 bind를 사용할 경우
    -> 함수 내에서 this는 인수로 전달된 객체입니다.
function fn() {
  console.log(this);
}

const params = {
  value: 10,
};

const bindFunc = fn.bind(params);

bindFunc();       // { value: 10 }
fn.call(params);  // { value: 10 }
fn.apply(params); // { value: 10 }
  1. 함수를 호출할 때 obj.method()와 같이 함수를 메소드로 호출하는 경우
    -> 함수 내에서 this는 함수가 프로퍼티인 객체입니다.
const obj = {
  value: 5,
  printThis: function() {
    console.log(this);
  }
};

obj.printThis();  //  { value: 5, printThis: f }
  1. 함수를 호출할 때 위 세가지 유형에 따른 호출이 아닌 자유함수 호출일 경우
    -> 함수 내에서 this는 전역 객체입니다. 브라우저에서의 전역 객체는 window이며, 엄격 모드('use strict')인 경우, this는 전역 객체 대신 undefind가 됩니다.
function fn() {
  console.log(this);
}

fn();  // Window {0: global, 1: global, 2: Window, ...}

이는 함수가 호출될 때 위 유형 3번과 동일하게 Windows 객체 안에서 선언된 함수가 호출되기 때문입니다.

console.log(fn === window.fn)  // true
  1. 위 규칙 중 다수가 적용되면, this는 상위 규칙으로 적용됩니다.
const obj = {
  value: 5,
  printThis: function() {
    console.log(this);
  }
};

const params = {
 value: 10,
};

const fn = obj.printThis.bind(params);

fn();  // { value: 10 }
  1. 함수가 ES2015의 화살표 함수인 경우 위 유형을 모두 무시하고 상위 스코프의 this 값을 받습니다.
const obj = {
  value: 'abc',
  createArrowFn: function() {
    return () => console.log(this);
  }
};

const arrowFn = obj.createArrowFn();

arrowFn();  // { value: 'abc', createArrowFn: f }

프로토타입 상속은 어떻게 이루어지는가?

모든 Javascript 객체는 다른 객체에 대한 참조인 프로토타입 Prototype을 가지고 있습니다.
객체의 프로퍼티에 접근할 때, 해당 객체에 해당 프로퍼티가 없으면, Javascript 엔진은 객체의 프로토타입과 프로토타입의 프로토타입을 등을 보고 프로퍼티가 정의되어 있을 때 까지 찾고, 만약 객체의 프로퍼티를 참조하고자 할 때 해당 프로퍼티가 없으면, 프로토타입 체인 중 하나에 있거나, 프로토타입 체인의 끝에 도달할 때 까지 찾습니다. 이 과정은 고전적인 상속을 흉내내지만, 실제로는 상속보다는 위임에 가깝습니다.

AMD vs CommonJS에 대해 어떻게 생각하는가?

두 가지 모두 ES2015가 등장하기 전까지 Javascript에 기본적으로 존재하지 않는 모듈 시스템을 구현하는 방법입니다.
CommonJS는 동기식인 반면 AMD(Asynchronous Module Definition - 비동기식 모듈 정의)는 분명히 비동기식입니다. CommonJS는 서버사이드 개발을 염두에 두고 설계되었으며, AMD는 모듈의 비동기 로딩을 지원하므로 브라우저용으로 더 많이 사용됩니다.

AMD는 구문이 매우 장황하고, CommonJS는 다른 언어처럼 import 문을 작성하는 스타일에 더 가깝습니다.
대부분의 경우 AMD를 필요로 하지 않습니다. 모든 Javascript는 연결된 하나의 번들 파일로 제공하면 비동기 로딩 속성의 이점을 누릴 수 없기 때문입니다.
또한 CommonJS 구문은 모듈 작성의 노드 스타일에 가깝고 클라이언트사이드와 서버사이드 Javascript 개발 사이를 전환할 때 문맥 전환 오버 헤드가 적습니다.

ES2015 모듈이 동기식 및 비동기식 로딩을 모두 지원하는 것이 반가운 것은 마침내 하나의 접근 방식만 고수할 수 있다는 점입니다.
브라우저와 노드에서 완전히 작동되지는 않지만, 언제나 트랜스파일러를 사용하여 코드를 변환할 수 있습니다.

다음 로직이 IIFE로 작동하지 않는 이유는?

IIFE는 즉시 함수 호출 표현식(Immediately Invoked Function Expressions)을 의미합니다.
Javascript parser는 function foo(){ }();을 function foo(){ }와 (); 로 읽습니다.
전자는 함수 선언이며 후자(한 쌍의 중괄호)는 함수를 호출하려고 시도했지만, 호출할 함수의 이름이 지정되지 않았기 때문에 Uncaught SynctaError: Unexpected token )을 발생시킵니다.

이를 해결하기 위해 다음 세 가지 방법을 고안해볼 필요가 있습니다.

에러 로직

function foo(){ }();  // Uncaught SynctaError: Unexpected token )

1. 괄호를 사용한 방법 1

-> 중괄호를 사용하여 함수식을 선언합니다.

(function foo(){ })();

2. 괄호를 사용한 방법 2

(function foo(){ })());

3. void 연산자를 사용한 방법

void function foo(){ }();

불행히도 세 번째 접근방식으로는 호출된 IIFE 함수에서 반환된 값을 받을 수 없다는 단점이 있습니다.

undeclared, undefined, null의 각 차이점이 무엇이고 검사 방법은 무엇인가?

1. undeclared

undeclared 변수는 const나 let, 혹은 var 키워드를 사용하지 않고 선언된 식별자에 값을 할당하려할 때 발생한다.
엄격 모드('use strict')인 경우, undeclared는 오류를 발생시키며 이는 전역변수 사용과 동일하게 권장되지 않는다.
이를 검사하려면 try/catch 블록에 감싸야한다.

2. undefined

undefind 변수는 선언되었지만 초기화되지 않은(값이 할당되지 않은) 변수를 뜻합니다.
이는 undefined 타입을 가지며, 함수가 실행되었을 때 아무런 값을 반환하지 않는다면 undefined를 반환하며 이는 동일하게 undefined 타입을 가집니다.
이를 검사하기위해 엄격한(===)검사 혹은 typeof를 사용하여 문자열을 통해 비교를 진행하면 되며, 확인을 위해 추상 평등 연산자(==)를 사용해서는 안됩니다. (값이 null이면 true를 반환하기 때문입니다.)

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

3. null

null 변수는 사용자가 명시적으로 null을 할당하였다는 점에서 undefined와 차이점을 지닙니다.
이를 검사하기위해 완전 평등(===) 연산자를 사용하여 비교를 진행하면 되며, 위와 동일하게 추상 평등 연산자(==)를 사용해서는 안됩니다. (값이 undefined이면 true를 반환하기 때문입니다.)

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

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

클로저는 함수와 그 함수가 선언된 렉시컬 환경의 조합입니다.
렉시컬은 렉시컬 범위 지정이 변수가 사용 가능한 위치를 결정하기 위해 소스 코드 내에서 변수가 선언된 위치를 사용한다는 사실을 나타냅니다.
클로저는 외부 함수가 반환된 후에도 외부 함수의 변수 범위 체인에 접근할 수 있는 함수입니다.

왜 클로저를 사용합니까?

  • 클로저로 데이터 프라이버시 / private method를 모방. 일반적으로 모듈 패턴에 사용됩니다.

    private Method를 모방한 클로저
    외부에서 closer의 내장 함수 및 변수에 접근 불가, OOP의 추상적 이론인 캡슐화 및 데이터 은닉화가 가능함

const closer = function() {
  let result = 0;
  
  return {
  	getResult: function () {
      return result;
    },
    addResult: function (x) {
      result += x;
    },
    subtractResult: function (x) {
      result -= x;
    } 
  }
}

const closerA = closer();

closerA.addResult(3);
const closerAResult = closerA.getResult();

console.log(closerAResult);  // 3
  • 부분 적용 또는 currying.

    부분 적용 함수: n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m) 개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수

const add = function() {
  let result = 0;
  
  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  
  return result;
};

const addPartial = add.bind(null, 1, 2, 3, 4, 5);  // bind 메서드로 인자 5개를 미리 적용 (적용만 하고 실행하지 않음)

console.log(addPartial(6, 7, 8, 9, 10));  // 나머지 인자를 추가로 넘겨줌 -> 55

커링 함수: 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것

const curry = function(func) {
  return function(a) {
    return function(b) {
      return func(a, b);
    }
  }
};

const getMaxWith10 = curry(Math.max)(10);

console.log(getMaxWith10(8));   // 10
console.log(getMaxWith10(25));  // 25

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

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

보통 익명함수는 한 번 사용되고 다른 곳에서는 사용할 필요가 없는 콜백으로 사용됩니다.
함수의 본체를 찾기위해 다른 곳을 찾아볼 필요 없이 코드를 호출하는 코드 바로 안에 핸들러가 정의되어 있으면 코드가 보다 독립적이고 읽기 쉽게 보일 것입니다.

setTimeout(function() {
 console.log('hello world!');
}, 1000);

개인에 따른 코드 구성 방식?

필자는 과거에 MVC 패턴에 대한 백본을 구성하여 개발을 진행했었다.

하지만 위의 방법은 다음과 같은 문제점이 발생할 수 있었는데,

프로젝트가 커지면 커질 수록 의존성문제부터 시작해서 수정 범위가 커지는 단점이 발생했다는 것이다.
view에서 받아온 사용자의 데이터를 model을 통해 데이터를 갱신하고 조회하는데, Model이 세분화될수록 개발 난이도가 높아지는 것은 어찌보면 당연한 것이다.

그래서 Facebook 개발팀은 위 구조의 원초적 문제를 해결하기 위해, 차세대 프레임워크 개발에 FLUX 패턴 구조를 도입하였다.

이는 단방향 프로그래밍 방식을 지향함에 따라 구조가 복잡하지 않으며, 스토어에 대한 각 액션만 관리해주면 된다.

특별한 이유가 없으면 이러한 코드 구성 방식을 유지할 계획이다.

호스트객체와 내장객체의 차이점이 무엇인가?

내장객체는 ECMAScript Spec에 정의된 Javascript 언어의 일부인 객체입니다.
(예: String, Math, RegExp, Object, Function 등)

호스트객체는 window, XMLHTTPRequest 등과 같이 런타임 환경 (브라우저 or 노드)에 의해 제공됩니다.

call과 apply의 차이점은 무엇인가?

이 둘은 모두 함수를 호출하는데 사용됩니다.
첫 번째 매개변수는 공통으로 함수 내에서 this의 값으로 사용됩니다.
그러나 call은 쉼표로 구분된 인수를 두 번째 인수로 취하고, apply는 인수의 배열을 두 번째 인수로 취합니다.
call의 C는 Comma, apply의 A는 arguments라고 기억하면 쉽습니다.

function add(a, b) {
  return a + b;
}

console.log(add.call(null, 1, 2);     // 3
console.log(add.apply(null, [1, 2]);  // 3

참고
https://velog.io/@chris/front-end-interview-handbook-js-1

profile
안녕하세요! FE개발자 최근원입니다.

0개의 댓글