이벤트 루프가 비동기 함수를 어떻게 처리하는지 설명하라.

우선 자바스크립트는 한번에 하나의 일을 처리할 수 있는 싱글 스레드 처리방식을 가진다.

그러므로 동기적으로 일을 처리할 때에 시간이 오래 걸리는 처리를 할 경우 다음 일에 블로킹이 발생한다.

이러한 블로킹 현상을 해결하기 위해 비동기식 처리 방식을 사용하고 여러 개의 일이 동시에 처리되는 것처럼 느껴지도록 도와주는 이벤트 루프가 있다.

이벤트 루프는 브라우저에 내장된 기능 중 하나이다.

이벤트 루프

위 사진은 이벤트 루프가 동작하는 방식을 보여주는 사진이다.

  • 콜 스택 : 함수 호출 시 함수 실행 컨텍스트가 콜 스택에 푸시되어 순차적으로 실행된다.
  • 태스크 큐 : 타이머 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다. 태스크 큐와는 별도로 프로미스의 후속처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로 태스크 큐도 존재한다.
  • 이벤트 루프 : 이벤트 루프는 현재 콜 스택에 실행중인 실행 컨텍스트가 있는지, 태스크 큐에 대기중인 함수가 있는지 확인하여 콜 스택이 비어있고 태스크 큐에 대기중인 함수가 있다면 이벤트 루프가 순차적(FIFO)으로 태스크 큐에 대기중인 함수를 콜 스택으로 이동시킨다. 즉, 태스크 큐에 일시 보관된 함수들은 비동기 처리 방식으로 동작한다.

비동기 처리에서 소스코드 평가 및 실행을 제외한 모든 코드는 브라우저 또는 NodeJS가 담당한다. 브라우저는 멀티 스레드로 동작한다.

댓글 공유

모의 면접 2차 회고

카테고리 CS

📌 1분 자기소개 파트

기술면접만 진행하실 줄 알았는데 갑자기 자기소개를 하라하셔서 당황하여 말을 제대로 못했다.

  • ~ 하는 편입니다. 보다는 ~ 합니다. 라고 명료하게 답변하자

📚 기술면접 파트

비동기 함수와 이벤트 루프에 대한 설명해라

  • Web API, setTimeout, 이벤트리스너등의 특수한 경우에서 자바스크립트는 멀티스레드로 동작하는 것처럼 보이는 것이다. 실제로 자바스크립트는 싱글 스레드이다.
  • 일련의 과정을 설명하는데 짜임새가 부족하였다.
  • 자바스크립트는 원래 싱글 스레드인데, 런타임 이전에서 AJAX나 비동기 코드를 위해 불가피하게 멀티 스레드를 두어 사용하게 되었다 라는 보충 설명 해주면 좋을 것 같다.

객체 불변성에 대해 설명해라

  • 리액트와 객체 불변성을 연관짓는 설명이 부족하다.
  • ~ 한다고 생각합니다 대신 ~ 할 수 있습니다로 바꿔서 말하기

This 설명하라

  • 처음에 4가지 분류를 말하였는데 말하다보니 까먹어서 3가지밖에 말하지 못한 부분이 아쉬웠다.
  • 화살표 함수는 this를 가지고 있지 않으므로, 내부의 this는 상위 스코프의 this에 바인딩된다.

var, let, const 비교 설명하라

  • 스코프 설명하면서 함수레벨 스코프, 블록 레벨 스코프가 왜 더 안전한 코드를 짤 수 있도록 하는 지에 대한 설명 보충

함수가 일급 객체인 이유는?

  • 좀 더 자세한 설명이 필요하다.
  • 특징을 2가지 밖에 말하지 못하여 아쉬웠다.

클로저 설명, 장점 및 주의점

  • 주의점 설명이 부족했다. 클로저는 외부 함수의 실행 컨텍스트가 사라져도 내부 함수에 대한 참조가 유지되고 있기 때문에 메모리 누수에 대한 부분도 고려해줘야한다.
  • 리액트내에서도 useEffect 함수 내부에서 클린업 함수를 해주어 메모리 누수를 방지하여야 한다.

