본문 바로가기
JS 공부

JS문법종합반 정리 3주차 03 - 콜스택, 실행컨텍스트, VE, LE, 식별자정보, 외부환경정보, 호이스팅, 스코프, this, call(), apply(), bind()

by 나노다 2024. 11. 7.

콜 스택

  • 후입 선출의 보관 방식인 스택. 실행 컨텍스트를 보관하는 방식
// ---- 1번 전역은 늘 먼저 스택
var a = 1;
function outer() {
	function inner() {
		console.log(a); //undefined
		var a = 3;
	}
	inner(); // ---- 3번 inner가 스택됨
	console.log(a); // 1
}
outer(); // ---- 2번 outer가 스택됨
console.log(a); // 1
  • 코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료
실행 컨텍스트 코드실행 outer() 실행 inner() 실행 inner{} 끝 outer{} 끝 코드 종료
콜 스택            
    inner (in)      
  outer (in) outer (중단) outer (재개)    
전역 (in) 전역 (중단) 전역 (중단) 전역 (중단) 전역 (재개)  

 

 

실행 컨텍스트

  • 실행할 코드에 제공할 환경 정보를 모아놓은 객체. VE, LE, ThisBindings
  • 자바스크립트는 실행 컨텍스트가 활성화되는 순간 세 가지 일을 함
  • 선언된 변수를 위로 끌어올림 (hoisting)
  • 외부 환경 정보를 구성함 (outer)
  • this 값을 설정 (ThisBinding)

 

VariableEnviornment와 LexicalEnviornment

  • 둘은 똑같이 식별자 정보 record와 외부환경 정보 outer를 가짐
  • 다만 VE와 달리 LE는 변경사항을 실시간으로 적용하기 때문에, 우리는 LE를 주로 활용하게 됨.
  • 이때 VE는 LE에서 변동이 생기기 전 초기 상태를 보여주는 스냅샷으로 기능

실행 컨텍스트를 생성할 때, VE에 먼저 정보를 담고, 이를 복사해 LE를 만들어 활용함

 

 

식별자 정보 record (enviornmentRecord)

  • 수집 대상 : 함수 자체, 함수에 지정된 매개변수 식별자, 선언된 변수 식별자
  • 정보는 실행 컨텍스트 내부를 처음부터 끝까지 순서대로 수집되는데, 눈에 보이는대로가 아니고, 가상개념인 호이스팅이 적용된 순서로 수집

 

호이스팅

식별자 정보를 위로 끌어 올려 먼저 읽음

 

1) 매개변수나 변수는 선언부를 호이스팅

var x = 2; 라는 구문에서 선언부인 var x;만 호이스팅해 위에서 수집하고, 값을 할당하는 x = 2;가 자리에 남아있음

/* 예 시 */
function a (x) {
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}

 

이런 함수가 있을 때, a(1)을 실행한다고 하면 다음과 같이 호이스팅 됨

/* 매개변수 적용 */
function a () {
	var x = 1; // 매개변수 x에 1이 할당됨
	console.log(x); // 1이 반환될 듯?
	var x;
	console.log(x); // undefined가 반환될 듯?
	var x = 2;
	console.log(x); // 2가 반환될 듯?
}

/* 호이스팅된 모습 */
function a () {
	var x;
   	var x;
   	var x
   	x = 1;
	console.log(x); // 예상대로 2 반환
	console.log(x); // 실제론 1이 반환됨
	x = 2;
	console.log(x); // 예상 적중 2 반환
}

 

2) 함수 선언은 함수 전체를 호이스팅

  • 함수 선언문은 함수 전체를 호이스팅하므로, 선언 이후에만 영향주는 함수를 만들고 싶다면 이 방식은 X
/* 예시 */
function a () {
	console.log(b); // refer error 예상됨
	var b = 'bbb';
	console.log(b); // "bbb" 예상됨
	function b() { }
	console.log(b); // func~ 예상됨
}

