Lsiron

Java Script -This, 실행컨텍스트, prototype, closure (feat. Elice) 본문

언어/Java Script

Java Script -This, 실행컨텍스트, prototype, closure (feat. Elice)

Lsiron 2024. 5. 9. 20:03

1. This (면접 단골질문)

  • 전역에서의 this는 기본적으로 window 객체이다.
  • 일반 함수에서의 this는 window이다. 다만 'use strict를 사용하는  strict mode에서는 undefined이다.
  • 객체의 method로 호출될 때 this는 해당 객체를 가리킨다.       
let game = '게임';
let steam = {
  game: '스팀게임',
  dogame: dogameFn
}

function dogameFn() {
  console.log(`Lsiron이 ${this.game}을 합니다.`);    
}

// (1) 객체 method 호출
steam.game(); // Lsiron이 스팀게임을 합니다.

// (2) 함수 직접 호출
dogameFn(); // Lsiron이 게임을 합니다.

 

화살표함수 This 없음 This 호출 가능 함수가 선언된 곳의 This를 따름(중요)
일반함수 This 가지고 있음 This 호출 가능 누가 일반 함수를 호출했는지를 보면됨 
ex) someone.func(); 라면 someone이 
This의 대상이 된다.
let game = {
  name: 'overWatch',
  f1: () => {
    console.log(this);
  },
  f2: function () {
    console.log(this);
  },
};
game.f1(); // window , 화살표함수는 객체 game이 바라보는 this를 봄
game.f2(); //overWatch, 일반함수는 누가 일반 함수를 호출했는지를 봄
  This 중첩함수 시 This
일반함수 함수를 부르는 대상 무조건 window
화살표함수 함수가 선언된 곳 함수가 선언된 곳

 

call, apply, bind를 이용하면 'this'를 원하는 객체에 연결할 수 있다.

내부 함수를 사용하면서도 this가 객체를 가리키도록 연결해 줄 수있다.

let game = '게임';
let steam = {
  game: '스팀 게임',
  doGame: function() {
    doGameFn();      
  },
  doGameCall: function() {
    // 여기서의 this는 steam
    doGameFn.call(this);
  },  
  doGameBind: function() {
    // 여기서의 this는 steam
    (doGameFn.bind(this))();
  }
}

function doGameFn() {
  console.log(`"Lsiron이" ${this.game}을 합니다.`);    
}

steam.doGame();     // Lsiron이 게임을 합니다.
steam.doGameCall(); // Lsiron이 스팀게임을 합니다.
steam.doGameBind(); // Lsiron이 스팀게임을 합니다.

 

2. Prototype

자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어있고 이 부모 객체를 프로토타입이라고 한다.

prototype도 면접에 자주나온다. ex) prototype에 대해 설명해보시오.

쉽게말해 사용자가 쓰는 Math.floor() , split(), 심지어 array까지, 사용자가 직접 프로세스를 만들지 않아도 사용할 수 있는 이유인 셈이다.

https://jake-seo-dev.tistory.com/319 설명이 아주 잘 나와있다... 

3. 실행컨텍스트

  • 자바스크립트의 함수는 선언-실행-종료의 흐름이 있다.
  • 함수는 크게 봤을 때 선언부와 할당부로 나뉘어진다.
  • 선언부와 할당부는 모두 함수가 실행이 될 때 시작한다.

 

var, let, const, function 등 예약어를 통해 만들어진 변수나 함수를 수집한다.

수집되는 과정에서 var는 undefined로 초기화되고 let, const는 값이 초기화가 되기 전 까지 접근할 수없는 상태로 유지된다.

(TDZ)(호이스팅) cf) TDZ( Temporal Dead Zone ) - const, let : 변수가 접근할 수 없는 상태에서 접근하려고 할 때 접근하지 못하게 막힌 현상.

선언부에서 미리 초기화가 되는 대상의 경우가 호이스팅 이라고 표현하는 부분.

 

let, const로 만든 변수는 코드에 명시된 초기 값으로 값을 할당한다. 이때 let, const에 비로소 접근이 가능하다

 

이렇게 선언된 실행컨텍스트는 실행컨텍스트가 담기는 스택 구조의 메모리 공간이 존재하는데 이를 콜스택이라 부른다.

  • 콜스택 가장 아래에는 전역컨텍스트가 있다.
  • 함수가 실행될때 함수의 실행컨텍스트가 올라가고 종료될때 제거된다.

콜스택을 범람시키는 간단한 방법 : 재귀함수를 사용한다.

 

4. closure (면접질문 100퍼센트)