CORS 우회 방법 설명하라

  • 프록시 객체라는 설명이 모호해서 아쉬웠다. 프록시 서버에 대한 설명을 프록시 객체로 잘못 답변하였다.

제너레이터에 대해 설명하라

  • 제너레이터 공부하자…

✏️ 총평

  • 정제된 단어로 정리해서 답변을 하면 더 좋을 것 같다.
  • 객체 변경 불가성 중요하니깐 제대로 알아두자. 상태값을 객체로 주로 저장하기 때문에, 이 자체의 참조값을 바꿔야지만 내부적으로 렌더링이 일어난다.
  • 클로저의 주의점에 대해서는 클린업 함수, 메모리 누수 설명 보충하자.
  • 1급 객체에 대한 특징 및 조건 설명을 풍부하게 해보자.
  • CORS 해결 방법에 대한 설명을 제대로 답변하지 못하였으니 공부하자.

댓글 공유

🛠 JWT 토큰 방식이란?

JWT는 JSON Web Token의 약자로, 인증에 필요한 데이터를 암호화한 JSON으로 이루어진 토큰을 말한다.

기존의 세션 인증방식은 인증관련 정보를 세션 저장소라는 DB에 저장하여 서버가 과부하 되거나 서버를 확장하기가 어려웠다.

이러한 문제 때문에 서버자원을 절약하기 위해 사용자 인증에 필요한 정보를 토큰 자체에 담아두는 JWT를 사용하게 되었다.

토큰은 로그인 이후 서버가 만들어주는 문자열이고, 토큰 안에는 사용자의 로그인 정보와 서버의 서명이 들어있다.

JWT 토큰 방식 순서

  1. 사용자가 로그인을 하면 서버는 사용자에게 사용자 로그인 정보 및 서버의 서명이 들어간 토큰을 발급한다.
  2. 사용자는 토큰을 가지고 권한이 필요한 API 작업을 요청한다.
  3. 서버는 토큰의 유효성 검사를 통해 요청에 응답한다.

JWT 토큰 특징

👍 장점

  • 서버에서 사용자 정보를 저장해둘 필요가 없어 서버 스케일링시 큰 문제가 없다.
  • 별도의 I/O 작업 없는 빠른 인증 처리 가능

👎 단점

  • 한번 발급된 토큰은 수정 및 폐기가 불가능하다.(클라이언트에게 있으니깐!)
    • 그렇기 때문에 유효기간을 짧게 설정해주는것이 중요하다.
  • 토큰의 길이가 늘어날 수록 네트워크 부하가 심해진다.

댓글 공유

SPA는 무엇인가요?

카테고리 CS

✏️ SPA란?

Single Page Application의 약자로, 단일 페이지로 구성된 애플리케이션을 말한다.

전통적으로 브라우저는 서버에서 HTML을 받아 렌더링 해주는 역할을 한다. 즉, 사용자가 다른 URL로 이동하거나 사용자 액션에 의해 화면의 UI가 변경될 때마다 서버에 HTML을 요청하고 응답받는 서버측 렌더링(SSR)을 사용한다.

하지만 오늘날 최신 SPA에서는 대화형 요소와 동적인 요소가 많아 클라이언트가 작업을 수행하고 작업에 대한 응답을 받을 수 있는 클라이언트 사이드 렌더링(CSR)을 사용한다.

🤓 SPA 동작 원리

  1. 브라우저는 웹에 필요한 소스코드 및 스타일 시트를 서버로부터 응답받아 초기 페이지를 렌더링한다.
  2. 대화형 요소, 동적인 요소에 의해 새 페이지가 필요해지면, 새 페이지에 필요한 새 데이터는 서버에게 AJAX 요청을 보낸다. 이후 SPA는 초기 페이지 로드시 다운로드한 JavaScript를 통해 응답받은 데이터를 페이지에 동적으로 업데이트한다.

사용자가 다른 페이지로 이동할 때 새로고침이 발생하지 않는다. 페이지의 URL은 HTML5 History API를 통해 업데이트된다.

📚 SPA 특징

