TIL

TIL 8/16 - JavaScript 문법 종합반 3주차

nbcssw 2024. 8. 19. 21:06

JavaScript 문법 종합반

2. 실행 컨텍스트 (스코프, 변수, 객체, 호이스팅)

(1) 실행 컨텍스트란?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체

자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 다음과 같은 일을 함

  1. 선언된 변수를 위로 끌어올림 (호이스팅)
  2. 외부 환경 정보를 구성함
  3. this 값을 설정함

실행 컨텍스트를 이해하기 위해서는 먼저 콜 스택에 대한 이해가 필요함!!

 

콜 스택

실행 컨텍스트를 구성하고 이것을 콜 스택에 쌓아올리는데, 이 때 가장 위에 쌓여있는 컨텍스트와 관련된 코드를 실행하는 방법으로 코드의 환경 및 순서를 보장할 수 있다

  1. 컨텍스트의 구성
    • 구성 방법 (여러가지 있지만, 사실 함수만 생각하면 됨)
      • 전역 공간
      • eval() 함수
      • 함수(우리가 흔히 실행 컨텍스트를 구성하는 방법)
    • 실행 컨텍스트 구성 예시
// ---- 1번
var a = 1;
function outer() {
	function inner() {
		console.log(a); //undefined
		var a = 3;
	}
	inner(); // ---- 2번
	console.log(a);
}
outer(); // ---- 3번
console.log(a);

코드 진행 순서

코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료

 

결국, 특정 실행 컨텍스트가 생성되거나 활성화되는 시점에 콜 스택 맨 위에 쌓이고, 스택 위에서부터 코드가 실행된다

 

실행 컨텍스트 객체의 실체(=담기는 정보)

  1. VariableEnvironment
    • 현재 컨텍스트 내의 식별자 정보(=record)를 갖고 있음
      • var a = 3 에서, var a 를 의미
    • 외부 환경 정보(=outer)를 갖고 있음
    • 선언 시점 LexicalEnvironment의 snapshot (초기 정보)
  2. LexicalEnvironment
    • VariableEnvironment와 동일하지만, 변경사항을 실시간으로 반영함
  3. ThisBinding
    • this 식별자가 바라봐야할 객체

 

(2) VariableEnvironment, LexicalEnvironment의 개요

VE vs LE

이 둘에 감기는 항목은 완벽하게 동일하지만, 스냅샷 유지여부에 따라 다름

  • VE: 스냅샷을 유지 (초기상태를 유지함)
  • LE: 스탭샷을 유지하지 않음. 즉, 실시간으로 변경사항을 계속해서 반영함

실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에 LE를 활용하는 것

 

구성 요소

VE와 LE 모두 동일하며, environmentRecord와 outerEnvironmentReference로 구성

  • environmentRecord(=record)
    • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨
    • 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수 식별자 등
  • outerEnvironmentReference(=outer)

 

(3) LexicalEnvironment(1) - environmentRecord(=record)와 호이스팅

  • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨. (record)
  • 수집 대상 정보: 함수에 지정된 매개변수 식별자, 함수 자체, var로 설언된 변수 식별자 등
  • 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집(실행이 아님!!)

 

호이스팅

  • hoisting: 게양. 끌어올리는 것
  • 변수정보 수집을 모두 마쳤더라도, 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태임.
  • 호이스팅이란, 변수 정보 수집 과정을 이해하기 쉽게 설명한 '가상 개념'임

호이스팅 규칙

규칙 1: 매개변수 및 변수는 선언부를 호이스팅함

// 호이스팅 적용 전 (실행하고자 하는 코드)
function a (x) {
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);
// 호이스팅 적용 후
function a () {
	var x; // 매개변수 적용
	var x;
	var x;
	// 매개변수 및 변수의 선언부를 호이스팅함(끌어올림)
	x = 1;
	console.log(x);
	console.log(x);
	x = 2;
	console.log(x);
}
a(1);

결과: 1 / 1 / 2

 

규칙 2: 함수 선언은 전체를 호이스팅

// 호이스팅 전 (실행하고자 하는 코드)
function a () {
	console.log(b);
	var b = 'bbb';
	console.log(b);
	function b() { }
	console.log(b);
}
a();
// 호이스팅 적용 후
function a () {
	var b; // 변수 선언부 호이스팅
	function b() { } // 함수 선언은 전체를 호이스팅

	console.log(b);
	b = 'bbb'; // 변수의 할당부는 원래 자리에

	console.log(b);
	console.log(b);
}
a();

결과: function b / bbb / bbb

 

3. 함수 선언문, 함수 표현식

// 함수 선언문
function a () {}
a();

// 함수 표현식: 정의한 function을 별도 변수에 할당하는 경우
var b = function () {}
b();

 