함수의 실행컨텍스트는 기본적으로 함수 종료시 콜스택에서 제거된다. 이 때, 실행컨텍스트가 종료되면 더 이상 함수 내부의 변수나 함수를 사용할 수 없어야한다. 하지만, 함수가 종료돼서 콜스택에서 실행 컨텍스트가 빠졌음에도 불구하고 여전히 연결 링크가 살아있는 경우가 있다.

 

이를 봤을때 클로저는 함수가 아니라 현상이다.

 

내부 함수가 종료됐음에도 여전히 외부 함수의 변수를 바라보는 링크가 살아있을 때 그 현상을 클로저라 부른다.

클로저를 사용하는 이유는 상태를 안전하게 은닉하고 보존시키기 위함이다. 그리고 이를 수정하는 방법은 특정 함수한테만 그 권한을 준다.

즉, 다수의 개발자와 함께 일을 할 때 실수를 방지하고 더 탄탄한 코드를 만들기위한 코드 패턴이다.

면접장에서 closure를 설명할 때 렉시컬 단어를 잘못 꺼내면 탈락할 수 있음.

 

function sayHello () {
  const a = 'Hello';
  const b = 'World';
 
  function sumString () {
    console.log(a + ' ' + b);
  }
 
  return sumString;
}

const myFunc = sayHello();

myFunc(); // 'Hello World'

 

예제를 살펴보면, myFunc라는 변수는 sayHello 함수를 호출하고 있다. 그래서 myFunc를 실행하게 되면 어떠한 문제 없이 Hello World가 잘 출력된다.

여기서 살펴볼 점은 myFunc의 부분은 변수 a와 b가 담겨 있는 sayHello 함수 스코프의 바깥에 있는데도 불구하고 a와 b를 합친 Hello World를 잘 출력한다는 것이다.

 

그 이유가 바로 클로저(Closure) 때문이다. 모든 자바스크립트 함수는 선언(생성)될 당시에 클로저가 형성되어 주변 환경, 즉 렉시컬 스코프를 기억할 수 있게 되는 것이다.

 

렉시컬 스코프???

lexical의 사전적 의미는 "어휘의"라는 의미이지만, "어휘 범위"라고 하면 무슨 뜻인지 잘 와닿지 않는다. 따라서 렉시컬 스코프를 번역할 때는 흔히 "정적 범위", "정적 스코프"라고 번역한다.

렉시컬 스코프란, 함수를 어디서 호출하는지가 아니라 어떤 스코프에 선언하였는지에 따라 변수의 실제 값이 결정된다 것이다.

let text = 'global';

function foo() {
  console.log(text);
}

function bar() {
  let text = 'bar';
  foo();
}

bar() // 무엇이 출력될까?

 

위에서 bar함수를 실행하면, bar가 아닌 global이 출력된다.

bar안에서 foo를 호출했으니, text의 값을 bar안에서 찾아야 되겠지만 이는 틀렸다.

함수를 어디서 호출하는지가 아니라 '처음에 어떤 스코프에 선언하였는지'에 따라 변수의 실제 값이 결정된다.

즉, 스코프란 코드를 실행하면서 바뀌는 것이 아니라 처음 작성한 그 스코프로 결정된다는 것이다.

따라서, foo는 bar에서 호출되든 어떤 함수 안에서 호출되든지 상관없이, 무조건 자기 자신의 스코프를 찾아보고 그 이후에는 전역 스코프를 찾는다.  foo가 한번 선언된 이상 전역변수 text를 참조하는 것을 바꿀 수는 없다.

global이 아닌 bar로 출력되게 하고 싶으면 bar함수 안에서 text 변수를 새로 선언할 것이 아니라,

맨 처음에 전역범위에 선언했던 text변수의 값을 바꾸어 주면 된다.

let text = 'global';

function foo() {
  console.log(text);
}

function bar() {
  text = 'bar';
  foo();
}

bar()

번외. Garbage Collection

언어는 저마다 메모리 공간을 확보하기 위한 전략을 가지고있다.

 

개발자가 메모리 공간을 신경쓰지 않도록 적당한 시점에 자동으로 메모리 공간을 관리하거나 회수하기 위해 가비지 콜렉터가 등장한다.

가비지 콜렉터는 메모리를 적당한 시점이 되면 메모리에서 삭제한다. JS에선 개발자가 가비지 콜렉션을 임의로 실행할 수 없다.

 

가비지 콜렉터는 사용하지 않는 데이터를 표시해놨다가 때가 되면 메모리에서 지운다. 

가비지 콜렉터가 메모리에서 객체를 제거하는 과정을 가비지 콜렉션이라고 부른다.

즉, 아무도 찾지않는 데이터만 지워질 수 있다. 