👍 장점

  • 앱의 반응속도가 빨라지고 새로고침으로 인한 깜빡임 현상이 사라진다.
  • 페이지 렌더링 시 중복된 데이터를 다시 다운받을 필요가 없어 서버에 대한 HTTP 요청이 줄어든다.
  • 클라이언트와 서버간의 문제를 명확히 구분할 수 있다. 서버 코드를 수정하지 않고도 다양한 플랫폼을 위한 새로운 클라이언트를 쉽게 구축할 수 있다.

👎 단점

  • 초기에 웹에 필요한 모든 데이터를 서버로 부터 로드 받기 때문에 초기 페이지 로드 시간이 길다.
  • 모든 요청을 단일 진입점으로 라우트하고 클라이언트 측 라우팅이 단일 진입점에서 응답받을 수 있도록 서버를 구성하는 추가 단계가 필요하다.
1
2
3
4
5
6
// server.js

// 브라우저 새로고침 시 서버는 index.html 파일을 응답하고 클라이언트는 window.location.pathname을 참조해 라우팅
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "public/index.html"));
});
  • SPA는 JavaScript에 의존하는데 검색 엔진이 크롤링 중에 JavaScript를 실행하지 않기 때문에 해당 웹이 빈 컨텐츠로 표시될 수 있어 SEO 최적화에 부적합하다.

    이를 해결하기 위해 서버측에서 앱을 렌더링하거나 Prerender 같은 서비스를 사용하여 브라우저에서 JavaScript를 렌더링하고 정적 HTML을 저장한 다음 크롤러에게 반환할 수 있다.

댓글 공유

브라우저란?

브라우저는 HTML, CSS, Javascript로 작성된 텍스트 문서를 서버에게 요청하여 응답을 받아 의미있는 단위인 토큰으로 파싱하여 시각적으로 렌더링 해주는 역할을 담당한다.

브라우저 렌더링 과정

1. 요청과 응답

서버에 요청하기 위해 브라우저는 주소창을 제공한다. 주소창에 URL을 입력하면 URL의 호스트 이름이 DNS를 통해 IP주소로 변환되고 IP 주소를 갖는 서버에게 요청을 전송한다.

이렇게 요청을 보내면 서버는 서버의 루트 폴더에 존재하는 정적파일로 응답을 보낸다. 기본적으로 index.html이다.

2. HTML 파싱과 DOM 생성

브라우저 요청에 의해 서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트이다.

그래서 HTML 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM을 생성한다.

  1. 문자열로 변환된 HTML문서를 토큰화한다.
  2. 각 토큰을 객체로 변환하여 노드를 생성한다. 노드는 DOM을 구성하는 기본 요소이다. ex) 문서 노드, 요소 노드 등
  3. HTML 문서는 중첩관계를 통해 부자관계가 형성된다. 이러한 부자관계를 반영하여 모든 노드들을 트리 자료구조로 구성한다. 이러한 노드들로 구성된 트리 자료구조를 DOM이라 부른다.

3. CSS 파싱과 CSSOM 생성

렌더링 엔진은 HTML을 한줄씩 읽어나가며 순차적으로 파싱하여 DOM을 생성해 나간다. DOM을 생성하다가 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시중단한다.

그 결과 CSS 파일을 서버에 요청하여 응답받은 CSS 파일이나 style 태그 내의 CSS를 HTML과 동일한 과정으로 토큰화 생성 → CSSOM 생성 과정을 거친다. 이후 파싱이 완료되면 HTML 파싱이 중단된 지점부터 다시 HTML을 파싱하기 시작한다.

4. 렌더 트리 생성

앞선 과정에서 생성된 DOM과 CSSOM은 렌더링을 위해 렌더 트리로 결합된다. 이 때 브라우저 화면에 렌더링되지 않는 노드(meta태그, script 태그 등)와 CSS에 의해 표시되지 않는(display:none) 노드들은 포함하지 않는다.

지금까지의 렌더링 과정은 여러번 반복되서 실행될 수 있다. 렌더링이 반복 실행되는 원인은 다음과 같다.

  • 자바스크립트에 의한 노드 추가 또는 삭제
  • 브라우저 창의 리사이징에 의한 viewport 크기 변경
  • HTML 요소의 레이아웃(위치와 크기)을 변경시키는 width, height, margin, padding, border, display, position 등의 스타일 변경