/* 호이스팅 된 모습 */
function a () {
	var b
	function b() { }
	console.log(b); // 실제론 func~ 반환
	b = 'bbb';
	console.log(b); // "bbb" 반환
	console.log(b); // "bbb" 반환
}
  • 함수 표현식은 선언부만 호이스팅하므로, 협업 상황이나 복잡한 코드, 전역 공간 위주의 코드에선 되도록 이 방식 사용
/* 예시 */
console.log(sum(1, 2));
console.log(multiply(3, 4));

function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

var multiply = function (a, b) { // 함수 표현식 multiply
	return a + b;
}

/* 호이스팅된 모습 */
function sum (a, b) { 
	return a + b;
}
var multiply;
console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) { 
	return a + b;
}

 

 

스코프

식별자의 유효범위를 의미. 변수가 어디까지 영향을 미칠 수 있는지

 

 

스코프 체인 

  • 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것. LE의 구성요소 중 outer가 외부 환경의 참조정보를 가지고 있기 때문에 스코프 체인을 가능케 함
  • outer는 현재 호출된 함수가 선언될 당시의 LE를 참조함. 가장 가까운 요소부터 차례대로 접근이 가능

 

this

1) 전역 공간에서의 this

 전역 객체를 가리킴. 런타임 환경에 따라 브라우저면 window를, node면 global을 가리킴

 

2) 메서드 내부에서의 this

 메서드를 호출한 주체의 정보를 담음. 

/* 예시 */
var obj = {
	methodA : function () { console.log(this) },
	inner : { methodB: function() { console.log(this) }  // 중첩 객체
	}
};

obj.methodA(); // 이때의 this는 메서드A를 호출한 obj
obj.inner.methodB(); // 이때의 this는 메서드B를 호출한 obj.inner

 

3) 함수 내부에서의 this

전역에 있는 함수든, 메스드의 내부함수든 함수에서의 this는 전역객체를 가리킴 (단, addEventListener의 콜백함수 같은 예외적인 경우가 있음)

 

4) this를 우회하는 방법

  • this를 별도의 변수에 직접 할당하기
  • 화살표 함수를 활용하기 : 일반 함수와의 가장 큰 차이점은 this binding의 여부에 있음. 화살표 함수는 실행 컨텍스트를 생성할 때 바인딩 과정이 없기 때문에, this의 값이 기존대로 유지됨

 

5) 생성자 함수 내부에서의 this

항상 인스턴스를 가리킴. (인스턴스 : 생성자 함수로 만들어진 객체)

 

 

명시적 this 바인딩

1) call

  • 호출 주체인 함수를 즉시 실행하는 메서드
  • 이때 call의 첫 매개변수로 this binding할 객체를 직접 설정해줄 수 있음
var func = function (a, b, c) {
	console.log(this, a, b, c);
};

// no binding
func(1, 2, 3); // 전역객체에 바인딩돼있음 Window{ ... } 1 2 3

// 명시적 binding
// func 안에 this에는 {x: 1}이 binding돼요
func.call({ x: 1 }, 4, 5, 6}; // 직접 입력한 객체에 바인딩 돼있음 { x: 1 } 4 5 6

 

 

2) apply

  • 호출 주체인 함수를 즉시 실행한다는 점에서 call과 같지만, apply는 매개변수를 배열 형태로 받아야할 때 사용
  • 마찬가지 방법으로 첫 매개변수에 this를 바인딩할 객체를 직접 설정할 수 있음
var func = function (a, b, c) {
	console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

var obj = {
	a: 1,
	method: function (x, y) {
		console.log(this.a, x, y);
	}
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 

3) bind

  • call이나 apply와 달리 함수를 즉시 호출하진 않음. 
  • 대신 명시적으로 바인딩한 this와 인수들을 바탕으로 새로운 함수를 반환하는 메서드
  • 바인딩 위해 많이 활용하게 될 방식, 또는 화살표 함수도 굉장히 많이 쓰임
  • bind를 적용해 새로 만든 함수는 name 프로퍼티에 "bound"란 접두어가 붙기 때문에 추적 관리가 용이함
  • 함수에 this를 미리 적용하거나 부분 적용 함수를 구현
var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // 이때의 this는 window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 미리 바인딩
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 바인딩된 this와 인자 4와 5가 적용된 함수를 반환
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9