클로저는 여전히 사용하고 있는 곳이 있기 때문에 가비지 콜렉션의 대상이 되지 않는다. (내부 함수가 외부함수의 변수를 참조하기 때문에)

가비지 콜렉터가 지우지 못하는 데이터가 쌓이다보면 그 양이 많아지게 된다. 지워지지 않는 데이터는 메모리의 공간을 여전히 차지하고 있다. 사용하지도 않고 지워지지도 않는 메모리 공간을 불필요하게 차지하고 있는 현상을 메모리 누수라고 한다. 메모리 누수가 심해지면 프로그램이 느려질 수 있다.

 

가비지 콜렉터가 어떤 알고리즘으로 동작하나요? - 20번 면접보면 1번은 나옴

★ 이 외 실습하며 배운 것들

function restOperation(...nums) {
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];

  const [first, ...rest] = nums;
  return first * restOperation(...rest);
}
console.log(restOperation(1,2,3,4));
번째 호출: first = 1, rest = [2, 3, 4] , 1 * restOperation(2, 3, 4) 계산
번째 호출: first = 2, rest = [3, 4] , 2 * restOperation(3, 4) 계산
번째 호출: first = 3, rest = [4] , 3 * restOperation(4) 계산
번째 호출: first = 4, rest = [] , 4 반환
이렇게 재귀 호출이 역순으로 종료되면서 호출에서 계산된 값을 곱해가며 최종 결과를 계산.
따라서 restOperation([1, 2, 3, 4]) 1 * 2 * 3 * 4 = 24 반환됩니다.

 

화살표함수 표현 방식
(num => first * num)
(num) => first * num
중괄호는 화살표 함수가 줄로 작성된 경우에는 생략할 있다.
이때는 return 문을 사용하지 않고도 값을 반환할 있다.
(num) => { return first * num; }
화살표 함수가 줄로 작성된 경우, return 문을 생략할 있다.

 

 

forEach() - 배열순회

https://codingeverybody.kr/%ec%9e%90%eb%b0%94%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-foreach-%ed%95%a8%ec%88%98/

map() - 배열에 조건 추가

https://codingeverybody.kr/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-map-%ED%95%A8%EC%88%98/

filter() - 배열요소 T/F 판별

https://codingeverybody.kr/%ec%9e%90%eb%b0%94%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-filter-%ed%95%a8%ec%88%98/

reduce() - 배열 요소들의 합

https://codingeverybody.kr/%ec%9e%90%eb%b0%94%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-reduce-%ed%95%a8%ec%88%98/

split("") - " " 큰 따옴표 내용물 기준으로 배열로 변환 <=> join("")

https://codingeverybody.kr/%ec%9e%90%eb%b0%94%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-%eb%ac%b8%ec%9e%90%ec%97%b4%ec%9d%84-%eb%b0%b0%ec%97%b4%eb%a1%9c-%eb%b3%80%ed%99%98-split-%ed%95%a8%ec%88%98/

reverse() - 배열을 역순으로 변환

https://codingeverybody.kr/%ec%9e%90%eb%b0%94%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-%eb%b0%b0%ec%97%b4-%ec%97%ad%ec%88%9c-%ec%a0%95%eb%a0%ac-reverse-%ed%95%a8%ec%88%98/

join(" ") - 배열을 문자열로 만듦 , 각 배열속 배열들을 합쳐서 문자열로 만들기도 함 <=> split(" ")

 https://codingeverybody.kr/%ec%9e%90%eb%b0%94%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-%eb%b0%b0%ec%97%b4%ec%9d%84-%eb%ac%b8%ec%9e%90%ec%97%b4%eb%a1%9c-%eb%b3%80%ed%99%98-join-%ed%95%a8%ec%88%98/

toLocalString() - 대표적으로 3자리 숫자마다 콤마

현업에서 자주 사용함

https://lily-im.tistory.com/64

substr(), substring(), slice() - 문자열 자르기

https://gent.tistory.com/414

Math.sqrt() - 루트값 구하기, Math.pow() - 제곱값 구하기

https://velog.io/@chayezo/%EB%A3%A8%ED%8A%B8%EC%99%80-%EC%A0%9C%EA%B3%B1-%EA%B5%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

'언어 > Java Script' 카테고리의 다른 글

new Date().getTime();  (0) 2024.05.16
실무에서 자주 쓰이는 Function Method  (0) 2024.05.12
Java Script -DOM 객체 (feat. Elice)  (0) 2024.05.08
Java Script -함수 (feat. Elice)  (0) 2024.05.03
4.조건문과 반복문  (0) 2024.04.22