이러한 리렌더링은 비용이 많이 들고 성능에 악영향을 주므로 리렌더링이 적게 발생하도록 하여야한다.

5. 자바스크립트 파싱과 실행

HTML 파싱의 결과물 DOM은 HTML 문서의 구조와 정보뿐 아니라 HTML 요소와 스타일을 변경할 수 있는 프로그래밍 인터페이스로서 DOM API를 제공한다.

즉, DOM API를 사용하여 이미 생성된 DOM을 동적으로 조작할 수 있다.

CSS 파싱과정과 마찬가지로 script 태그 만나면 DOM 생성을 일시 중단한다.

이후 자바스크립트 파일을 서버에 요청하여 응답받은 파일이나 script 태그내의 코드를 파싱하기 위해 자바스크립트 엔진에 제어권을 넘긴다. (렌더링 엔진 → 자바스크립트 엔진으로 제어권 이동)

이후 자바스크립트 파싱과 실행이 종료되면 렌더링 엔진으로 다시 제어권 넘겨 HTML 파싱 중단된 시점부터 DOM 생성을 재개한다.

6. 리플로우와 리페인트

만약 자바스크립트 코드에 DOM, CSSOM을 변경하는 DOM API가 사용된 경우 DOM, CSSOM이 변경되고 변경된 DOM, CSSOM으로 다시 렌더트리로 결합되고 레이아웃과 페인트 과정을 거쳐 브라우저 화면에 다시 렌더링한다. 이를 리플로우, 리페인트라고 한다.

리플로우가 발생하면 성능을 저하시키므로 리플로우가 발생하는 메서드 사용을 최소화 하여야 한다. 일단 가독성 위주로 코딩을 하되 성능 측정하여 렌더링에 2초 이상 걸린다면 리팩터링을 하여 성능을 개선해야한다.

댓글 공유

📌 호이스팅이란?

호이스팅이란 코드에서 선언이 마치 코드 최상단으로 옮겨진 것처럼 동작하는 것을 말한다.

1
2
3
4
5
6
7
8
9
// var 선언은 에러를 발생하지 않는다.
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1

// let/const 선언은 에러를 발생 시킨다.
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2

마치 위 예제를 보았을 때, let, const 키워드로 선언할 경우 호이스팅이 발생하지 않는 것처럼 느낄 수 있는데, let, const 키워드로 선언했을 경우에도 호이스팅이 발생한다.

위 예제에서 오류를 발생시키는 것은 var 키워드의 경우 변수 선언 단계와 초기화 단계가 동시에 진행되므로 이 때, 변수가 선언됨과 동시에 undefined로 초기화되어 오류를 발생시키지 않는다.

반면, let, const 키워드는 선언단계와 초기화 단계가 분리되어 진행되어 초기화 단계 이전에 해당 변수를 참조하게 되면 참조에러를 발생시킨다.

선언단계 ~ 초기화 단계 시작 전까지 구간을 Temporal Dead Zone(일시적 사각 지대)라고 한다.

📚 함수 호이스팅

호이스팅은 선언 단계가 마치 최상단으로 올라간 것처럼 동작하는 것을 말하므로 이는 변수 선언과 함수 선언 모두 해당될 수 있다.

변수 선언은 앞의 예제에서 다루었으니 함수 호이스팅 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
const numbers = [1, 2, 3, 4, 5];
const doubled = doubleNum(numbers);
const trippled = trippleNum(numbers);

const doubleNum = (num) => {
return num.map((n) => n * 2);
};

const trippleNum = (num) => {
return num.map((n) => n * 3);
};

// Uncaught ReferenceError: doubleNum is not defined
  • 위 예제에서는 화살표 함수가 표현식으로 선언되었으므로 변수 호이스팅이 발생하여 오류를 발생하는 것을 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
const numbers = [1, 2, 3, 4, 5];
const doubled = doubleNum(numbers);
const trippled = trippleNum(numbers);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(trippled); // [3, 6, 9, 12, 15]

function doubleNum(num) {
return num.map((n) => n * 2);
}