함수 호이스팅

 

 

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;
}

위 코드가 호이스팅 되면,

 

// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

// 변수는 선언부만 hoisting
var multiply; 

console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) { // 변수의 할당부는 원래 자리
	return a + b;
};

위와 같이 된다.

 

 

함수 선언문을 주의해야 하는 이유!!

...

console.log(sum(3, 4));

// 함수 선언문으로 짠 코드
// 100번째 줄 : 시니어 개발자가 짠 코드(활용하는 곳 -> 200군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
	return x + y;
}

...
...

var a = sum(1, 2); // 아래에 있는 sum함수가 되버림

...

// 함수 선언문으로 짠 코드
// 5000번째 줄 : 신입이 개발자가 짠 코드(활용하는 곳 -> 10군데)
// hoisting에 의해 함수 전체가 위로 쭉!
// 5000번째 줄 아래에만 적용하고 싶었지만, 함수 전체가 위로 올라가버려 모든 코드에 적용되버리는 사고가 일어남
function sum (x, y) {
	return x + ' + ' + y + ' = ' + (x + y);
}

...

var c = sum(1, 2);

console.log(c);

 

함수 표현식이었다면

...

console.log(sum(3, 4));

// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
	return x + y;
}

...
...

var a = sum(1, 2); // 위에 있는 sum 함수

...

// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
	return x + ' + ' + y + ' = ' + (x + y);
}

...

var c = sum(1, 2); // 아래에 있는 sum 함수

console.log(c);

 

 

(4) LexicalEnvironment(2) - 스코프, 스코프 체인, outerEnvironmentReference(=outer)

  1. 스코프: 식별자에 대한 유효범위
  2. 스코프 체인: 식별자의 유효범위를 안에서부터 바깥으로 차례대로 검색해나가는 것
  3. outerEnvironmentReference(=outer): 스코픞 체인이 가능토록 하는 것(외부 환경의 참조정보)

스코프 체인

  1. outer는 헌재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조!!
  2. 예를 들어서, a 함수 내부에서 b 함수를 선언, b 함수 내부에서 c 함수를 선언한 경우, 타고 올라가다 보면 전역 컨텍스트의 LexicalEnvironment를 참조하게됨
  3. outer는 항상 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능함
  4. 결국, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근 가능
var a = 1;
var outer = function() {
	var inner = function() {
		console.log(a); // undefined => inner 내부의 a
		var a = 3;
	};
	inner();
	console.log(a); // 1 => 전역
};
outer();
console.log(a); // 1 => 전역

 

 

3. this(정의, 활용방법, 바인딩, call, apply, bind)

(1) 상황에 따라 달라지는 this

1. this는 실행 컨텍스트가 생성될 때 결정됨( = this를 bind한다). 즉, this는 함수를 호출할 때 결정됨!

 

전역 공간에서의 this

전역 공간에서의 this는 전역 객체를 가리킴

브라우저 환경에서는 window, node 환경에서는 global을 가리킴

 

메서드로서 호출할 때 그 메서드 내부에서의 this

 - 함수 vs 메서드

     함수: 함수명(); => 함수는 그 자체로 독립적인 기능을 수행함

     메서트: 객체.메서드명(); => 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행함

 

this의 할당

// 1. 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미함
var func = function (x) {
	console.log(this, x);
};
func(1); // Window { ... } 1

// 2. 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미함
var obj = {
	method: func,
};
obj.method(2); // { method: [Function: func] } 2

 

메서드 내부에서의 this: this에는 호출을 누가 했는지에 대한 정보가 담김!

var obj = {
	methodA: function () { console.log(this) },
	inner: {
		methodB: function() { console.log(this) },
	}
};

obj.methodA();             // this === obj
obj['methodA']();          // this === obj

obj.inner.methodB();       // this === obj.inner
obj.inner['methodB']();    // this === obj.inner
obj['inner'].methodB();    // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner

 

함수로서 호출할 때 그 메서드 내부에서의 this

  • 함수 내부에서의 this
    • 어떤 함수를 함수로서 호출할 경우, this는 지정되지 않음
    • 실행 컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미
    • 따라서, 함수로서 '독립적으로' 호출할 때는 this는 항상 전역객체를 가리킴!!
  • 메서드의 내부함수에서의 this
    • 예외는 없다! 메서드의 내부여도 함수로서 호출한다면 this는 전역 객체를 의미함
var obj1 = {
	outer: function() {
		console.log(this); // obj1
		var innerFunc = function() {
			console.log(this);
		}
		innerFunc(); // 전역

		var obj2 = {
			innerMethod: innerFunc
		};
		obj2.innerMethod(); // obj2
	}
};
obj1.outer();