function trippleNum(num) {
return num.map((n) => n * 3);
}
  • 위 예제에서는 함수 선언식으로 함수를 정의하여서 함수 호이스팅이 발생하여 오류를 발생시키지 않았다.

댓글 공유

📚 클로저란 무엇인가요?

클로저란, ECMAScript에선 공식 사양으로 등장하진 않지만, 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.

❓ 클로저는 왜 사용 하나요?

클로저는 함수형 프로그래밍 방식에서 정보 은닉과 캡슐화를 통해 정보를 보다 안전하게 관리하기 위해 사용한다.

C++과 같은 객체지향 프로그래밍 언어와 달리 자바스크립트는 private 같은 접근 제한자를 제공하지 않는다.

때문에 자바스크립트에서 객체 프로퍼티를 안전하게 참조 및 변경하기 위해 클로저를 사용한다.

❓ 클로저는 언제, 어떻게 사용하나요?

클로저를 구현하는 방법은 아래 두가지 조건을 만족시켜 구현할 수 있다.

  1. 함수 안에 내부함수를 정의하고 내부함수는 외부함수의 식별자를 참조한다.
  2. 외부 함수의 반환값으로 내부함수를 반환하여 변수에 할당한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const counter = (function () {
// 카운트 상태 변수
let num = 0;

// 클로저인 메서드를 갖는 객체를 반환한다.
return {
increase() {
return ++num;
},
decrease() {
return num > 0 ? --num : 0;
},
};
})();

console.log(counter.increase()); // 1
console.log(counter.increase()); // 2

console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0

❗️ 주의점

클로저는 외부함수의 실행 컨텍스트가 제거되어도 내부함수가 외부함수의 변수를 기억하고 있는데 이는 추후에 메모리 누수가 발생할 수 있다.

예를 들어 리액트에서 useEffect 함수를 사용하여 렌더링 이후에 이벤트를 등록하였을 때, 이를 다시 렌더링이 되었을 때, 기존 이벤트를 클린업 해주지 않는 다면 같은 역할을 하는 이벤트가 중복등록이 되고 이는 성능상 문제를 일으킬 수 있으므로 이러한 점을 주의해야한다.

📌 정리

클로저는 보다 안전하게 객체를 다루기 위해 사용한다.

하지만 ES6에서 클래스가 도입되어 다른 객체지향 프로그래밍 언어처럼 private 접근 제한자가 생겨나 객체를 안전하게 관리할 수 있게 되었다.

예전에 페어프로그래밍을 하면서 DOM 요소를 참조하고 변경하는 클로저함수만 외부로 빼내어 해당 함수로만 DOM 요소를 조작할 수 있도록 코드를 구현해본 경험이 있습니다.

댓글 공유

프로토타입이란?

자바스크립트는 프로토타입을 기반으로 상속을 구현하는 언어이다. 프로토타입은 엄밀히 말하면 프로토타입 객체이다.

프로토타입 객체는 어떤 객체의 상위(부모) 객체의 역할을 한다.

프로토타입을 왜 사용하나요?

객체 간 상속을 구현하기 위해 사용한다. 즉, 프로토타입을 상속 받은 하위(자식) 객체는 상위(부모) 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있다.

프로토타입이 어떻게 동작하나요?

프로토타입은 객체 생성 방식에 따라 객체가 생성될 때 결정되고 해당 객체의 [[Prototype]] 내부 슬롯에 저장된다.

__proto__접근자 프로퍼티를 통해서 프로토타입에 간접적으로 접근이 가능하다.

prototype

  • 생성자 함수가 생성한 객체 : __proto__접근자 프로퍼티로 자신의 프로토타입에 간접적 접근 가능
  • 프로토타입 : 자신의 constructor 프로퍼티로 생성자 함수에 접근 가능
  • 생성자 함수 : 자신의 prototype 프로퍼티로 프로토타입에 접근 가능

사실 모든 객체는 __proto__접근자 프로퍼티를 가지고 있다. 그러므로 모든 객체는 __proto__접근자 프로퍼티로 자신의 프로토타입 객체에 간접적으로 접근할 수 있다.

프로토타입 체인이란?

자바스크립트의 모든 객체는 프로토타입 기반 상속을 받고 있다. 자바스크립트는 객체의 프로퍼티에 접근할 때 해당 객체에 접근하려는 프로퍼티가 없다면, [[Prototype]] 내부슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.

이를 프로토타입 체인이라고 한다.

프로토타입 체인

댓글 공유

this는 무엇인가?

this는 자바스크립트 언어에서 매우 추상적인 부분 중에 하나이다. 그래도 한마디로 단정짓는다면, this는 자기 자신을 나타내기 위한 키워드이다.

좀 더 자세히 말하면 자신이 속한 객체나 자신이 생성할 객체를 가리키는 자기 참조 변수이다.

  • this는 자바스크립트 엔진에 의해 암묵적으로 생성된다.

함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다. arguments 객체를 함수 내부에서 지역변수처럼 사용할 수 있듯이 this도 마찬가지이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// this로 자신이 속한 객체 참조
const test = {
value: 10,
getValue() {
return this.value;
},
};

console.log(test.getValue()); // 10

// this로 자신이 생성할 객체 참조
function Circle(radius) {
this.radius = radius;
}

Circle.prototype.getDiameter = function () {
return 2 * this.radius;
};

const circle = new Circle(5);
console.log(circle.getDiameter()); // 10

this에 바인딩될 값은 함수 호출 방식에 의해 동적으로 결정되는 특징이 있다.

그러므로 this를 어떻게 호출하느냐에 따라 this 값이 달라지게 된다.

this의 작동원리 in JavaScript

1. 일반 함수로 호출

일반 함수로 호출할 경우 해당 함수 내부의 this는 전역 객체에 바인딩된다.

이 때, 중첩함수, 콜백함수가 일반함수로 호출되는 경우에도 마찬가지이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const obj = {
value: 100,
foo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
console.log("foo's this.value: ", this.value); // 100

// 메서드 내에서 정의한 중첩 함수
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}

// 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩된다.
bar();
},
// 콜백함수가 일반함수로 호출되는 경우
boo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
// 콜백 함수 내부의 this에는 전역 객체가 바인딩된다.
setTimeout(function () {
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
},
};

2. new 키워드 사용하여 생성자 함수로 호출

new 키워드로 함수를 호출할 경우 자바스크립트는 해당 함수를 생성자 함수로 판단하고 암시적으로 다음 행동을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.

// 2. this에 바인딩되어 있는 인스턴스를 초기화
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};

// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
}

// 인스턴스 생성. Circle 생성자 함수는 암묵적으로 this를 반환한다.
const circle = new Circle(1);
  1. 새로운 객체를 생성하고 this를 바인딩한다.
  2. this에 바인딩 되어 있는 객체를 초기화한다.
  3. 해당 함수는 return 하는 값이 없다면 this가 암묵적으로 반환된다.

생성자 함수는 this가 바인딩된 객체를 암묵적으로 반환하기 때문에, 생성자 함수로 호출 시 생성자 함수 내부의 this는 생성자 함수가 미래에 생성할 객체에 바인딩된다.

new 키워드를 사용한 함수 내부에서 return을 명시적으로 해준다는 것은 this를 암묵적으로 반환하는 생성자 함수 행동을 훼손하므로 이는 안티 패턴이다.

3. 메서드로 호출

메서드란, 객체의 프로퍼티 값이 함수인 경우를 해당 함수를 메서드라고 부른다.

메서드로 호출될 경우 메서드 내부의 this는 해당 메서드가 속한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const person = {
name: "Lee",
getName() {
// 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
return this.name;
},
};
const people = {
name: "Kim",
getName() {
return this.name;
},
};

console.log(person.getName()); // Lee
console.log(people.getName()); // Kim

getName()이라는 메서드가 person 객체 내부에 있으니 메서드로 호출 시 this 바인딩이 메서드를 포함하는 객체인 person에 될 것이라 생각하는 것은 잘못된 생각이다.

1
2
3
4
5
6
7
8
const anotherPerson = {
name: "Kim",
};
// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;

// getName 메서드를 호출한 객체는 anotherPerson이다.
console.log(anotherPerson.getName()); // Kim
  • 위와 같이 getName 메서드를 anotherPerson 객체의 메서드로 할당한 경우, getName 메서드 내부의 this는 자신을 호출한 객체에 바인딩 되기 때문에 anotherPerson.name을 참조하게 된다.

4. apply,call,bind 함수에 의한 간접 호출

이 경우 함수 내부의 this는 인수로 전달된 객체에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getThisBinding() {
return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

console.log(getThisBinding()); // window

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a: 1}
console.log(getThisBinding.call(thisArg)); // {a: 1}

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)()); // {a: 1}
  • call, apply의 반환값은 호출한 함수의 반환값이다.
  • bind는 함수를 호출하지 않고 인수로 전달받은 객체로 this 바인딩이 교체된 함수를 새롭게 생성하여 반환한다.

과거에는 this를 불일치 문제를 bind 함수를 사용하여 해결하였다.

1
2
3
4
5
6
7
8
9
10
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback, 100);
},
};

person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is .
});
  • 일반 함수로 호출된 콜백 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
  • 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ‘’이다.
1
2
3
4
5
6
7
8
9
10
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback.bind(this), 100);
},
};

person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is Lee.
});

5. 화살표 함수로 호출

화살표 함수로 호출할 경우 위의 규칙을 모두 무시하고 화살표 함수가 생성된 시점에서 상위 스코프의 this에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arrow = () => {
return this; // Window
};

const obj = {
far() {
return arrow();
},
};

const anotherObj = {
baz() {
return (() => this)();
},
};

obj.far(); // Window
anotherObj.baz(); // anotherObj

this 화살표함수 예시

  • arrow 함수는 생성된 시점에서 상위 스코프의 this는 전역객체(Window)이다.
  • anotherObj 객체의 baz 메서드 내부의 this는 자신을 호출한 객체에 바인딩된다. 그러므로 화살표 함수 내부의 this는 자신이 생성된 시점에서 상위 스코프인 baz의 this와 일치한다.

댓글 공유

이벤트 위임에 대해 알기 전 우선 이벤트와 이벤트 핸들러에 대해 알고 가자.

이벤트와 이벤트 핸들러

브라우저는 사용자가 어떤 행동을 하였을 때, 이를 감지하여 이벤트를 발생시킨다.

ex) 참된 개발자: 브라우저야 사용자가 제출하기 버튼을 클릭 했을 때, 정보를 제출하는 함수를 호출(실행)해줘~

이 때 사용자가 언제 행동을 할지 모르기 때문에 개발자는 브라우저에게 대신 함수를 호출해달라고 한다.

이벤트 발생 시 호출할 함수를 이벤트 핸들러라고 한다.

  • 이벤트 발생 시 호출될 함수: 이벤트 핸들러
  • 이벤트 발생 시 브라우저에게 이벤트 핸들러 호출을 위임: 이벤트 핸들러 등록
  • 이벤트 핸들러를 등록하는 방법은 여러가지가 있는데 설명을 위해 이번 장에서는 이벤트 리스너 방식을 사용하겠습니다.

이벤트 위임

이벤트 위임은 이벤트 리스너를 하위 요소에 추가하는 대신 상위 요소에 추가하여 이벤트를 위임하는 것을 말한다.

이벤트 리스너는 이벤트 버블링(Event Bubbling)으로 인해 하위 요소에서 이벤트가 발생할 때 마다 이벤트 리스너가 실행된다.

아래 그림은 이벤트 전파가 일어나는 흐름을 설명한 그림이다. 이벤트 전파에 대한 설명은 그림으로 대체한다.

이벤트 Flow

이벤트 위임을 사용하는 이유

이벤트 위임을 사용하면 하위 요소에 같은 동작을 하는 이벤트를 중복해서 등록해주지 않고 상위 요소에 이벤트 위임을 통해 등록하여 중복을 피하고 불필요한 메모리 낭비를 줄일 수 있다.

또한, 제거된 요소에 이벤트를 해제하고 새 요소에 이벤트를 다시 등록하는 번거로운 작업을 할 필요가 없어지므로 적절한 상황에 맞게 유용하게 사용할 수 있다.

댓글 공유

loco9939

author.bio


author.job